-
Notifications
You must be signed in to change notification settings - Fork 790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple conflicting extras installed when using explicit index assignments #9289
Comments
Interesting, thanks. I don't think the extra indexes are relevant, since you can reproduce with: [project]
name = "project"
version = "0.1.0"
requires-python = ">=3.11,<3.12"
dependencies = []
[project.optional-dependencies]
foo = [
"idna==3.10",
]
bar = [
"idna==3.9",
]
baz = [
"anyio",
]
[tool.uv]
conflicts = [
[
{ extra = "foo" },
{ extra = "bar" },
],
] From there,
|
Thanks. It also happens if the two conflicting extra groups share the common dependant: [project]
name = "project"
version = "0.1.0"
requires-python = ">=3.11,<3.12"
dependencies = []
[project.optional-dependencies]
foo = [
"idna==3.10",
"anyio",
]
bar = [
"idna==3.9",
"anyio",
]
[tool.uv]
conflicts = [
[
{ extra = "foo" },
{ extra = "bar" },
],
] |
I think i see a path to fixing this. I think it has two components:
One hiccup here is that this doesn't take groups into account, so we might not be able to reuse markers for this. We'll instead need an additional field to encode this extra/group inclusion logic. |
Working it out by hand, I think the conditional logic for each idna dependency in each of the three forks present in this example is:
Which combines to:
And simplifies to:
Note that because of how extras work, the above notation is quite misleading. It's more clearly written like this:
|
When we generate conflict markers for each resolution after the resolver runs, it turns out that generating them just from exclusion rules is not sufficient. For example, if `foo` and `bar` are declared as conflicting extras, then we end up with the following forks: A: extra != 'foo' B: extra != 'bar' C: extra != 'foo' and extra != 'bar' Now let's take an example where these forks don't share the same version for all packages. Consider a case where `idna==3.9` is in forks A and C, but where `idna==3.10` is in fork B. If we combine the markers in forks A and C through disjunction, we get the following: idna==3.9: extra != 'foo' or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' But these are clearly not disjoint. Both dependencies could be selected, for example, when neither `foo` nor `bar` are active. We can remedy this by keeping around the inclusion rules for each fork: A: extra != 'foo' and extra == 'bar' B: extra != 'bar' and extra == 'foo' C: extra != 'foo' and extra != 'bar' And so for `idna`, we have: idna==3.9: (extra != 'foo' and extra == 'bar') or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' and extra == 'foo' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' and extra == 'foo' And these *are* properly disjoint. There is no way for them both to be active. This also correctly accounts for fork C where neither `foo` nor `bar` are active, and yet, `idna==3.9` is still enabled but `idna==3.10` is not. (In the [motivating example], this comes from `baz` being enabled.) That is, this captures the idea that for `idna==3.10` to be installed, there must actually be a specific extra that is enabled. That's what makes it disjoint from `idna==3.9`. We aren't quite done yet, because this does add *too many* conflict markers to dependency edges that don't need it. In the next commit, we'll add in our world knowledge to simplify these conflict markers. [motivating example]: #9289
When we generate conflict markers for each resolution after the resolver runs, it turns out that generating them just from exclusion rules is not sufficient. For example, if `foo` and `bar` are declared as conflicting extras, then we end up with the following forks: A: extra != 'foo' B: extra != 'bar' C: extra != 'foo' and extra != 'bar' Now let's take an example where these forks don't share the same version for all packages. Consider a case where `idna==3.9` is in forks A and C, but where `idna==3.10` is in fork B. If we combine the markers in forks A and C through disjunction, we get the following: idna==3.9: extra != 'foo' or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' But these are clearly not disjoint. Both dependencies could be selected, for example, when neither `foo` nor `bar` are active. We can remedy this by keeping around the inclusion rules for each fork: A: extra != 'foo' and extra == 'bar' B: extra != 'bar' and extra == 'foo' C: extra != 'foo' and extra != 'bar' And so for `idna`, we have: idna==3.9: (extra != 'foo' and extra == 'bar') or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' and extra == 'foo' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' and extra == 'foo' And these *are* properly disjoint. There is no way for them both to be active. This also correctly accounts for fork C where neither `foo` nor `bar` are active, and yet, `idna==3.9` is still enabled but `idna==3.10` is not. (In the [motivating example], this comes from `baz` being enabled.) That is, this captures the idea that for `idna==3.10` to be installed, there must actually be a specific extra that is enabled. That's what makes it disjoint from `idna==3.9`. We aren't quite done yet, because this does add *too many* conflict markers to dependency edges that don't need it. In the next commit, we'll add in our world knowledge to simplify these conflict markers. [motivating example]: #9289
When we generate conflict markers for each resolution after the resolver runs, it turns out that generating them just from exclusion rules is not sufficient. For example, if `foo` and `bar` are declared as conflicting extras, then we end up with the following forks: A: extra != 'foo' B: extra != 'bar' C: extra != 'foo' and extra != 'bar' Now let's take an example where these forks don't share the same version for all packages. Consider a case where `idna==3.9` is in forks A and C, but where `idna==3.10` is in fork B. If we combine the markers in forks A and C through disjunction, we get the following: idna==3.9: extra != 'foo' or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' But these are clearly not disjoint. Both dependencies could be selected, for example, when neither `foo` nor `bar` are active. We can remedy this by keeping around the inclusion rules for each fork: A: extra != 'foo' and extra == 'bar' B: extra != 'bar' and extra == 'foo' C: extra != 'foo' and extra != 'bar' And so for `idna`, we have: idna==3.9: (extra != 'foo' and extra == 'bar') or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' and extra == 'foo' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' and extra == 'foo' And these *are* properly disjoint. There is no way for them both to be active. This also correctly accounts for fork C where neither `foo` nor `bar` are active, and yet, `idna==3.9` is still enabled but `idna==3.10` is not. (In the [motivating example], this comes from `baz` being enabled.) That is, this captures the idea that for `idna==3.10` to be installed, there must actually be a specific extra that is enabled. That's what makes it disjoint from `idna==3.9`. We aren't quite done yet, because this does add *too many* conflict markers to dependency edges that don't need it. In the next commit, we'll add in our world knowledge to simplify these conflict markers. [motivating example]: #9289
When we generate conflict markers for each resolution after the resolver runs, it turns out that generating them just from exclusion rules is not sufficient. For example, if `foo` and `bar` are declared as conflicting extras, then we end up with the following forks: A: extra != 'foo' B: extra != 'bar' C: extra != 'foo' and extra != 'bar' Now let's take an example where these forks don't share the same version for all packages. Consider a case where `idna==3.9` is in forks A and C, but where `idna==3.10` is in fork B. If we combine the markers in forks A and C through disjunction, we get the following: idna==3.9: extra != 'foo' or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' But these are clearly not disjoint. Both dependencies could be selected, for example, when neither `foo` nor `bar` are active. We can remedy this by keeping around the inclusion rules for each fork: A: extra != 'foo' and extra == 'bar' B: extra != 'bar' and extra == 'foo' C: extra != 'foo' and extra != 'bar' And so for `idna`, we have: idna==3.9: (extra != 'foo' and extra == 'bar') or (extra != 'foo' and extra != 'bar') idna==3.10: extra != 'bar' and extra == 'foo' Which simplifies to: idna==3.9: extra != 'foo' idna==3.10: extra != 'bar' and extra == 'foo' And these *are* properly disjoint. There is no way for them both to be active. This also correctly accounts for fork C where neither `foo` nor `bar` are active, and yet, `idna==3.9` is still enabled but `idna==3.10` is not. (In the [motivating example], this comes from `baz` being enabled.) That is, this captures the idea that for `idna==3.10` to be installed, there must actually be a specific extra that is enabled. That's what makes it disjoint from `idna==3.9`. We aren't quite done yet, because this does add *too many* conflict markers to dependency edges that don't need it. In the next commit, we'll add in our world knowledge to simplify these conflict markers. [motivating example]: #9289
When conflicting extra groups share a dependant, both extra groups seem to be installed.
Consider the following example adapted from the docs:
The key difference is the additional extra
metrics
that includes thetorchmetrics
package that depends ontorch
.When I lock and sync both cpu and metrics extras, I get multiple versions of torch installed:
The text was updated successfully, but these errors were encountered: