Skip to content

Commit a726c53

Browse files
committed
fix: Make path dependencies with the same name stays locked
1 parent 9e9c81c commit a726c53

File tree

2 files changed

+122
-18
lines changed

2 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: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2993,12 +2993,77 @@ 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+
}
3005+
3006+
#[cargo_test]
3007+
fn same_name_version_changed() {
3008+
// Illustrates having two path packages with the same name, but different versions.
3009+
// Verifies it works correctly when one of the versions is changed.
3010+
let p = project()
3011+
.file(
3012+
"Cargo.toml",
3013+
r#"
3014+
[package]
3015+
name = "foo"
3016+
version = "1.0.0"
3017+
edition = "2021"
3018+
3019+
[dependencies]
3020+
foo2 = { path = "foo2", package = "foo" }
3021+
"#,
3022+
)
3023+
.file("src/lib.rs", "")
3024+
.file(
3025+
"foo2/Cargo.toml",
3026+
r#"
3027+
[package]
3028+
name = "foo"
3029+
version = "2.0.0"
3030+
edition = "2021"
3031+
"#,
3032+
)
3033+
.file("foo2/src/lib.rs", "")
3034+
.build();
3035+
3036+
p.cargo("tree")
3037+
.with_stderr("[LOCKING] 2 packages to latest compatible versions")
3038+
.with_stdout(
3039+
"\
3040+
foo v1.0.0 ([ROOT]/foo)
3041+
└── foo v2.0.0 ([ROOT]/foo/foo2)
3042+
",
3043+
)
3044+
.run();
3045+
3046+
p.change_file(
3047+
"foo2/Cargo.toml",
3048+
r#"
3049+
[package]
3050+
name = "foo"
3051+
version = "2.0.1"
3052+
edition = "2021"
3053+
"#,
3054+
);
3055+
p.cargo("tree")
3056+
.with_stderr(
3057+
"\
3058+
[LOCKING] 1 package to latest compatible version
3059+
[ADDING] foo v2.0.1 ([ROOT]/foo/foo2)
3060+
",
3061+
)
3062+
.with_stdout(
3063+
"\
3064+
foo v1.0.0 ([ROOT]/foo)
3065+
└── foo v2.0.1 ([ROOT]/foo/foo2)
3066+
",
3067+
)
3068+
.run();
3069+
}

0 commit comments

Comments
 (0)