Skip to content

Commit 198ba31

Browse files
committed
Auto merge of #13572 - linyihai:multi-dep-same-name, r=ehuss
Fix: Make path dependencies with the same name stays locked ### What does this PR try to resolve? Fixes: #13405 This is a workround based on #13405 (comment) ### How should we test and review this PR? first commit will pass, second commit fixed it and update test. ### Additional information
2 parents 986dac3 + ab92717 commit 198ba31

File tree

3 files changed

+206
-14
lines changed

3 files changed

+206
-14
lines changed

src/cargo/core/resolver/encode.rs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ impl EncodableResolve {
154154
/// primary uses is to be used with `resolve_with_previous` to guide the
155155
/// resolver to create a complete Resolve.
156156
pub fn into_resolve(self, original: &str, ws: &Workspace<'_>) -> CargoResult<Resolve> {
157-
let path_deps = build_path_deps(ws)?;
157+
let path_deps: HashMap<String, HashMap<semver::Version, SourceId>> = build_path_deps(ws)?;
158158
let mut checksums = HashMap::new();
159159

160160
let mut version = match self.version {
@@ -202,7 +202,11 @@ impl EncodableResolve {
202202
if !all_pkgs.insert(enc_id.clone()) {
203203
anyhow::bail!("package `{}` is specified twice in the lockfile", pkg.name);
204204
}
205-
let id = match pkg.source.as_deref().or_else(|| path_deps.get(&pkg.name)) {
205+
let id = match pkg
206+
.source
207+
.as_deref()
208+
.or_else(|| get_source_id(&path_deps, pkg))
209+
{
206210
// We failed to find a local package in the workspace.
207211
// It must have been removed and should be ignored.
208212
None => {
@@ -364,7 +368,11 @@ impl EncodableResolve {
364368

365369
let mut unused_patches = Vec::new();
366370
for pkg in self.patch.unused {
367-
let id = match pkg.source.as_deref().or_else(|| path_deps.get(&pkg.name)) {
371+
let id = match pkg
372+
.source
373+
.as_deref()
374+
.or_else(|| get_source_id(&path_deps, &pkg))
375+
{
368376
Some(&src) => PackageId::try_new(&pkg.name, &pkg.version, src)?,
369377
None => continue,
370378
};
@@ -395,7 +403,7 @@ impl EncodableResolve {
395403
version = ResolveVersion::V2;
396404
}
397405

398-
Ok(Resolve::new(
406+
return Ok(Resolve::new(
399407
g,
400408
replacements,
401409
HashMap::new(),
@@ -404,11 +412,35 @@ impl EncodableResolve {
404412
unused_patches,
405413
version,
406414
HashMap::new(),
407-
))
415+
));
416+
417+
fn get_source_id<'a>(
418+
path_deps: &'a HashMap<String, HashMap<semver::Version, SourceId>>,
419+
pkg: &'a EncodableDependency,
420+
) -> Option<&'a SourceId> {
421+
path_deps.iter().find_map(|(name, version_source)| {
422+
if name != &pkg.name || version_source.len() == 0 {
423+
return None;
424+
}
425+
if version_source.len() == 1 {
426+
return Some(version_source.values().next().unwrap());
427+
}
428+
// If there are multiple candidates for the same name, it needs to be determined by combining versions (See #13405).
429+
if let Ok(pkg_version) = pkg.version.parse::<semver::Version>() {
430+
if let Some(source_id) = version_source.get(&pkg_version) {
431+
return Some(source_id);
432+
}
433+
}
434+
435+
None
436+
})
437+
}
408438
}
409439
}
410440

411-
fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>> {
441+
fn build_path_deps(
442+
ws: &Workspace<'_>,
443+
) -> CargoResult<HashMap<String, HashMap<semver::Version, SourceId>>> {
412444
// If a crate is **not** a path source, then we're probably in a situation
413445
// such as `cargo install` with a lock file from a remote dependency. In
414446
// that case we don't need to fixup any path dependencies (as they're not
@@ -418,13 +450,15 @@ fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>>
418450
.filter(|p| p.package_id().source_id().is_path())
419451
.collect::<Vec<_>>();
420452

421-
let mut ret = HashMap::new();
453+
let mut ret: HashMap<String, HashMap<semver::Version, SourceId>> = HashMap::new();
422454
let mut visited = HashSet::new();
423455
for member in members.iter() {
424-
ret.insert(
425-
member.package_id().name().to_string(),
426-
member.package_id().source_id(),
427-
);
456+
ret.entry(member.package_id().name().to_string())
457+
.or_insert_with(HashMap::new)
458+
.insert(
459+
member.package_id().version().clone(),
460+
member.package_id().source_id(),
461+
);
428462
visited.insert(member.package_id().source_id());
429463
}
430464
for member in members.iter() {
@@ -444,7 +478,7 @@ fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>>
444478
fn build_pkg(
445479
pkg: &Package,
446480
ws: &Workspace<'_>,
447-
ret: &mut HashMap<String, SourceId>,
481+
ret: &mut HashMap<String, HashMap<semver::Version, SourceId>>,
448482
visited: &mut HashSet<SourceId>,
449483
) {
450484
for dep in pkg.dependencies() {
@@ -455,7 +489,7 @@ fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>>
455489
fn build_dep(
456490
dep: &Dependency,
457491
ws: &Workspace<'_>,
458-
ret: &mut HashMap<String, SourceId>,
492+
ret: &mut HashMap<String, HashMap<semver::Version, SourceId>>,
459493
visited: &mut HashSet<SourceId>,
460494
) {
461495
let id = dep.source_id();
@@ -467,7 +501,12 @@ fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>>
467501
Err(_) => return,
468502
};
469503
let Ok(pkg) = ws.load(&path) else { return };
470-
ret.insert(pkg.name().to_string(), pkg.package_id().source_id());
504+
ret.entry(pkg.package_id().name().to_string())
505+
.or_insert_with(HashMap::new)
506+
.insert(
507+
pkg.package_id().version().clone(),
508+
pkg.package_id().source_id(),
509+
);
471510
visited.insert(pkg.package_id().source_id());
472511
build_pkg(&pkg, ws, ret, visited);
473512
}

tests/testsuite/patch.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2914,3 +2914,91 @@ Caused by:
29142914
)
29152915
.run();
29162916
}
2917+
2918+
#[cargo_test]
2919+
fn patched_reexport_stays_locked() {
2920+
// Patch example where you emulate a semver-incompatible patch via a re-export.
2921+
// Testing an issue where the lockfile does not stay locked after a new version is published.
2922+
Package::new("bar", "1.0.0").publish();
2923+
Package::new("bar", "2.0.0").publish();
2924+
Package::new("bar", "3.0.0").publish();
2925+
2926+
let p = project()
2927+
.file(
2928+
"Cargo.toml",
2929+
r#"
2930+
[workspace]
2931+
2932+
2933+
[package]
2934+
name = "foo"
2935+
2936+
[dependencies]
2937+
bar1 = {package="bar", version="1.0.0"}
2938+
bar2 = {package="bar", version="2.0.0"}
2939+
2940+
[patch.crates-io]
2941+
bar1 = { package = "bar", path = "bar-1-as-3" }
2942+
bar2 = { package = "bar", path = "bar-2-as-3" }
2943+
"#,
2944+
)
2945+
.file("src/lib.rs", "")
2946+
.file(
2947+
"bar-1-as-3/Cargo.toml",
2948+
r#"
2949+
[package]
2950+
name = "bar"
2951+
version = "1.0.999"
2952+
2953+
[dependencies]
2954+
bar = "3.0.0"
2955+
"#,
2956+
)
2957+
.file("bar-1-as-3/src/lib.rs", "")
2958+
.file(
2959+
"bar-2-as-3/Cargo.toml",
2960+
r#"
2961+
[package]
2962+
name = "bar"
2963+
version = "2.0.999"
2964+
2965+
[dependencies]
2966+
bar = "3.0.0"
2967+
"#,
2968+
)
2969+
.file("bar-2-as-3/src/lib.rs", "")
2970+
.build();
2971+
2972+
p.cargo("tree")
2973+
.with_stdout(
2974+
"\
2975+
foo v0.0.0 ([ROOT]/foo)
2976+
├── bar v1.0.999 ([ROOT]/foo/bar-1-as-3)
2977+
│ └── bar v3.0.0
2978+
└── bar v2.0.999 ([ROOT]/foo/bar-2-as-3)
2979+
└── bar v3.0.0
2980+
",
2981+
)
2982+
.run();
2983+
2984+
std::fs::copy(
2985+
p.root().join("Cargo.lock"),
2986+
p.root().join("Cargo.lock.orig"),
2987+
)
2988+
.unwrap();
2989+
2990+
Package::new("bar", "3.0.1").publish();
2991+
p.cargo("tree")
2992+
.with_stdout(
2993+
"\
2994+
foo v0.0.0 ([ROOT]/foo)
2995+
├── bar v1.0.999 ([ROOT]/foo/bar-1-as-3)
2996+
│ └── bar v3.0.0
2997+
└── bar v2.0.999 ([ROOT]/foo/bar-2-as-3)
2998+
└── bar v3.0.0
2999+
",
3000+
)
3001+
.run();
3002+
3003+
assert_eq!(p.read_file("Cargo.lock"), p.read_file("Cargo.lock.orig"));
3004+
}

tests/testsuite/path.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,3 +1204,68 @@ fn catch_tricky_cycle() {
12041204
.with_status(101)
12051205
.run();
12061206
}
1207+
1208+
#[cargo_test]
1209+
fn same_name_version_changed() {
1210+
// Illustrates having two path packages with the same name, but different versions.
1211+
// Verifies it works correctly when one of the versions is changed.
1212+
let p = project()
1213+
.file(
1214+
"Cargo.toml",
1215+
r#"
1216+
[package]
1217+
name = "foo"
1218+
version = "1.0.0"
1219+
edition = "2021"
1220+
1221+
[dependencies]
1222+
foo2 = { path = "foo2", package = "foo" }
1223+
"#,
1224+
)
1225+
.file("src/lib.rs", "")
1226+
.file(
1227+
"foo2/Cargo.toml",
1228+
r#"
1229+
[package]
1230+
name = "foo"
1231+
version = "2.0.0"
1232+
edition = "2021"
1233+
"#,
1234+
)
1235+
.file("foo2/src/lib.rs", "")
1236+
.build();
1237+
1238+
p.cargo("tree")
1239+
.with_stderr("[LOCKING] 2 packages to latest compatible versions")
1240+
.with_stdout(
1241+
"\
1242+
foo v1.0.0 ([ROOT]/foo)
1243+
└── foo v2.0.0 ([ROOT]/foo/foo2)
1244+
",
1245+
)
1246+
.run();
1247+
1248+
p.change_file(
1249+
"foo2/Cargo.toml",
1250+
r#"
1251+
[package]
1252+
name = "foo"
1253+
version = "2.0.1"
1254+
edition = "2021"
1255+
"#,
1256+
);
1257+
p.cargo("tree")
1258+
.with_stderr(
1259+
"\
1260+
[LOCKING] 1 package to latest compatible version
1261+
[ADDING] foo v2.0.1 ([ROOT]/foo/foo2)
1262+
",
1263+
)
1264+
.with_stdout(
1265+
"\
1266+
foo v1.0.0 ([ROOT]/foo)
1267+
└── foo v2.0.1 ([ROOT]/foo/foo2)
1268+
",
1269+
)
1270+
.run();
1271+
}

0 commit comments

Comments
 (0)