Skip to content

Commit 0dd7c50

Browse files
committed
Auto merge of #7118 - alexcrichton:patch-bug, r=Eh2406
Handle activation conflicts for `[patch]` sources This commit updates the resolver to ensure that it recognizes conflicts when `[patch]` is used to augment an older version of what's already in a source, for example. Previously the deduplication based on semver-compatible versions didn't actually work when `[patch]` was used. This meant that when you used `[patch]` it might not transitively affect the entire crate graph, instead just giving you a version of a dependency and everyone else. This violates the intention of `[patch]`! The fix here is to catch this use case happening, when a `Dependency` source specification mismatches an activated package we need to list a second activation in the resolver to prevent major versions from being selected from both the original source as well as the source of the id. Closes #7117
2 parents ba606d2 + 83bb30c commit 0dd7c50

File tree

3 files changed

+100
-3
lines changed

3 files changed

+100
-3
lines changed

src/cargo/core/resolver/context.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,17 @@ impl Context {
9999

100100
/// Activate this summary by inserting it into our list of known activations.
101101
///
102+
/// The `parent` passed in here is the parent summary/dependency edge which
103+
/// cased `summary` to get activated. This may not be present for the root
104+
/// crate, for example.
105+
///
102106
/// Returns `true` if this summary with the given method is already activated.
103-
pub fn flag_activated(&mut self, summary: &Summary, method: &Method) -> CargoResult<bool> {
107+
pub fn flag_activated(
108+
&mut self,
109+
summary: &Summary,
110+
method: &Method,
111+
parent: Option<(&Summary, &Dependency)>,
112+
) -> CargoResult<bool> {
104113
let id = summary.package_id();
105114
let age: ContextAge = self.age();
106115
match self.activations.entry(id.as_activations_key()) {
@@ -121,6 +130,30 @@ impl Context {
121130
);
122131
}
123132
v.insert((summary.clone(), age));
133+
134+
// If we've got a parent dependency which activated us, *and*
135+
// the dependency has a different source id listed than the
136+
// `summary` itself, then things get interesting. This basically
137+
// means that a `[patch]` was used to augment `dep.source_id()`
138+
// with `summary`.
139+
//
140+
// In this scenario we want to consider the activation key, as
141+
// viewed from the perspective of `dep.source_id()`, as being
142+
// fulfilled. This means that we need to add a second entry in
143+
// the activations map for the source that was patched, in
144+
// addition to the source of the actual `summary` itself.
145+
//
146+
// Without this it would be possible to have both 1.0.0 and
147+
// 1.1.0 "from crates.io" in a dependency graph if one of those
148+
// versions came from a `[patch]` source.
149+
if let Some((_, dep)) = parent {
150+
if dep.source_id() != id.source_id() {
151+
let key = (id.name(), dep.source_id(), id.version().into());
152+
let prev = self.activations.insert(key, (summary.clone(), age));
153+
assert!(prev.is_none());
154+
}
155+
}
156+
124157
return Ok(false);
125158
}
126159
}

src/cargo/core/resolver/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,11 +651,16 @@ fn activate(
651651
}
652652
}
653653

654-
let activated = cx.flag_activated(&candidate, &method)?;
654+
let activated = cx.flag_activated(&candidate, &method, parent)?;
655655

656656
let candidate = match registry.replacement_summary(candidate_pid) {
657657
Some(replace) => {
658-
if cx.flag_activated(replace, &method)? && activated {
658+
// Note the `None` for parent here since `[replace]` is a bit wonky
659+
// and doesn't activate the same things that `[patch]` typically
660+
// does. TBH it basically cause panics in the test suite if
661+
// `parent` is passed through here and `[replace]` is otherwise
662+
// on life support so it's not critical to fix bugs anyway per se.
663+
if cx.flag_activated(replace, &method, None)? && activated {
659664
return Ok(None);
660665
}
661666
trace!(

tests/testsuite/patch.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,3 +1020,62 @@ fn replace_prerelease() {
10201020

10211021
p.cargo("build").run();
10221022
}
1023+
1024+
#[cargo_test]
1025+
fn patch_older() {
1026+
Package::new("baz", "1.0.2").publish();
1027+
1028+
let p = project()
1029+
.file(
1030+
"Cargo.toml",
1031+
r#"
1032+
[package]
1033+
name = "foo"
1034+
version = "0.1.0"
1035+
1036+
[dependencies]
1037+
bar = { path = 'bar' }
1038+
baz = "=1.0.1"
1039+
1040+
[patch.crates-io]
1041+
baz = { path = "./baz" }
1042+
"#,
1043+
)
1044+
.file("src/lib.rs", "")
1045+
.file(
1046+
"bar/Cargo.toml",
1047+
r#"
1048+
[project]
1049+
name = "bar"
1050+
version = "0.5.0"
1051+
authors = []
1052+
1053+
[dependencies]
1054+
baz = "1.0.0"
1055+
"#,
1056+
)
1057+
.file("bar/src/lib.rs", "")
1058+
.file(
1059+
"baz/Cargo.toml",
1060+
r#"
1061+
[project]
1062+
name = "baz"
1063+
version = "1.0.1"
1064+
authors = []
1065+
"#,
1066+
)
1067+
.file("baz/src/lib.rs", "")
1068+
.build();
1069+
1070+
p.cargo("build")
1071+
.with_stderr(
1072+
"\
1073+
[UPDATING] [..]
1074+
[COMPILING] baz v1.0.1 [..]
1075+
[COMPILING] bar v0.5.0 [..]
1076+
[COMPILING] foo v0.1.0 [..]
1077+
[FINISHED] [..]
1078+
",
1079+
)
1080+
.run();
1081+
}

0 commit comments

Comments
 (0)