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.
|
||||
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.
|
||||
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));
|
||||
// Sort the roots.
|
||||
roots.sort_by_key(|index| &graph[*index]);
|
||||
|
||||
roots
|
||||
} else {
|
||||
let mut edges = vec![];
|
||||
|
||||
// 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 {
|
||||
|
|
|
|||
|
|
@ -1703,3 +1703,99 @@ fn show_sizes() -> Result<()> {
|
|||
|
||||
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