mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-20 03:49:54 +00:00
Fix root of uv tree when --package is used with circular dependencies (#15908)
Closes #15907 Best viewed with https://github.com/astral-sh/uv/pull/15908/files?diff=unified&w=1 When `--package` is used, just use those as the roots rather than calculating them. I'm not sure if there will be undesirable side-effects, but it's the naive solution.
This commit is contained in:
parent
175be60727
commit
e2eea6d7db
2 changed files with 144 additions and 30 deletions
|
|
@ -362,40 +362,58 @@ impl<'env> TreeDisplay<'env> {
|
||||||
|
|
||||||
// Compute the list of roots.
|
// Compute the list of roots.
|
||||||
let roots = {
|
let roots = {
|
||||||
let mut edges = vec![];
|
// If specific packages were requested, use them as roots.
|
||||||
|
if !packages.is_empty() {
|
||||||
|
let mut roots = graph
|
||||||
|
.node_indices()
|
||||||
|
.filter(|index| {
|
||||||
|
let Node::Package(package_id) = graph[*index] else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
packages.contains(&package_id.name)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Remove any cycles.
|
// Sort the roots.
|
||||||
let feedback_set: Vec<EdgeIndex> = petgraph::algo::greedy_feedback_arc_set(&graph)
|
roots.sort_by_key(|index| &graph[*index]);
|
||||||
.map(|e| e.id())
|
|
||||||
.collect();
|
roots
|
||||||
for edge_id in feedback_set {
|
} else {
|
||||||
if let Some((source, target)) = graph.edge_endpoints(edge_id) {
|
let mut edges = vec![];
|
||||||
if let Some(weight) = graph.remove_edge(edge_id) {
|
|
||||||
edges.push((source, target, weight));
|
// Remove any cycles.
|
||||||
|
let feedback_set: Vec<EdgeIndex> = petgraph::algo::greedy_feedback_arc_set(&graph)
|
||||||
|
.map(|e| e.id())
|
||||||
|
.collect();
|
||||||
|
for edge_id in feedback_set {
|
||||||
|
if let Some((source, target)) = graph.edge_endpoints(edge_id) {
|
||||||
|
if let Some(weight) = graph.remove_edge(edge_id) {
|
||||||
|
edges.push((source, target, weight));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the root nodes: nodes with no incoming edges, or only an edge from the proxy.
|
||||||
|
let mut roots = graph
|
||||||
|
.node_indices()
|
||||||
|
.filter(|index| {
|
||||||
|
graph
|
||||||
|
.edges_directed(*index, Direction::Incoming)
|
||||||
|
.next()
|
||||||
|
.is_none()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Sort the roots.
|
||||||
|
roots.sort_by_key(|index| &graph[*index]);
|
||||||
|
|
||||||
|
// Re-add the removed edges.
|
||||||
|
for (source, target, weight) in edges {
|
||||||
|
graph.add_edge(source, target, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
roots
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the root nodes: nodes with no incoming edges, or only an edge from the proxy.
|
|
||||||
let mut roots = graph
|
|
||||||
.node_indices()
|
|
||||||
.filter(|index| {
|
|
||||||
graph
|
|
||||||
.edges_directed(*index, Direction::Incoming)
|
|
||||||
.next()
|
|
||||||
.is_none()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Sort the roots.
|
|
||||||
roots.sort_by_key(|index| &graph[*index]);
|
|
||||||
|
|
||||||
// Re-add the removed edges.
|
|
||||||
for (source, target, weight) in edges {
|
|
||||||
graph.add_edge(source, target, weight);
|
|
||||||
}
|
|
||||||
|
|
||||||
roots
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -1703,3 +1703,99 @@ fn show_sizes() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn workspace_circular_dependencies() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Create workspace root
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = ["packages/*"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Create package-a that depends on package-b
|
||||||
|
let package_a_dir = context.temp_dir.child("packages").child("package-a");
|
||||||
|
package_a_dir.create_dir_all()?;
|
||||||
|
let package_a_pyproject = package_a_dir.child("pyproject.toml");
|
||||||
|
package_a_pyproject.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "package-a"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["package-b"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
package-b = { workspace = true }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Create package-b that depends on package-a (circular dependency)
|
||||||
|
let package_b_dir = context.temp_dir.child("packages").child("package-b");
|
||||||
|
package_b_dir.create_dir_all()?;
|
||||||
|
let package_b_pyproject = package_b_dir.child("pyproject.toml");
|
||||||
|
package_b_pyproject.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "package-b"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["package-a"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
package-a = { workspace = true }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Test that package-a is at the root when requested
|
||||||
|
uv_snapshot!(context.filters(), context.tree().arg("--package").arg("package-a"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
package-a v0.1.0
|
||||||
|
└── package-b v0.1.0
|
||||||
|
└── package-a v0.1.0 (*)
|
||||||
|
(*) Package tree already displayed
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that package-b is at the root when requested
|
||||||
|
uv_snapshot!(context.filters(), context.tree().arg("--package").arg("package-b"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
package-b v0.1.0
|
||||||
|
└── package-a v0.1.0
|
||||||
|
└── package-b v0.1.0 (*)
|
||||||
|
(*) Package tree already displayed
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that both packages are shown as roots when both are requested
|
||||||
|
uv_snapshot!(context.filters(), context.tree().arg("--package").arg("package-a").arg("--package").arg("package-b"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
package-a v0.1.0
|
||||||
|
└── package-b v0.1.0
|
||||||
|
└── package-a v0.1.0 (*)
|
||||||
|
package-b v0.1.0 (*)
|
||||||
|
(*) Package tree already displayed
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue