Skip to content

Commit 272193a

Browse files
committed
cargo-metadata: error out when encountering invalid artifact kind syntax
This refactor reuse the logic of `core::compiler::unit_dependencies::match_artifacts_kind_with_targets` to emit error if there is any syntax error in `ArtifactKind`. It also put `match_artifacts_kind_with_targets` to a better place `core::compiler::artifact`.
1 parent e5ec492 commit 272193a

File tree

4 files changed

+153
-113
lines changed

4 files changed

+153
-113
lines changed

src/cargo/core/compiler/artifact.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/// Generate artifact information from unit dependencies for configuring the compiler environment.
22
use crate::core::compiler::unit_graph::UnitDep;
33
use crate::core::compiler::{Context, CrateType, FileFlavor, Unit};
4-
use crate::core::TargetKind;
4+
use crate::core::dependency::ArtifactKind;
5+
use crate::core::{Dependency, Target, TargetKind};
56
use crate::CargoResult;
67
use std::collections::HashMap;
78
use std::ffi::OsString;
@@ -55,3 +56,45 @@ fn unit_artifact_type_name_upper(unit: &Unit) -> &'static str {
5556
invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid),
5657
}
5758
}
59+
60+
/// Given a dependency with an artifact `artifact_dep` and a set of available `targets`
61+
/// of its package, find a target for each kind of artifacts that are to be built.
62+
///
63+
/// Failure to match any target results in an error mentioning the parent manifests
64+
/// `parent_package` name.
65+
pub(crate) fn match_artifacts_kind_with_targets<'a, F>(
66+
artifact_dep: &Dependency,
67+
targets: &'a [Target],
68+
parent_package: &str,
69+
mut callback: F,
70+
) -> CargoResult<()>
71+
where
72+
F: FnMut(&ArtifactKind, &mut dyn Iterator<Item = &'a Target>),
73+
{
74+
let artifact_requirements = artifact_dep.artifact().expect("artifact present");
75+
for artifact_kind in artifact_requirements.kinds() {
76+
let mut extend = |kind: &ArtifactKind, filter: &dyn Fn(&&Target) -> bool| {
77+
let mut iter = targets.iter().filter(filter).peekable();
78+
let found = iter.peek().is_some();
79+
callback(kind, &mut iter);
80+
found
81+
};
82+
let found = match artifact_kind {
83+
ArtifactKind::Cdylib => extend(artifact_kind, &|t| t.is_cdylib()),
84+
ArtifactKind::Staticlib => extend(artifact_kind, &|t| t.is_staticlib()),
85+
ArtifactKind::AllBinaries => extend(artifact_kind, &|t| t.is_bin()),
86+
ArtifactKind::SelectedBinary(bin_name) => extend(artifact_kind, &|t| {
87+
t.is_bin() && t.name() == bin_name.as_str()
88+
}),
89+
};
90+
if !found {
91+
anyhow::bail!(
92+
"dependency `{}` in package `{}` requires a `{}` artifact to be present.",
93+
artifact_dep.name_in_toml(),
94+
parent_package,
95+
artifact_kind
96+
);
97+
}
98+
}
99+
Ok(())
100+
}

src/cargo/core/compiler/unit_dependencies.rs

Lines changed: 49 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ use std::collections::{HashMap, HashSet};
1919

2020
use log::trace;
2121

22+
use crate::core::compiler::artifact::match_artifacts_kind_with_targets;
2223
use crate::core::compiler::unit_graph::{UnitDep, UnitGraph};
2324
use crate::core::compiler::{
2425
CompileKind, CompileMode, CrateType, RustcTargetData, Unit, UnitInterner,
2526
};
26-
use crate::core::dependency::{Artifact, ArtifactKind, ArtifactTarget, DepKind};
27+
use crate::core::dependency::{Artifact, ArtifactTarget, DepKind};
2728
use crate::core::profiles::{Profile, Profiles, UnitFor};
2829
use crate::core::resolver::features::{FeaturesFor, ResolvedFeatures};
2930
use crate::core::resolver::Resolve;
@@ -554,87 +555,54 @@ fn artifact_targets_to_unit_deps(
554555
artifact_pkg: &Package,
555556
dep: &Dependency,
556557
) -> CargoResult<Vec<UnitDep>> {
557-
let ret =
558-
match_artifacts_kind_with_targets(dep, artifact_pkg.targets(), parent.pkg.name().as_str())?
559-
.into_iter()
560-
.flat_map(|target| {
561-
// We split target libraries into individual units, even though rustc is able
562-
// to produce multiple kinds in an single invocation for the sole reason that
563-
// each artifact kind has its own output directory, something we can't easily
564-
// teach rustc for now.
565-
match target.kind() {
566-
TargetKind::Lib(kinds) => Box::new(
567-
kinds
568-
.iter()
569-
.filter(|tk| matches!(tk, CrateType::Cdylib | CrateType::Staticlib))
570-
.map(|target_kind| {
571-
new_unit_dep(
572-
state,
573-
parent,
574-
artifact_pkg,
575-
target
576-
.clone()
577-
.set_kind(TargetKind::Lib(vec![target_kind.clone()])),
578-
parent_unit_for,
579-
compile_kind,
580-
CompileMode::Build,
581-
dep.artifact(),
582-
)
583-
}),
584-
) as Box<dyn Iterator<Item = _>>,
585-
_ => Box::new(std::iter::once(new_unit_dep(
586-
state,
587-
parent,
588-
artifact_pkg,
589-
target,
590-
parent_unit_for,
591-
compile_kind,
592-
CompileMode::Build,
593-
dep.artifact(),
594-
))),
595-
}
596-
})
597-
.collect::<Result<Vec<_>, _>>()?;
598-
Ok(ret)
599-
}
600-
601-
/// Given a dependency with an artifact `artifact_dep` and a set of available `targets`
602-
/// of its package, find a target for each kind of artifacts that are to be built.
603-
///
604-
/// Failure to match any target results in an error mentioning the parent manifests
605-
/// `parent_package` name.
606-
fn match_artifacts_kind_with_targets<'a>(
607-
artifact_dep: &Dependency,
608-
targets: &'a [Target],
609-
parent_package: &str,
610-
) -> CargoResult<HashSet<&'a Target>> {
611-
let mut out = HashSet::new();
612-
let artifact_requirements = artifact_dep.artifact().expect("artifact present");
613-
for artifact_kind in artifact_requirements.kinds() {
614-
let mut extend = |filter: &dyn Fn(&&Target) -> bool| {
615-
let mut iter = targets.iter().filter(filter).peekable();
616-
let found = iter.peek().is_some();
617-
out.extend(iter);
618-
found
619-
};
620-
let found = match artifact_kind {
621-
ArtifactKind::Cdylib => extend(&|t| t.is_cdylib()),
622-
ArtifactKind::Staticlib => extend(&|t| t.is_staticlib()),
623-
ArtifactKind::AllBinaries => extend(&|t| t.is_bin()),
624-
ArtifactKind::SelectedBinary(bin_name) => {
625-
extend(&|t| t.is_bin() && t.name() == bin_name.as_str())
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+
))),
626602
}
627-
};
628-
if !found {
629-
anyhow::bail!(
630-
"dependency `{}` in package `{}` requires a `{}` artifact to be present.",
631-
artifact_dep.name_in_toml(),
632-
parent_package,
633-
artifact_kind
634-
);
635-
}
636-
}
637-
Ok(out)
603+
})
604+
.collect::<Result<Vec<_>, _>>()?;
605+
Ok(ret)
638606
}
639607

640608
/// Returns the dependencies necessary to document a package.

src/cargo/ops/cargo_output_metadata.rs

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use crate::core::compiler::artifact::match_artifacts_kind_with_targets;
12
use crate::core::compiler::{CompileKind, RustcTargetData};
2-
use crate::core::dependency::{ArtifactKind, DepKind};
3+
use crate::core::dependency::DepKind;
34
use crate::core::package::SerializedPackage;
45
use crate::core::resolver::{features::CliFeatures, HasDevUnits, Resolve};
5-
use crate::core::{Package, PackageId, Target, Workspace};
6+
use crate::core::{Package, PackageId, Workspace};
67
use crate::ops::{self, Packages};
78
use crate::util::interning::InternedString;
89
use crate::util::CargoResult;
@@ -160,7 +161,7 @@ fn build_resolve_graph(
160161
&package_map,
161162
&target_data,
162163
&requested_kinds,
163-
);
164+
)?;
164165
}
165166
// Get a Vec of Packages.
166167
let actual_packages = package_map
@@ -183,9 +184,9 @@ fn build_resolve_graph_r(
183184
package_map: &BTreeMap<PackageId, Package>,
184185
target_data: &RustcTargetData<'_>,
185186
requested_kinds: &[CompileKind],
186-
) {
187+
) -> CargoResult<()> {
187188
if node_map.contains_key(&pkg_id) {
188-
return;
189+
return Ok(());
189190
}
190191
// This normalizes the IDs so that they are consistent between the
191192
// `packages` array and the `resolve` map. This is a bit of a hack to
@@ -268,35 +269,30 @@ fn build_resolve_graph_r(
268269
None => None,
269270
};
270271

271-
let mut extend = |kind: &ArtifactKind, filter: &dyn Fn(&&Target) -> bool| {
272-
let iter = targets.iter().filter(filter).map(|target| DepKindInfo {
273-
kind: dep.kind(),
274-
target: dep.platform().cloned(),
275-
artifact: Some(kind.crate_type()),
276-
extern_name: extern_name(target),
277-
compile_target,
278-
bin_name: target.is_bin().then(|| target.name().to_string()),
279-
});
280-
dep_kinds.extend(iter);
281-
};
282-
283-
for kind in artifact_requirements.kinds() {
284-
match kind {
285-
ArtifactKind::Cdylib => extend(kind, &|t| t.is_cdylib()),
286-
ArtifactKind::Staticlib => extend(kind, &|t| t.is_staticlib()),
287-
ArtifactKind::AllBinaries => extend(kind, &|t| t.is_bin()),
288-
ArtifactKind::SelectedBinary(bin_name) => {
289-
extend(kind, &|t| t.is_bin() && t.name() == bin_name.as_str())
290-
}
291-
};
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));
292288
}
293289
}
294290

295291
dep_kinds.sort();
296292

297293
let pkg = normalize_id(dep_id);
298294

299-
match (lib_target_name, dep_kinds.len()) {
295+
let dep = match (lib_target_name, dep_kinds.len()) {
300296
(Some(name), _) => Some(Dep {
301297
name,
302298
pkg,
@@ -311,10 +307,11 @@ fn build_resolve_graph_r(
311307
// No lib or artifact dep exists.
312308
// Ususally this mean parent depending on non-lib bin crate.
313309
(None, _) => None,
314-
}
310+
};
311+
dep.map(Ok)
315312
})
316-
.collect();
317-
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| normalize_id(dep.pkg)).collect();
313+
.collect::<CargoResult<_>>()?;
314+
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
318315
let to_visit = dumb_deps.clone();
319316
let node = MetadataResolveNode {
320317
id: normalize_id(pkg_id),
@@ -331,6 +328,8 @@ fn build_resolve_graph_r(
331328
package_map,
332329
target_data,
333330
requested_kinds,
334-
);
331+
)?;
335332
}
333+
334+
Ok(())
336335
}

tests/testsuite/metadata.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,36 @@ Caused by:
18571857
.run();
18581858
}
18591859

1860+
#[cargo_test]
1861+
fn cargo_metadata_with_invalid_artifact_deps() {
1862+
let p = project()
1863+
.file(
1864+
"Cargo.toml",
1865+
r#"
1866+
[package]
1867+
name = "foo"
1868+
version = "0.5.0"
1869+
1870+
[dependencies]
1871+
artifact = { path = "artifact", artifact = "bin:notfound" }
1872+
"#,
1873+
)
1874+
.file("src/lib.rs", "")
1875+
.file("artifact/Cargo.toml", &basic_bin_manifest("artifact"))
1876+
.file("artifact/src/main.rs", "fn main() {}")
1877+
.build();
1878+
1879+
p.cargo("metadata -Z bindeps")
1880+
.masquerade_as_nightly_cargo(&["bindeps"])
1881+
.with_status(101)
1882+
.with_stderr(
1883+
"\
1884+
[WARNING] please specify `--format-version` flag explicitly to avoid compatibility problems
1885+
[ERROR] dependency `artifact` in package `foo` requires a `bin:notfound` artifact to be present.",
1886+
)
1887+
.run();
1888+
}
1889+
18601890
const MANIFEST_OUTPUT: &str = r#"
18611891
{
18621892
"packages": [{

0 commit comments

Comments
 (0)