Skip to content

Commit 690e6a4

Browse files
committed
cargo-metadata: error out if same dep with differnt names
Previous, `cargo metadata` allows a dependency with different renamed co-exist. However, its `resolve.nodes.deps` will miss that dependency, which is wrong. After this commit, `cargo metadata starts erroring out for that situation.
1 parent 272193a commit 690e6a4

File tree

4 files changed

+141
-121
lines changed

4 files changed

+141
-121
lines changed

src/cargo/core/compiler/artifact.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::core::compiler::{Context, CrateType, FileFlavor, Unit};
44
use crate::core::dependency::ArtifactKind;
55
use crate::core::{Dependency, Target, TargetKind};
66
use crate::CargoResult;
7-
use std::collections::HashMap;
7+
use std::collections::{HashMap, HashSet};
88
use std::ffi::OsString;
99

1010
/// Return all environment variables for the given unit-dependencies
@@ -62,21 +62,18 @@ fn unit_artifact_type_name_upper(unit: &Unit) -> &'static str {
6262
///
6363
/// Failure to match any target results in an error mentioning the parent manifests
6464
/// `parent_package` name.
65-
pub(crate) fn match_artifacts_kind_with_targets<'a, F>(
66-
artifact_dep: &Dependency,
67-
targets: &'a [Target],
65+
pub(crate) fn match_artifacts_kind_with_targets<'t, 'd>(
66+
artifact_dep: &'d Dependency,
67+
targets: &'t [Target],
6868
parent_package: &str,
69-
mut callback: F,
70-
) -> CargoResult<()>
71-
where
72-
F: FnMut(&ArtifactKind, &mut dyn Iterator<Item = &'a Target>),
73-
{
69+
) -> CargoResult<HashSet<(&'d ArtifactKind, &'t Target)>> {
70+
let mut out = HashSet::new();
7471
let artifact_requirements = artifact_dep.artifact().expect("artifact present");
7572
for artifact_kind in artifact_requirements.kinds() {
76-
let mut extend = |kind: &ArtifactKind, filter: &dyn Fn(&&Target) -> bool| {
73+
let mut extend = |kind, filter: &dyn Fn(&&Target) -> bool| {
7774
let mut iter = targets.iter().filter(filter).peekable();
7875
let found = iter.peek().is_some();
79-
callback(kind, &mut iter);
76+
out.extend(std::iter::repeat(kind).zip(iter));
8077
found
8178
};
8279
let found = match artifact_kind {
@@ -96,5 +93,5 @@ where
9693
);
9794
}
9895
}
99-
Ok(())
96+
Ok(out)
10097
}

src/cargo/core/compiler/unit_dependencies.rs

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -555,53 +555,48 @@ fn artifact_targets_to_unit_deps(
555555
artifact_pkg: &Package,
556556
dep: &Dependency,
557557
) -> CargoResult<Vec<UnitDep>> {
558-
let mut targets = HashSet::new();
559-
match_artifacts_kind_with_targets(
560-
dep,
561-
artifact_pkg.targets(),
562-
parent.pkg.name().as_str(),
563-
|_, iter| targets.extend(iter),
564-
)?;
565-
let ret = targets
566-
.into_iter()
567-
.flat_map(|target| {
568-
// We split target libraries into individual units, even though rustc is able
569-
// to produce multiple kinds in an single invocation for the sole reason that
570-
// each artifact kind has its own output directory, something we can't easily
571-
// teach rustc for now.
572-
match target.kind() {
573-
TargetKind::Lib(kinds) => Box::new(
574-
kinds
575-
.iter()
576-
.filter(|tk| matches!(tk, CrateType::Cdylib | CrateType::Staticlib))
577-
.map(|target_kind| {
578-
new_unit_dep(
579-
state,
580-
parent,
581-
artifact_pkg,
582-
target
583-
.clone()
584-
.set_kind(TargetKind::Lib(vec![target_kind.clone()])),
585-
parent_unit_for,
586-
compile_kind,
587-
CompileMode::Build,
588-
dep.artifact(),
589-
)
590-
}),
591-
) as Box<dyn Iterator<Item = _>>,
592-
_ => Box::new(std::iter::once(new_unit_dep(
593-
state,
594-
parent,
595-
artifact_pkg,
596-
target,
597-
parent_unit_for,
598-
compile_kind,
599-
CompileMode::Build,
600-
dep.artifact(),
601-
))),
602-
}
603-
})
604-
.collect::<Result<Vec<_>, _>>()?;
558+
let ret =
559+
match_artifacts_kind_with_targets(dep, artifact_pkg.targets(), parent.pkg.name().as_str())?
560+
.into_iter()
561+
.map(|(_artifact_kind, target)| target)
562+
.flat_map(|target| {
563+
// We split target libraries into individual units, even though rustc is able
564+
// to produce multiple kinds in an single invocation for the sole reason that
565+
// each artifact kind has its own output directory, something we can't easily
566+
// teach rustc for now.
567+
match target.kind() {
568+
TargetKind::Lib(kinds) => Box::new(
569+
kinds
570+
.iter()
571+
.filter(|tk| matches!(tk, CrateType::Cdylib | CrateType::Staticlib))
572+
.map(|target_kind| {
573+
new_unit_dep(
574+
state,
575+
parent,
576+
artifact_pkg,
577+
target
578+
.clone()
579+
.set_kind(TargetKind::Lib(vec![target_kind.clone()])),
580+
parent_unit_for,
581+
compile_kind,
582+
CompileMode::Build,
583+
dep.artifact(),
584+
)
585+
}),
586+
) as Box<dyn Iterator<Item = _>>,
587+
_ => Box::new(std::iter::once(new_unit_dep(
588+
state,
589+
parent,
590+
artifact_pkg,
591+
target,
592+
parent_unit_for,
593+
compile_kind,
594+
CompileMode::Build,
595+
dep.artifact(),
596+
))),
597+
}
598+
})
599+
.collect::<Result<Vec<_>, _>>()?;
605600
Ok(ret)
606601
}
607602

src/cargo/ops/cargo_output_metadata.rs

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,15 @@ struct Dep {
9191
struct DepKindInfo {
9292
kind: DepKind,
9393
target: Option<Platform>,
94+
/// What the manifest calls the crate.
95+
///
96+
/// A renamed dependency will show the rename instead of original name.
97+
extern_name: InternedString,
9498

9599
// vvvvv The fields below are introduced for `-Z bindeps`.
96100
/// Artifact's crate type, e.g. staticlib, cdylib, bin...
97101
#[serde(skip_serializing_if = "Option::is_none")]
98102
artifact: Option<&'static str>,
99-
/// What the manifest calls the crate.
100-
///
101-
/// A renamed dependency will show the rename instead of original name.
102-
#[serde(skip_serializing_if = "Option::is_none")]
103-
extern_name: Option<InternedString>,
104103
/// Equivalent to `{ target = "…" }` in an artifact dependency requirement.
105104
///
106105
/// * If the target points to a custom target JSON file, the path will be absolute.
@@ -205,9 +204,9 @@ fn build_resolve_graph_r(
205204
let normalize_id = |id| -> PackageId { *package_map.get_key_value(&id).unwrap().0 };
206205
let features = resolve.features(pkg_id).to_vec();
207206

208-
let deps: Vec<Dep> = resolve
209-
.deps(pkg_id)
210-
.filter(|(_dep_id, deps)| {
207+
let deps = {
208+
let mut dep_metadatas = Vec::new();
209+
let iter = resolve.deps(pkg_id).filter(|(_dep_id, deps)| {
211210
if requested_kinds == [CompileKind::Host] {
212211
true
213212
} else {
@@ -216,8 +215,8 @@ fn build_resolve_graph_r(
216215
.any(|dep| target_data.dep_platform_activated(dep, *kind))
217216
})
218217
}
219-
})
220-
.filter_map(|(dep_id, deps)| {
218+
});
219+
for (dep_id, deps) in iter {
221220
let mut dep_kinds = Vec::new();
222221

223222
let targets = package_map[&dep_id].targets();
@@ -227,30 +226,30 @@ fn build_resolve_graph_r(
227226
resolve
228227
.extern_crate_name_and_dep_name(pkg_id, dep_id, target)
229228
.map(|(ext_crate_name, _)| ext_crate_name)
230-
.ok()
231229
};
232230

233-
let lib_target_name = targets.iter().find(|t| t.is_lib()).map(extern_name);
231+
let lib_target = targets.iter().find(|t| t.is_lib());
234232

235233
for dep in deps.iter() {
236-
let mut include_lib = || {
237-
dep_kinds.push(DepKindInfo {
238-
kind: dep.kind(),
239-
target: dep.platform().cloned(),
240-
artifact: None,
241-
extern_name: lib_target_name,
242-
compile_target: None,
243-
bin_name: None,
244-
});
245-
};
246-
247-
// When we do have a library target, include them in deps if...
248-
match (dep.artifact(), lib_target_name) {
249-
// it is also an artifact dep with `{ …, lib = true }`
250-
(Some(a), Some(_)) if a.is_lib() => include_lib(),
251-
// it is not an artifact dep at all
252-
(None, Some(_)) => include_lib(),
253-
_ => {}
234+
if let Some(target) = lib_target {
235+
// When we do have a library target, include them in deps if...
236+
let included = match dep.artifact() {
237+
// it is not an artifact dep at all
238+
None => true,
239+
// it is also an artifact dep with `{ …, lib = true }`
240+
Some(a) if a.is_lib() => true,
241+
_ => false,
242+
};
243+
if included {
244+
dep_kinds.push(DepKindInfo {
245+
kind: dep.kind(),
246+
target: dep.platform().cloned(),
247+
extern_name: extern_name(target)?,
248+
artifact: None,
249+
compile_target: None,
250+
bin_name: None,
251+
});
252+
}
254253
}
255254

256255
// No need to proceed if there is no artifact dependency.
@@ -269,48 +268,47 @@ fn build_resolve_graph_r(
269268
None => None,
270269
};
271270

272-
if let Err(e) = match_artifacts_kind_with_targets(
273-
dep,
274-
targets,
275-
pkg_id.name().as_str(),
276-
|kind, targets| {
277-
dep_kinds.extend(targets.map(|target| DepKindInfo {
278-
kind: dep.kind(),
279-
target: dep.platform().cloned(),
280-
artifact: Some(kind.crate_type()),
281-
extern_name: extern_name(target),
282-
compile_target,
283-
bin_name: target.is_bin().then(|| target.name().to_string()),
284-
}))
285-
},
286-
) {
287-
return Some(Err(e));
271+
let target_set =
272+
match_artifacts_kind_with_targets(dep, targets, pkg_id.name().as_str())?;
273+
dep_kinds.reserve(target_set.len());
274+
for (kind, target) in target_set.into_iter() {
275+
dep_kinds.push(DepKindInfo {
276+
kind: dep.kind(),
277+
target: dep.platform().cloned(),
278+
extern_name: extern_name(target)?,
279+
artifact: Some(kind.crate_type()),
280+
compile_target,
281+
bin_name: target.is_bin().then(|| target.name().to_string()),
282+
})
288283
}
289284
}
290285

291286
dep_kinds.sort();
292287

293288
let pkg = normalize_id(dep_id);
294289

295-
let dep = match (lib_target_name, dep_kinds.len()) {
296-
(Some(name), _) => Some(Dep {
297-
name,
290+
let dep = match (lib_target, dep_kinds.len()) {
291+
(Some(target), _) => Dep {
292+
name: extern_name(target)?,
298293
pkg,
299294
dep_kinds,
300-
}),
295+
},
301296
// No lib target exists but contains artifact deps.
302-
(None, 1..) => Some(Dep {
297+
(None, 1..) => Dep {
303298
name: InternedString::new(""),
304299
pkg,
305300
dep_kinds,
306-
}),
301+
},
307302
// No lib or artifact dep exists.
308303
// Ususally this mean parent depending on non-lib bin crate.
309-
(None, _) => None,
304+
(None, _) => continue,
310305
};
311-
dep.map(Ok)
312-
})
313-
.collect::<CargoResult<_>>()?;
306+
307+
dep_metadatas.push(dep)
308+
}
309+
dep_metadatas
310+
};
311+
314312
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
315313
let to_visit = dumb_deps.clone();
316314
let node = MetadataResolveNode {

tests/testsuite/metadata.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,24 +1632,24 @@ fn workspace_metadata_with_dependencies_and_resolve() {
16321632
"target": null
16331633
},
16341634
{
1635-
"artifact": "bin",
1636-
"bin_name": "baz-name",
1635+
"artifact": "cdylib",
16371636
"compile_target": "wasm32-unknown-unknown",
1638-
"extern_name": "baz_name",
1637+
"extern_name": "artifact",
16391638
"kind": null,
16401639
"target": null
16411640
},
16421641
{
1643-
"artifact": "cdylib",
1642+
"artifact": "staticlib",
16441643
"compile_target": "wasm32-unknown-unknown",
16451644
"extern_name": "artifact",
16461645
"kind": null,
16471646
"target": null
16481647
},
16491648
{
1650-
"artifact": "staticlib",
1649+
"artifact": "bin",
1650+
"bin_name": "baz-name",
16511651
"compile_target": "wasm32-unknown-unknown",
1652-
"extern_name": "artifact",
1652+
"extern_name": "baz_name",
16531653
"kind": null,
16541654
"target": null
16551655
},
@@ -1887,6 +1887,36 @@ fn cargo_metadata_with_invalid_artifact_deps() {
18871887
.run();
18881888
}
18891889

1890+
#[cargo_test]
1891+
fn cargo_metadata_with_invalid_duplicate_renamed_deps() {
1892+
let p = project()
1893+
.file(
1894+
"Cargo.toml",
1895+
r#"
1896+
[package]
1897+
name = "foo"
1898+
version = "0.5.0"
1899+
1900+
[dependencies]
1901+
bar = { path = "bar" }
1902+
baz = { path = "bar", package = "bar" }
1903+
"#,
1904+
)
1905+
.file("src/lib.rs", "")
1906+
.file("bar/Cargo.toml", &basic_lib_manifest("bar"))
1907+
.file("bar/src/lib.rs", "")
1908+
.build();
1909+
1910+
p.cargo("metadata")
1911+
.with_status(101)
1912+
.with_stderr(
1913+
"\
1914+
[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
1915+
[ERROR] the crate `foo v0.5.0 ([..])` depends on crate `bar v0.5.0 ([..])` multiple times with different names",
1916+
)
1917+
.run();
1918+
}
1919+
18901920
const MANIFEST_OUTPUT: &str = r#"
18911921
{
18921922
"packages": [{

0 commit comments

Comments
 (0)