Skip to content

Commit b93f4c8

Browse files
committed
patch can conflict on not activated packages
1 parent 524231f commit b93f4c8

File tree

2 files changed

+124
-14
lines changed

2 files changed

+124
-14
lines changed

src/cargo/core/resolver/mod.rs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -823,15 +823,12 @@ fn generalize_conflicting(
823823
dep: &Dependency,
824824
conflicting_activations: &ConflictMap,
825825
) -> Option<ConflictMap> {
826-
if conflicting_activations.is_empty() {
827-
return None;
828-
}
829826
// We need to determine the `ContextAge` that this `conflicting_activations` will jump to, and why.
830-
let (backtrack_critical_age, backtrack_critical_id) = conflicting_activations
831-
.keys()
832-
.map(|&c| (cx.is_active(c).expect("not currently active!?"), c))
833-
.max()
834-
.unwrap();
827+
let (backtrack_critical_age, backtrack_critical_id) = shortcircuit_max(
828+
conflicting_activations
829+
.keys()
830+
.map(|&c| cx.is_active(c).map(|a| (a, c))),
831+
)?;
835832
let backtrack_critical_reason: ConflictReason =
836833
conflicting_activations[&backtrack_critical_id].clone();
837834

@@ -923,6 +920,19 @@ fn generalize_conflicting(
923920
None
924921
}
925922

923+
/// Returns Some of the largest item in the iterator.
924+
/// Returns None if any of the items are None or the iterator is empty.
925+
fn shortcircuit_max<I: Ord>(iter: impl Iterator<Item = Option<I>>) -> Option<I> {
926+
let mut out = None;
927+
for i in iter {
928+
if i.is_none() {
929+
return None;
930+
}
931+
out = std::cmp::max(out, i);
932+
}
933+
out
934+
}
935+
926936
/// Looks through the states in `backtrack_stack` for dependencies with
927937
/// remaining candidates. For each one, also checks if rolling back
928938
/// could change the outcome of the failed resolution that caused backtracking
@@ -949,12 +959,10 @@ fn find_candidate(
949959
// the cause of that backtrack, so we do not update it.
950960
let age = if !backtracked {
951961
// we don't have abnormal situations. So we can ask `cx` for how far back we need to go.
952-
let a = cx.is_conflicting(Some(parent.package_id()), conflicting_activations);
953-
// If the `conflicting_activations` does not apply to `cx`, then something went very wrong
954-
// in building it. But we will just fall back to laboriously trying all possibilities witch
955-
// will give us the correct answer so only `assert` if there is a developer to debug it.
956-
debug_assert!(a.is_some());
957-
a
962+
// If the `conflicting_activations` does not apply to `cx`,
963+
// we will just fall back to laboriously trying all possibilities witch
964+
// will give us the correct answer.
965+
cx.is_conflicting(Some(parent.package_id()), conflicting_activations)
958966
} else {
959967
None
960968
};

tests/testsuite/patch.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,3 +2541,105 @@ foo v0.1.0 [..]
25412541
))
25422542
.run();
25432543
}
2544+
2545+
// From https://github.com/rust-lang/cargo/issues/7463
2546+
#[cargo_test]
2547+
fn patch_eq_conflict_panic() {
2548+
Package::new("bar", "0.1.0").publish();
2549+
Package::new("bar", "0.1.1").publish();
2550+
let p = project()
2551+
.file(
2552+
"Cargo.toml",
2553+
r#"
2554+
[package]
2555+
name = "foo"
2556+
version = "0.1.0"
2557+
2558+
[dependencies]
2559+
bar = "=0.1.0"
2560+
2561+
[dev-dependencies]
2562+
bar = "=0.1.1"
2563+
2564+
[patch.crates-io]
2565+
bar = {path="bar"}
2566+
"#,
2567+
)
2568+
.file("src/lib.rs", "")
2569+
.file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
2570+
.file("bar/src/lib.rs", "")
2571+
.build();
2572+
2573+
p.cargo("generate-lockfile")
2574+
.with_status(101)
2575+
.with_stderr(
2576+
r#"[UPDATING] `dummy-registry` index
2577+
[ERROR] failed to select a version for `bar`.
2578+
... required by package `foo v0.1.0 ([..])`
2579+
versions that meet the requirements `=0.1.1` are: 0.1.1
2580+
2581+
all possible versions conflict with previously selected packages.
2582+
2583+
previously selected package `bar v0.1.0`
2584+
... which satisfies dependency `bar = "=0.1.0"` of package `foo v0.1.0 ([..])`
2585+
2586+
failed to select a version for `bar` which could resolve this conflict
2587+
"#,
2588+
)
2589+
.run();
2590+
}
2591+
2592+
// From https://github.com/rust-lang/cargo/issues/11336
2593+
#[cargo_test]
2594+
fn mismatched_version2() {
2595+
Package::new("qux", "0.1.0-beta.1").publish();
2596+
Package::new("qux", "0.1.0-beta.2").publish();
2597+
Package::new("bar", "0.1.0")
2598+
.dep("qux", "=0.1.0-beta.1")
2599+
.publish();
2600+
let p = project()
2601+
.file(
2602+
"Cargo.toml",
2603+
r#"
2604+
[package]
2605+
name = "foo"
2606+
version = "0.1.0"
2607+
2608+
[dependencies]
2609+
bar = "0.1.0"
2610+
qux = "0.1.0-beta.2"
2611+
2612+
[patch.crates-io]
2613+
qux = { path = "qux" }
2614+
"#,
2615+
)
2616+
.file("src/lib.rs", "")
2617+
.file(
2618+
"qux/Cargo.toml",
2619+
r#"
2620+
[package]
2621+
name = "qux"
2622+
version = "0.1.0-beta.1"
2623+
"#,
2624+
)
2625+
.file("qux/src/lib.rs", "")
2626+
.build();
2627+
2628+
p.cargo("generate-lockfile")
2629+
.with_status(101)
2630+
.with_stderr(
2631+
r#"[UPDATING] `dummy-registry` index
2632+
[ERROR] failed to select a version for `qux`.
2633+
... required by package `bar v0.1.0`
2634+
... which satisfies dependency `bar = "^0.1.0"` of package `foo v0.1.0 ([..])`
2635+
versions that meet the requirements `=0.1.0-beta.1` are: 0.1.0-beta.1
2636+
2637+
all possible versions conflict with previously selected packages.
2638+
2639+
previously selected package `qux v0.1.0-beta.2`
2640+
... which satisfies dependency `qux = "^0.1.0-beta.2"` of package `foo v0.1.0 ([..])`
2641+
2642+
failed to select a version for `qux` which could resolve this conflict"#,
2643+
)
2644+
.run();
2645+
}

0 commit comments

Comments
 (0)