Skip to content
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

Conflicting groups should handle conflicting inclusions automatically #11232

Closed
zanieb opened this issue Feb 5, 2025 · 5 comments · Fixed by #12005
Closed

Conflicting groups should handle conflicting inclusions automatically #11232

zanieb opened this issue Feb 5, 2025 · 5 comments · Fixed by #12005
Assignees
Labels
enhancement New feature or improvement to existing functionality

Comments

@zanieb
Copy link
Member

zanieb commented Feb 5, 2025

Summary

When using the include-group directive in dependency groups, uv does not propagate dependency group conflicts automatically. Instead, the conflicts must be manually enumerated. uv should be able to do this? Either by retaining the provenance of the included dependencies, or by just filling in more conflicts for the user before forwarding to the resolver.

Example

The following is required

[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[dependency-groups]
dev = [
  { include-group = "test" },
]
test = ["tox>4"]
magic = ["tox<4"]

[tool.uv]
conflicts = [
  [
    { group = "test" },
    { group = "magic" },
  ],
  [
    { group = "dev" },
    { group = "magic" },
  ], 
]

But I would like to just do

[tool.uv]
conflicts = [
  [
    { group = "test" },
    { group = "magic" },
  ],
]

which fails with

❯ uv lock
  × No solution found when resolving dependencies:
  ╰─▶ Because example:magic depends on tox<4 and example:dev depends on tox>4, we can conclude that example:dev and
      example:magic are incompatible.
      And because your project requires example:dev and example:magic, we can conclude that your project's requirements
      are unsatisfiable.

While this example is contrived, you can imagine more complicated examples with multiple nested group inclusions and conflicts.

@zanieb zanieb added the enhancement New feature or improvement to existing functionality label Feb 5, 2025
@zanieb
Copy link
Member Author

zanieb commented Feb 5, 2025

cc @BurntSushi just curious for your take on feasibility here.

Does something similar apply to extras since those can reference other extras too? I'd need to check.

@gaborbernat
Copy link
Contributor

Here's the real world example where this came up gaborbernat/datamodel-code-generator@b654b43, dev needs to be included otherwise things fail, even though only pkg-meta conflicts....

@BurntSushi
Copy link
Member

or by just filling in more conflicts for the user before forwarding to the resolver.

I think this implementation path would be pretty feasible. I think this is ultimately what you would have to do anyway. As in, I don't see how the resolver could do anything better per se given knowledge that one group references another.

As for extras, I'm actually not sure if you can do this? Is there syntax for referring to a sibling extra in the same package?

Overall I think the main concern I'd have here is that this could make it very easy for the number of conflicts to grow large. And that you could have a number of conflicts that isn't proportional to the actual written down declared conflicts. And this matters because increasing the number of conflicts means increasing the number of resolver forks, which can slow down resolution. But this is a very vague hand-wavy concern, and I don't think it's enough of a concern to out-weigh the benefits here.

@zanieb
Copy link
Member Author

zanieb commented Feb 5, 2025

As for extras, I'm actually not sure if you can do this? Is there syntax for referring to a sibling extra in the same package?

Of course! People frequently use this to make an all group.

[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[project.optional-dependencies]
dev = ["example[test]"]
test = ["tox>4"]
magic = ["tox<4"]

[tool.uv]
conflicts = [
  [
    { extra = "test" },
    { extra = "magic" },
  ],
]

which fails with

❯ uv lock
  × No solution found when resolving dependencies:
  ╰─▶ Because example[magic] depends on tox<4 and example[dev] depends on tox>4, we can conclude that example[dev] and
      example[magic] are incompatible.
      And because your project requires example[dev] and example[magic], we can conclude that your project's requirements
      are unsatisfiable.

And that you could have a number of conflicts that isn't proportional to the actual written down declared conflicts. And this matters because increasing the number of conflicts means increasing the number of resolver fork

If the declared conflict is correct, then the transitive inferred conflict is strictly necessary for them to perform a successful resolution at all.

@BurntSushi
Copy link
Member

Derp, I tried that, but I had a typo.

So yeah, it probably makes sense to do this for extras as well. I think it only requires a shallow traversal of optional dependencies here? As in, we don't need the full dependency tree to setup the conflicts. I think that would be harder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or improvement to existing functionality
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants