Skip to content

Commit ab92717

Browse files
linyihaiehuss
authored andcommitted
fix: Make path dependencies with the same name stays locked
1 parent 5c1b631 commit ab92717

File tree

3 files changed

+122
-18
lines changed

3 files changed

+122
-18
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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2993,12 +2993,12 @@ foo v0.0.0 ([ROOT]/foo)
29932993
"\
29942994
foo v0.0.0 ([ROOT]/foo)
29952995
├── bar v1.0.999 ([ROOT]/foo/bar-1-as-3)
2996-
│ └── bar v3.0.1
2996+
│ └── bar v3.0.0
29972997
└── bar v2.0.999 ([ROOT]/foo/bar-2-as-3)
2998-
└── bar v3.0.1
2998+
└── bar v3.0.0
29992999
",
30003000
)
30013001
.run();
30023002

3003-
assert_ne!(p.read_file("Cargo.lock"), p.read_file("Cargo.lock.orig"));
3004-
}
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)