Skip to content

Commit

Permalink
Auto merge of #11550 - weihanglo:issue-10607, r=ehuss
Browse files Browse the repository at this point in the history
`cargo metadata` supports artifact dependencies
  • Loading branch information
bors committed Jan 16, 2023
2 parents 8eb9a0c + d02e36c commit f0b6f81
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 87 deletions.
44 changes: 42 additions & 2 deletions src/cargo/core/compiler/artifact.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/// Generate artifact information from unit dependencies for configuring the compiler environment.
use crate::core::compiler::unit_graph::UnitDep;
use crate::core::compiler::{Context, CrateType, FileFlavor, Unit};
use crate::core::TargetKind;
use crate::core::dependency::ArtifactKind;
use crate::core::{Dependency, Target, TargetKind};
use crate::CargoResult;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::ffi::OsString;

/// Return all environment variables for the given unit-dependencies
Expand Down Expand Up @@ -55,3 +56,42 @@ fn unit_artifact_type_name_upper(unit: &Unit) -> &'static str {
invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid),
}
}

/// Given a dependency with an artifact `artifact_dep` and a set of available `targets`
/// of its package, find a target for each kind of artifacts that are to be built.
///
/// Failure to match any target results in an error mentioning the parent manifests
/// `parent_package` name.
pub(crate) fn match_artifacts_kind_with_targets<'t, 'd>(
artifact_dep: &'d Dependency,
targets: &'t [Target],
parent_package: &str,
) -> CargoResult<HashSet<(&'d ArtifactKind, &'t Target)>> {
let mut out = HashSet::new();
let artifact_requirements = artifact_dep.artifact().expect("artifact present");
for artifact_kind in artifact_requirements.kinds() {
let mut extend = |kind, filter: &dyn Fn(&&Target) -> bool| {
let mut iter = targets.iter().filter(filter).peekable();
let found = iter.peek().is_some();
out.extend(std::iter::repeat(kind).zip(iter));
found
};
let found = match artifact_kind {
ArtifactKind::Cdylib => extend(artifact_kind, &|t| t.is_cdylib()),
ArtifactKind::Staticlib => extend(artifact_kind, &|t| t.is_staticlib()),
ArtifactKind::AllBinaries => extend(artifact_kind, &|t| t.is_bin()),
ArtifactKind::SelectedBinary(bin_name) => extend(artifact_kind, &|t| {
t.is_bin() && t.name() == bin_name.as_str()
}),
};
if !found {
anyhow::bail!(
"dependency `{}` in package `{}` requires a `{}` artifact to be present.",
artifact_dep.name_in_toml(),
parent_package,
artifact_kind
);
}
}
Ok(out)
}
43 changes: 3 additions & 40 deletions src/cargo/core/compiler/unit_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ use std::collections::{HashMap, HashSet};

use log::trace;

use crate::core::compiler::artifact::match_artifacts_kind_with_targets;
use crate::core::compiler::unit_graph::{UnitDep, UnitGraph};
use crate::core::compiler::{
CompileKind, CompileMode, CrateType, RustcTargetData, Unit, UnitInterner,
};
use crate::core::dependency::{Artifact, ArtifactKind, ArtifactTarget, DepKind};
use crate::core::dependency::{Artifact, ArtifactTarget, DepKind};
use crate::core::profiles::{Profile, Profiles, UnitFor};
use crate::core::resolver::features::{FeaturesFor, ResolvedFeatures};
use crate::core::resolver::Resolve;
Expand Down Expand Up @@ -557,6 +558,7 @@ fn artifact_targets_to_unit_deps(
let ret =
match_artifacts_kind_with_targets(dep, artifact_pkg.targets(), parent.pkg.name().as_str())?
.into_iter()
.map(|(_artifact_kind, target)| target)
.flat_map(|target| {
// We split target libraries into individual units, even though rustc is able
// to produce multiple kinds in an single invocation for the sole reason that
Expand Down Expand Up @@ -598,45 +600,6 @@ fn artifact_targets_to_unit_deps(
Ok(ret)
}

/// Given a dependency with an artifact `artifact_dep` and a set of available `targets`
/// of its package, find a target for each kind of artifacts that are to be built.
///
/// Failure to match any target results in an error mentioning the parent manifests
/// `parent_package` name.
fn match_artifacts_kind_with_targets<'a>(
artifact_dep: &Dependency,
targets: &'a [Target],
parent_package: &str,
) -> CargoResult<HashSet<&'a Target>> {
let mut out = HashSet::new();
let artifact_requirements = artifact_dep.artifact().expect("artifact present");
for artifact_kind in artifact_requirements.kinds() {
let mut extend = |filter: &dyn Fn(&&Target) -> bool| {
let mut iter = targets.iter().filter(filter).peekable();
let found = iter.peek().is_some();
out.extend(iter);
found
};
let found = match artifact_kind {
ArtifactKind::Cdylib => extend(&|t| t.is_cdylib()),
ArtifactKind::Staticlib => extend(&|t| t.is_staticlib()),
ArtifactKind::AllBinaries => extend(&|t| t.is_bin()),
ArtifactKind::SelectedBinary(bin_name) => {
extend(&|t| t.is_bin() && t.name() == bin_name.as_str())
}
};
if !found {
anyhow::bail!(
"dependency `{}` in package `{}` requires a `{}` artifact to be present.",
artifact_dep.name_in_toml(),
parent_package,
artifact_kind
);
}
}
Ok(out)
}

/// Returns the dependencies necessary to document a package.
fn compute_deps_doc(
unit: &Unit,
Expand Down
21 changes: 14 additions & 7 deletions src/cargo/core/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,10 +566,8 @@ impl ser::Serialize for ArtifactKind {
S: ser::Serializer,
{
let out: Cow<'_, str> = match *self {
ArtifactKind::AllBinaries => "bin".into(),
ArtifactKind::Staticlib => "staticlib".into(),
ArtifactKind::Cdylib => "cdylib".into(),
ArtifactKind::SelectedBinary(name) => format!("bin:{}", name.as_str()).into(),
_ => self.crate_type().into(),
};
out.serialize(s)
}
Expand All @@ -578,15 +576,24 @@ impl ser::Serialize for ArtifactKind {
impl fmt::Display for ArtifactKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
ArtifactKind::Cdylib => "cdylib",
ArtifactKind::Staticlib => "staticlib",
ArtifactKind::AllBinaries => "bin",
ArtifactKind::SelectedBinary(bin_name) => return write!(f, "bin:{}", bin_name),
ArtifactKind::SelectedBinary(bin_name) => return write!(f, "bin:{bin_name}"),
_ => self.crate_type(),
})
}
}

impl ArtifactKind {
/// Returns a string of crate type of the artifact being built.
///
/// Note that the name of `SelectedBinary` would be dropped and displayed as `bin`.
pub fn crate_type(&self) -> &'static str {
match self {
ArtifactKind::AllBinaries | ArtifactKind::SelectedBinary(_) => "bin",
ArtifactKind::Cdylib => "cdylib",
ArtifactKind::Staticlib => "staticlib",
}
}

fn parse(kind: &str) -> CargoResult<Self> {
Ok(match kind {
"bin" => ArtifactKind::AllBinaries,
Expand Down
169 changes: 134 additions & 35 deletions src/cargo/ops/cargo_output_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::core::compiler::artifact::match_artifacts_kind_with_targets;
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::DepKind;
use crate::core::package::SerializedPackage;
use crate::core::resolver::{features::CliFeatures, HasDevUnits, Resolve};
use crate::core::{Dependency, Package, PackageId, Workspace};
use crate::core::{Package, PackageId, Workspace};
use crate::ops::{self, Packages};
use crate::util::interning::InternedString;
use crate::util::CargoResult;
Expand Down Expand Up @@ -81,6 +82,8 @@ struct MetadataResolveNode {

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

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

/// Builds the resolve graph as it will be displayed to the user.
Expand Down Expand Up @@ -149,7 +164,7 @@ fn build_resolve_graph(
&package_map,
&target_data,
&requested_kinds,
);
)?;
}
// Get a Vec of Packages.
let actual_packages = package_map
Expand All @@ -172,9 +187,9 @@ fn build_resolve_graph_r(
package_map: &BTreeMap<PackageId, Package>,
target_data: &RustcTargetData<'_>,
requested_kinds: &[CompileKind],
) {
) -> CargoResult<()> {
if node_map.contains_key(&pkg_id) {
return;
return Ok(());
}
// This normalizes the IDs so that they are consistent between the
// `packages` array and the `resolve` map. This is a bit of a hack to
Expand All @@ -193,9 +208,9 @@ fn build_resolve_graph_r(
let normalize_id = |id| -> PackageId { *package_map.get_key_value(&id).unwrap().0 };
let features = resolve.features(pkg_id).to_vec();

let deps: Vec<Dep> = resolve
.deps(pkg_id)
.filter(|(_dep_id, deps)| {
let deps = {
let mut dep_metadatas = Vec::new();
let iter = resolve.deps(pkg_id).filter(|(_dep_id, deps)| {
if requested_kinds == [CompileKind::Host] {
true
} else {
Expand All @@ -204,27 +219,109 @@ fn build_resolve_graph_r(
.any(|dep| target_data.dep_platform_activated(dep, *kind))
})
}
})
.filter_map(|(dep_id, deps)| {
let mut dep_kinds: Vec<_> = deps.iter().map(DepKindInfo::from).collect();
});
for (dep_id, deps) in iter {
let mut dep_kinds = Vec::new();

let targets = package_map[&dep_id].targets();

// Try to get the extern name for lib, or crate name for bins.
let extern_name = |target| {
resolve
.extern_crate_name_and_dep_name(pkg_id, dep_id, target)
.map(|(ext_crate_name, _)| ext_crate_name)
};

let lib_target = targets.iter().find(|t| t.is_lib());

for dep in deps.iter() {
if let Some(target) = lib_target {
// When we do have a library target, include them in deps if...
let included = match dep.artifact() {
// it is not an artifact dep at all
None => true,
// it is also an artifact dep with `{ …, lib = true }`
Some(a) if a.is_lib() => true,
_ => false,
};
// TODO(bindeps): Cargo shouldn't have `extern_name` field
// if the user is not using -Zbindeps.
// Remove this condition ` after -Zbindeps gets stabilized.
let extern_name = if dep.artifact().is_some() {
Some(extern_name(target)?)
} else {
None
};
if included {
dep_kinds.push(DepKindInfo {
kind: dep.kind(),
target: dep.platform().cloned(),
extern_name,
artifact: None,
compile_target: None,
bin_name: None,
});
}
}

// No need to proceed if there is no artifact dependency.
let Some(artifact_requirements) = dep.artifact() else {
continue;
};

let compile_target = match artifact_requirements.target() {
Some(t) => t
.to_compile_target()
.map(|t| t.rustc_target())
// Given that Cargo doesn't know which target it should resolve to,
// when an artifact dep is specified with { target = "target" },
// keep it with a special "<target>" string,
.or_else(|| Some(InternedString::new("<target>"))),
None => None,
};

let target_set =
match_artifacts_kind_with_targets(dep, targets, pkg_id.name().as_str())?;
dep_kinds.reserve(target_set.len());
for (kind, target) in target_set.into_iter() {
dep_kinds.push(DepKindInfo {
kind: dep.kind(),
target: dep.platform().cloned(),
extern_name: extern_name(target).ok(),
artifact: Some(kind.crate_type()),
compile_target,
bin_name: target.is_bin().then(|| target.name().to_string()),
})
}
}

dep_kinds.sort();
package_map
.get(&dep_id)
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
.and_then(|lib_target| {
resolve
.extern_crate_name_and_dep_name(pkg_id, dep_id, lib_target)
.map(|(ext_crate_name, _)| ext_crate_name)
.ok()
})
.map(|name| Dep {
name,
pkg: normalize_id(dep_id),

let pkg = normalize_id(dep_id);

let dep = match (lib_target, dep_kinds.len()) {
(Some(target), _) => Dep {
name: extern_name(target)?,
pkg,
dep_kinds,
})
})
.collect();
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| normalize_id(dep.pkg)).collect();
},
// No lib target exists but contains artifact deps.
(None, 1..) => Dep {
name: InternedString::new(""),
pkg,
dep_kinds,
},
// No lib or artifact dep exists.
// Ususally this mean parent depending on non-lib bin crate.
(None, _) => continue,
};

dep_metadatas.push(dep)
}
dep_metadatas
};

let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
let to_visit = dumb_deps.clone();
let node = MetadataResolveNode {
id: normalize_id(pkg_id),
Expand All @@ -241,6 +338,8 @@ fn build_resolve_graph_r(
package_map,
target_data,
requested_kinds,
);
)?;
}

Ok(())
}
Loading

0 comments on commit f0b6f81

Please sign in to comment.