Skip to content

Commit f0b6f81

Browse files
committed
Auto merge of #11550 - weihanglo:issue-10607, r=ehuss
`cargo metadata` supports artifact dependencies
2 parents 8eb9a0c + d02e36c commit f0b6f81

File tree

5 files changed

+328
-87
lines changed

5 files changed

+328
-87
lines changed

src/cargo/core/compiler/artifact.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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;
6-
use std::collections::HashMap;
7+
use std::collections::{HashMap, HashSet};
78
use std::ffi::OsString;
89

910
/// Return all environment variables for the given unit-dependencies
@@ -55,3 +56,42 @@ 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<'t, 'd>(
66+
artifact_dep: &'d Dependency,
67+
targets: &'t [Target],
68+
parent_package: &str,
69+
) -> CargoResult<HashSet<(&'d ArtifactKind, &'t Target)>> {
70+
let mut out = HashSet::new();
71+
let artifact_requirements = artifact_dep.artifact().expect("artifact present");
72+
for artifact_kind in artifact_requirements.kinds() {
73+
let mut extend = |kind, filter: &dyn Fn(&&Target) -> bool| {
74+
let mut iter = targets.iter().filter(filter).peekable();
75+
let found = iter.peek().is_some();
76+
out.extend(std::iter::repeat(kind).zip(iter));
77+
found
78+
};
79+
let found = match artifact_kind {
80+
ArtifactKind::Cdylib => extend(artifact_kind, &|t| t.is_cdylib()),
81+
ArtifactKind::Staticlib => extend(artifact_kind, &|t| t.is_staticlib()),
82+
ArtifactKind::AllBinaries => extend(artifact_kind, &|t| t.is_bin()),
83+
ArtifactKind::SelectedBinary(bin_name) => extend(artifact_kind, &|t| {
84+
t.is_bin() && t.name() == bin_name.as_str()
85+
}),
86+
};
87+
if !found {
88+
anyhow::bail!(
89+
"dependency `{}` in package `{}` requires a `{}` artifact to be present.",
90+
artifact_dep.name_in_toml(),
91+
parent_package,
92+
artifact_kind
93+
);
94+
}
95+
}
96+
Ok(out)
97+
}

src/cargo/core/compiler/unit_dependencies.rs

Lines changed: 3 additions & 40 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;
@@ -557,6 +558,7 @@ fn artifact_targets_to_unit_deps(
557558
let ret =
558559
match_artifacts_kind_with_targets(dep, artifact_pkg.targets(), parent.pkg.name().as_str())?
559560
.into_iter()
561+
.map(|(_artifact_kind, target)| target)
560562
.flat_map(|target| {
561563
// We split target libraries into individual units, even though rustc is able
562564
// to produce multiple kinds in an single invocation for the sole reason that
@@ -598,45 +600,6 @@ fn artifact_targets_to_unit_deps(
598600
Ok(ret)
599601
}
600602

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())
626-
}
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)
638-
}
639-
640603
/// Returns the dependencies necessary to document a package.
641604
fn compute_deps_doc(
642605
unit: &Unit,

src/cargo/core/dependency.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,8 @@ impl ser::Serialize for ArtifactKind {
566566
S: ser::Serializer,
567567
{
568568
let out: Cow<'_, str> = match *self {
569-
ArtifactKind::AllBinaries => "bin".into(),
570-
ArtifactKind::Staticlib => "staticlib".into(),
571-
ArtifactKind::Cdylib => "cdylib".into(),
572569
ArtifactKind::SelectedBinary(name) => format!("bin:{}", name.as_str()).into(),
570+
_ => self.crate_type().into(),
573571
};
574572
out.serialize(s)
575573
}
@@ -578,15 +576,24 @@ impl ser::Serialize for ArtifactKind {
578576
impl fmt::Display for ArtifactKind {
579577
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580578
f.write_str(match self {
581-
ArtifactKind::Cdylib => "cdylib",
582-
ArtifactKind::Staticlib => "staticlib",
583-
ArtifactKind::AllBinaries => "bin",
584-
ArtifactKind::SelectedBinary(bin_name) => return write!(f, "bin:{}", bin_name),
579+
ArtifactKind::SelectedBinary(bin_name) => return write!(f, "bin:{bin_name}"),
580+
_ => self.crate_type(),
585581
})
586582
}
587583
}
588584

589585
impl ArtifactKind {
586+
/// Returns a string of crate type of the artifact being built.
587+
///
588+
/// Note that the name of `SelectedBinary` would be dropped and displayed as `bin`.
589+
pub fn crate_type(&self) -> &'static str {
590+
match self {
591+
ArtifactKind::AllBinaries | ArtifactKind::SelectedBinary(_) => "bin",
592+
ArtifactKind::Cdylib => "cdylib",
593+
ArtifactKind::Staticlib => "staticlib",
594+
}
595+
}
596+
590597
fn parse(kind: &str) -> CargoResult<Self> {
591598
Ok(match kind {
592599
"bin" => ArtifactKind::AllBinaries,

src/cargo/ops/cargo_output_metadata.rs

Lines changed: 134 additions & 35 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};
23
use crate::core::dependency::DepKind;
34
use crate::core::package::SerializedPackage;
45
use crate::core::resolver::{features::CliFeatures, HasDevUnits, Resolve};
5-
use crate::core::{Dependency, Package, PackageId, 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;
@@ -81,6 +82,8 @@ struct MetadataResolveNode {
8182

8283
#[derive(Serialize)]
8384
struct Dep {
85+
// TODO(bindeps): after -Zbindeps gets stabilized,
86+
// mark this field as deprecated in the help manual of cargo-metadata
8487
name: InternedString,
8588
pkg: PackageId,
8689
dep_kinds: Vec<DepKindInfo>,
@@ -90,15 +93,27 @@ struct Dep {
9093
struct DepKindInfo {
9194
kind: DepKind,
9295
target: Option<Platform>,
93-
}
9496

95-
impl From<&Dependency> for DepKindInfo {
96-
fn from(dep: &Dependency) -> DepKindInfo {
97-
DepKindInfo {
98-
kind: dep.kind(),
99-
target: dep.platform().cloned(),
100-
}
101-
}
97+
// vvvvv The fields below are introduced for `-Z bindeps`.
98+
/// What the manifest calls the crate.
99+
///
100+
/// A renamed dependency will show the rename instead of original name.
101+
// TODO(bindeps): Remove `Option` after -Zbindeps get stabilized.
102+
#[serde(skip_serializing_if = "Option::is_none")]
103+
extern_name: Option<InternedString>,
104+
/// Artifact's crate type, e.g. staticlib, cdylib, bin...
105+
#[serde(skip_serializing_if = "Option::is_none")]
106+
artifact: Option<&'static str>,
107+
/// Equivalent to `{ target = "…" }` in an artifact dependency requirement.
108+
///
109+
/// * If the target points to a custom target JSON file, the path will be absolute.
110+
/// * If the target is a build assumed target `{ target = "target" }`, it will show as `<target>`.
111+
#[serde(skip_serializing_if = "Option::is_none")]
112+
compile_target: Option<InternedString>,
113+
/// Executable name for an artifact binary dependency.
114+
#[serde(skip_serializing_if = "Option::is_none")]
115+
bin_name: Option<String>,
116+
// ^^^^^ The fields above are introduced for `-Z bindeps`.
102117
}
103118

104119
/// Builds the resolve graph as it will be displayed to the user.
@@ -149,7 +164,7 @@ fn build_resolve_graph(
149164
&package_map,
150165
&target_data,
151166
&requested_kinds,
152-
);
167+
)?;
153168
}
154169
// Get a Vec of Packages.
155170
let actual_packages = package_map
@@ -172,9 +187,9 @@ fn build_resolve_graph_r(
172187
package_map: &BTreeMap<PackageId, Package>,
173188
target_data: &RustcTargetData<'_>,
174189
requested_kinds: &[CompileKind],
175-
) {
190+
) -> CargoResult<()> {
176191
if node_map.contains_key(&pkg_id) {
177-
return;
192+
return Ok(());
178193
}
179194
// This normalizes the IDs so that they are consistent between the
180195
// `packages` array and the `resolve` map. This is a bit of a hack to
@@ -193,9 +208,9 @@ fn build_resolve_graph_r(
193208
let normalize_id = |id| -> PackageId { *package_map.get_key_value(&id).unwrap().0 };
194209
let features = resolve.features(pkg_id).to_vec();
195210

196-
let deps: Vec<Dep> = resolve
197-
.deps(pkg_id)
198-
.filter(|(_dep_id, deps)| {
211+
let deps = {
212+
let mut dep_metadatas = Vec::new();
213+
let iter = resolve.deps(pkg_id).filter(|(_dep_id, deps)| {
199214
if requested_kinds == [CompileKind::Host] {
200215
true
201216
} else {
@@ -204,27 +219,109 @@ fn build_resolve_graph_r(
204219
.any(|dep| target_data.dep_platform_activated(dep, *kind))
205220
})
206221
}
207-
})
208-
.filter_map(|(dep_id, deps)| {
209-
let mut dep_kinds: Vec<_> = deps.iter().map(DepKindInfo::from).collect();
222+
});
223+
for (dep_id, deps) in iter {
224+
let mut dep_kinds = Vec::new();
225+
226+
let targets = package_map[&dep_id].targets();
227+
228+
// Try to get the extern name for lib, or crate name for bins.
229+
let extern_name = |target| {
230+
resolve
231+
.extern_crate_name_and_dep_name(pkg_id, dep_id, target)
232+
.map(|(ext_crate_name, _)| ext_crate_name)
233+
};
234+
235+
let lib_target = targets.iter().find(|t| t.is_lib());
236+
237+
for dep in deps.iter() {
238+
if let Some(target) = lib_target {
239+
// When we do have a library target, include them in deps if...
240+
let included = match dep.artifact() {
241+
// it is not an artifact dep at all
242+
None => true,
243+
// it is also an artifact dep with `{ …, lib = true }`
244+
Some(a) if a.is_lib() => true,
245+
_ => false,
246+
};
247+
// TODO(bindeps): Cargo shouldn't have `extern_name` field
248+
// if the user is not using -Zbindeps.
249+
// Remove this condition ` after -Zbindeps gets stabilized.
250+
let extern_name = if dep.artifact().is_some() {
251+
Some(extern_name(target)?)
252+
} else {
253+
None
254+
};
255+
if included {
256+
dep_kinds.push(DepKindInfo {
257+
kind: dep.kind(),
258+
target: dep.platform().cloned(),
259+
extern_name,
260+
artifact: None,
261+
compile_target: None,
262+
bin_name: None,
263+
});
264+
}
265+
}
266+
267+
// No need to proceed if there is no artifact dependency.
268+
let Some(artifact_requirements) = dep.artifact() else {
269+
continue;
270+
};
271+
272+
let compile_target = match artifact_requirements.target() {
273+
Some(t) => t
274+
.to_compile_target()
275+
.map(|t| t.rustc_target())
276+
// Given that Cargo doesn't know which target it should resolve to,
277+
// when an artifact dep is specified with { target = "target" },
278+
// keep it with a special "<target>" string,
279+
.or_else(|| Some(InternedString::new("<target>"))),
280+
None => None,
281+
};
282+
283+
let target_set =
284+
match_artifacts_kind_with_targets(dep, targets, pkg_id.name().as_str())?;
285+
dep_kinds.reserve(target_set.len());
286+
for (kind, target) in target_set.into_iter() {
287+
dep_kinds.push(DepKindInfo {
288+
kind: dep.kind(),
289+
target: dep.platform().cloned(),
290+
extern_name: extern_name(target).ok(),
291+
artifact: Some(kind.crate_type()),
292+
compile_target,
293+
bin_name: target.is_bin().then(|| target.name().to_string()),
294+
})
295+
}
296+
}
297+
210298
dep_kinds.sort();
211-
package_map
212-
.get(&dep_id)
213-
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
214-
.and_then(|lib_target| {
215-
resolve
216-
.extern_crate_name_and_dep_name(pkg_id, dep_id, lib_target)
217-
.map(|(ext_crate_name, _)| ext_crate_name)
218-
.ok()
219-
})
220-
.map(|name| Dep {
221-
name,
222-
pkg: normalize_id(dep_id),
299+
300+
let pkg = normalize_id(dep_id);
301+
302+
let dep = match (lib_target, dep_kinds.len()) {
303+
(Some(target), _) => Dep {
304+
name: extern_name(target)?,
305+
pkg,
223306
dep_kinds,
224-
})
225-
})
226-
.collect();
227-
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| normalize_id(dep.pkg)).collect();
307+
},
308+
// No lib target exists but contains artifact deps.
309+
(None, 1..) => Dep {
310+
name: InternedString::new(""),
311+
pkg,
312+
dep_kinds,
313+
},
314+
// No lib or artifact dep exists.
315+
// Ususally this mean parent depending on non-lib bin crate.
316+
(None, _) => continue,
317+
};
318+
319+
dep_metadatas.push(dep)
320+
}
321+
dep_metadatas
322+
};
323+
324+
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
228325
let to_visit = dumb_deps.clone();
229326
let node = MetadataResolveNode {
230327
id: normalize_id(pkg_id),
@@ -241,6 +338,8 @@ fn build_resolve_graph_r(
241338
package_map,
242339
target_data,
243340
requested_kinds,
244-
);
341+
)?;
245342
}
343+
344+
Ok(())
246345
}

0 commit comments

Comments
 (0)