mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-13 17:25:41 +00:00
Avoid installing duplicate dependencies across conflicting groups (#11653)
## Summary We need to compute the set of activated groups prior to evaluating the conflict markers on the groups' dependencies. Closes https://github.com/astral-sh/uv/issues/11648.
This commit is contained in:
parent
3365eb4a1c
commit
b588a8ea2f
2 changed files with 137 additions and 5 deletions
|
|
@ -53,6 +53,43 @@ pub trait Installable<'lock> {
|
||||||
|
|
||||||
let root = petgraph.add_node(Node::Root);
|
let root = petgraph.add_node(Node::Root);
|
||||||
|
|
||||||
|
// Determine the set of activated extras and groups, from the root.
|
||||||
|
//
|
||||||
|
// TODO(charlie): This isn't quite right. Below, when we add the dependency groups to the
|
||||||
|
// graph, we rely on the activated extras and dependency groups, to evaluate the conflict
|
||||||
|
// marker. But at that point, we don't know the full set of activated extras; this is only
|
||||||
|
// computed below. We somehow need to add the dependency groups _after_ we've computed all
|
||||||
|
// enabled extras, but the groups themselves could depend on the set of enabled extras.
|
||||||
|
if !self.lock().conflicts().is_empty() {
|
||||||
|
for root_name in self.roots() {
|
||||||
|
let dist = self
|
||||||
|
.lock()
|
||||||
|
.find_by_name(root_name)
|
||||||
|
.map_err(|_| LockErrorKind::MultipleRootPackages {
|
||||||
|
name: root_name.clone(),
|
||||||
|
})?
|
||||||
|
.ok_or_else(|| LockErrorKind::MissingRootPackage {
|
||||||
|
name: root_name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Track the activated extras.
|
||||||
|
if dev.prod() {
|
||||||
|
for extra in extras.extra_names(dist.optional_dependencies.keys()) {
|
||||||
|
activated_extras.push((&dist.id.name, extra));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the activated groups.
|
||||||
|
for group in dist
|
||||||
|
.dependency_groups
|
||||||
|
.keys()
|
||||||
|
.filter(|group| dev.contains(group))
|
||||||
|
{
|
||||||
|
activated_groups.push((&dist.id.name, group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the workspace packages to the queue.
|
// Add the workspace packages to the queue.
|
||||||
for root_name in self.roots() {
|
for root_name in self.roots() {
|
||||||
let dist = self
|
let dist = self
|
||||||
|
|
@ -77,12 +114,10 @@ pub trait Installable<'lock> {
|
||||||
petgraph.add_edge(root, index, Edge::Prod(MarkerTree::TRUE));
|
petgraph.add_edge(root, index, Edge::Prod(MarkerTree::TRUE));
|
||||||
|
|
||||||
if dev.prod() {
|
if dev.prod() {
|
||||||
// Push its dependencies on the queue and track
|
// Push its dependencies onto the queue.
|
||||||
// activated extras.
|
|
||||||
queue.push_back((dist, None));
|
queue.push_back((dist, None));
|
||||||
for extra in extras.extra_names(dist.optional_dependencies.keys()) {
|
for extra in extras.extra_names(dist.optional_dependencies.keys()) {
|
||||||
queue.push_back((dist, Some(extra)));
|
queue.push_back((dist, Some(extra)));
|
||||||
activated_extras.push((&dist.id.name, extra));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,10 +134,13 @@ pub trait Installable<'lock> {
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
if !dep.complexified_marker.evaluate_no_extras(marker_env) {
|
if !dep.complexified_marker.evaluate(
|
||||||
|
marker_env,
|
||||||
|
activated_extras.iter().copied(),
|
||||||
|
activated_groups.iter().copied(),
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
activated_groups.push((&dist.id.name, group));
|
|
||||||
|
|
||||||
let dep_dist = self.lock().find_by_id(&dep.package_id);
|
let dep_dist = self.lock().find_by_id(&dep.package_id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7714,3 +7714,97 @@ fn unsupported_git_scheme() -> Result<()> {
|
||||||
"###);
|
"###);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See: <https://github.com/astral-sh/uv/issues/11648>
|
||||||
|
#[test]
|
||||||
|
fn multiple_group_conflicts() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
foo = [
|
||||||
|
"iniconfig>=2",
|
||||||
|
]
|
||||||
|
bar = [
|
||||||
|
"iniconfig<2",
|
||||||
|
]
|
||||||
|
baz = [
|
||||||
|
"iniconfig",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
conflicts = [
|
||||||
|
[
|
||||||
|
{ group = "foo" },
|
||||||
|
{ group = "bar" },
|
||||||
|
],
|
||||||
|
]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Audited in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("baz"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar").arg("--group").arg("baz"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Prepared 1 package in [TIME]
|
||||||
|
Uninstalled 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
- iniconfig==2.0.0
|
||||||
|
+ iniconfig==1.1.1
|
||||||
|
"###);
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
error: Groups `foo` and `bar` are incompatible with the declared conflicts: {`project:foo`, `project:bar`}
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue