Skip to content

Commit a92fd48

Browse files
committed
Improve error message for cyclic dependencies
First reported in rust-lang/rust#65014 it looks like our error message on cyclic dependencies may be confusing at times. It looks like this is an issue because there are multiple paths through a graph for a dependency, so using the generic `path_to_top` function isn't producing the most useful path for this purpose. We're already walking the graph though, so this commit adds an extra parameter which collects the list of packages we've visited so far to produce a hopefully always-accurate error message showing the chain of dependencies end-to-end for what depends on what.
1 parent a429e8c commit a92fd48

File tree

4 files changed

+37
-10
lines changed

4 files changed

+37
-10
lines changed

crates/resolver-tests/tests/resolve.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,3 +1451,21 @@ fn conflict_store_more_then_one_match() {
14511451
let reg = registry(input);
14521452
let _ = resolve_and_validated(vec![dep("nA")], &reg, None);
14531453
}
1454+
1455+
#[test]
1456+
fn cyclic_good_error_message() {
1457+
let input = vec![
1458+
pkg!(("A", "0.0.0") => [dep("C")]),
1459+
pkg!(("B", "0.0.0") => [dep("C")]),
1460+
pkg!(("C", "0.0.0") => [dep("A")]),
1461+
];
1462+
let reg = registry(input);
1463+
let error = resolve(vec![dep("A"), dep("B")], &reg).unwrap_err();
1464+
println!("{}", error);
1465+
assert_eq!("\
1466+
cyclic package dependency: package `A v0.0.0 (registry `https://example.com/`)` depends on itself. Cycle:
1467+
package `A v0.0.0 (registry `https://example.com/`)`
1468+
... which is depended on by `C v0.0.0 (registry `https://example.com/`)`
1469+
... which is depended on by `A v0.0.0 (registry `https://example.com/`)`\
1470+
", error.to_string());
1471+
}

src/cargo/core/resolver/mod.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -993,9 +993,11 @@ fn check_cycles(resolve: &Resolve) -> CargoResult<()> {
993993
let mut all_packages: Vec<_> = resolve.iter().collect();
994994
all_packages.sort_unstable();
995995
let mut checked = HashSet::new();
996+
let mut path = Vec::new();
997+
let mut visited = HashSet::new();
996998
for pkg in all_packages {
997999
if !checked.contains(&pkg) {
998-
visit(resolve, pkg, &mut HashSet::new(), &mut checked)?
1000+
visit(resolve, pkg, &mut visited, &mut path, &mut checked)?
9991001
}
10001002
}
10011003
return Ok(());
@@ -1004,14 +1006,16 @@ fn check_cycles(resolve: &Resolve) -> CargoResult<()> {
10041006
resolve: &Resolve,
10051007
id: PackageId,
10061008
visited: &mut HashSet<PackageId>,
1009+
path: &mut Vec<PackageId>,
10071010
checked: &mut HashSet<PackageId>,
10081011
) -> CargoResult<()> {
1012+
path.push(id);
10091013
// See if we visited ourselves
10101014
if !visited.insert(id) {
10111015
failure::bail!(
10121016
"cyclic package dependency: package `{}` depends on itself. Cycle:\n{}",
10131017
id,
1014-
errors::describe_path(&resolve.path_to_top(&id))
1018+
errors::describe_path(&path.iter().rev().collect::<Vec<_>>()),
10151019
);
10161020
}
10171021

@@ -1023,23 +1027,25 @@ fn check_cycles(resolve: &Resolve) -> CargoResult<()> {
10231027
// visitation list as we can't induce a cycle through transitive
10241028
// dependencies.
10251029
if checked.insert(id) {
1030+
let mut empty_set = HashSet::new();
1031+
let mut empty_vec = Vec::new();
10261032
for (dep, listings) in resolve.deps_not_replaced(id) {
10271033
let is_transitive = listings.iter().any(|d| d.is_transitive());
1028-
let mut empty = HashSet::new();
1029-
let visited = if is_transitive {
1030-
&mut *visited
1034+
let (visited, path) = if is_transitive {
1035+
(&mut *visited, &mut *path)
10311036
} else {
1032-
&mut empty
1037+
(&mut empty_set, &mut empty_vec)
10331038
};
1034-
visit(resolve, dep, visited, checked)?;
1039+
visit(resolve, dep, visited, path, checked)?;
10351040

10361041
if let Some(id) = resolve.replacement(dep) {
1037-
visit(resolve, id, visited, checked)?;
1042+
visit(resolve, id, visited, path, checked)?;
10381043
}
10391044
}
10401045
}
10411046

10421047
// Ok, we're done, no longer visiting our node any more
1048+
path.pop();
10431049
visited.remove(&id);
10441050
Ok(())
10451051
}

tests/testsuite/build.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,7 +1487,8 @@ fn self_dependency() {
14871487
.with_stderr(
14881488
"\
14891489
[ERROR] cyclic package dependency: package `test v0.0.0 ([CWD])` depends on itself. Cycle:
1490-
package `test v0.0.0 ([CWD])`",
1490+
package `test v0.0.0 ([CWD])`
1491+
... which is depended on by `test v0.0.0 ([..])`",
14911492
)
14921493
.run();
14931494
}
@@ -2613,7 +2614,8 @@ fn cyclic_deps_rejected() {
26132614
.with_stderr(
26142615
"[ERROR] cyclic package dependency: package `a v0.0.1 ([CWD]/a)` depends on itself. Cycle:
26152616
package `a v0.0.1 ([CWD]/a)`
2616-
... which is depended on by `foo v0.0.1 ([CWD])`",
2617+
... which is depended on by `foo v0.0.1 ([CWD])`
2618+
... which is depended on by `a v0.0.1 ([..])`",
26172619
).run();
26182620
}
26192621

tests/testsuite/patch.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,7 @@ fn cycle() {
11871187
error: cyclic package dependency: [..]
11881188
package `[..]`
11891189
... which is depended on by `[..]`
1190+
... which is depended on by `[..]`
11901191
",
11911192
)
11921193
.run();

0 commit comments

Comments
 (0)