#
cargo/ops/
cargo_output_metadata.rs

    1use crate::core::compiler::artifact::match_artifacts_kind_with_targets;
    2use crate::core::compiler::{CompileKind, CompileKindFallback, RustcTargetData};
    3use crate::core::dependency::DepKind;
    4use crate::core::package::SerializedPackage;
    5use crate::core::resolver::{features::CliFeatures, HasDevUnits, Resolve};
    6use crate::core::{Package, PackageId, PackageIdSpec, Workspace};
    7use crate::ops::{self, Packages};
    8use crate::util::interning::InternedString;
    9use crate::util::CargoResult;
    10use cargo_platform::Platform;
    11use serde::Serialize;
    12use std::collections::BTreeMap;
    13use std::path::PathBuf;
    14
    15const VERSION: u32 = 1;
    16
    17pub struct OutputMetadataOptions {
    18    pub cli_features: CliFeatures,
    19    pub no_deps: bool,
    20    pub version: u32,
    21    pub filter_platforms: Vec<String>,
    22}
    23
    24/// Loads the manifest, resolves the dependencies of the package to the concrete
    25/// used versions - considering overrides - and writes all dependencies in a JSON
    26/// format to stdout.
    27pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
    28    if opt.version != VERSION {
    29        anyhow::bail!(
    30            "metadata version {} not supported, only {} is currently supported",
    31            opt.version,
    32            VERSION
    33        );
    34    }
    35    let (packages, resolve) = if opt.no_deps {
    36        let packages = ws
    37            .members()
    38            .map(|pkg| pkg.serialized(ws.gctx().cli_unstable(), ws.unstable_features()))
    39            .collect();
    40        (packages, None)
    41    } else {
    42        let (packages, resolve) = build_resolve_graph(ws, opt)?;
    43        (packages, Some(resolve))
    44    };
    45
    46    Ok(ExportInfo {
    47        packages,
    48        workspace_members: ws.members().map(|pkg| pkg.package_id().to_spec()).collect(),
    49        workspace_default_members: ws
    50            .default_members()
    51            .map(|pkg| pkg.package_id().to_spec())
    52            .collect(),
    53        resolve,
    54        target_directory: ws.target_dir().into_path_unlocked(),
    55        build_directory: ws
    56            .gctx()
    57            .cli_unstable()
    58            .build_dir
    59            .then(|| ws.build_dir().into_path_unlocked()),
    60        version: VERSION,
    61        workspace_root: ws.root().to_path_buf(),
    62        metadata: ws.custom_metadata().cloned(),
    63    })
    64}
    65
    66/// This is the structure that is serialized and displayed to the user.
    67///
    68/// See cargo-metadata.adoc for detailed documentation of the format.
    69#[derive(Serialize)]
    70pub struct ExportInfo {
    71    packages: Vec<SerializedPackage>,
    72    workspace_members: Vec<PackageIdSpec>,
    73    workspace_default_members: Vec<PackageIdSpec>,
    74    resolve: Option<MetadataResolve>,
    75    target_directory: PathBuf,
    76    #[serde(skip_serializing_if = "Option::is_none")]
    77    build_directory: Option<PathBuf>,
    78    version: u32,
    79    workspace_root: PathBuf,
    80    metadata: Option<toml::Value>,
    81}
    82
    83#[derive(Serialize)]
    84struct MetadataResolve {
    85    nodes: Vec<MetadataResolveNode>,
    86    root: Option<PackageIdSpec>,
    87}
    88
    89#[derive(Serialize)]
    90struct MetadataResolveNode {
    91    id: PackageIdSpec,
    92    dependencies: Vec<PackageIdSpec>,
    93    deps: Vec<Dep>,
    94    features: Vec<InternedString>,
    95}
    96
    97#[derive(Serialize)]
    98struct Dep {
    99    // TODO(bindeps): after -Zbindeps gets stabilized,
    100    // mark this field as deprecated in the help manual of cargo-metadata
    101    name: InternedString,
    102    pkg: PackageIdSpec,
    103    #[serde(skip)]
    104    pkg_id: PackageId,
    105    dep_kinds: Vec<DepKindInfo>,
    106}
    107
    108#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
    109struct DepKindInfo {
    110    kind: DepKind,
    111    target: Option<Platform>,
    112
    113    // vvvvv The fields below are introduced for `-Z bindeps`.
    114    /// What the manifest calls the crate.
    115    ///
    116    /// A renamed dependency will show the rename instead of original name.
    117    // TODO(bindeps): Remove `Option` after -Zbindeps get stabilized.
    118    #[serde(skip_serializing_if = "Option::is_none")]
    119    extern_name: Option<InternedString>,
    120    /// Artifact's crate type, e.g. staticlib, cdylib, bin...
    121    #[serde(skip_serializing_if = "Option::is_none")]
    122    artifact: Option<&'static str>,
    123    /// Equivalent to `{ target = "…" }` in an artifact dependency requirement.
    124    ///
    125    /// * If the target points to a custom target JSON file, the path will be absolute.
    126    /// * If the target is a build assumed target `{ target = "target" }`, it will show as `<target>`.
    127    #[serde(skip_serializing_if = "Option::is_none")]
    128    compile_target: Option<InternedString>,
    129    /// Executable name for an artifact binary dependency.
    130    #[serde(skip_serializing_if = "Option::is_none")]
    131    bin_name: Option<String>,
    132    // ^^^^^ The fields above are introduced for `-Z bindeps`.
    133}
    134
    135/// Builds the resolve graph as it will be displayed to the user.
    136fn build_resolve_graph(
    137    ws: &Workspace<'_>,
    138    metadata_opts: &OutputMetadataOptions,
    139) -> CargoResult<(Vec<SerializedPackage>, MetadataResolve)> {
    140    // TODO: Without --filter-platform, features are being resolved for `host` only.
    141    // How should this work?
    142    //
    143    // Otherwise note that "just host" is used as the fallback here if
    144    // `filter_platforms` is empty to intentionally avoid reading
    145    // `$CARGO_BUILD_TARGET` (or `build.target`) which makes sense for other
    146    // subcommands like `cargo build` but does not fit with this command.
    147    let requested_kinds = CompileKind::from_requested_targets_with_fallback(
    148        ws.gctx(),
    149        &metadata_opts.filter_platforms,
    150        CompileKindFallback::JustHost,
    151    )?;
    152    let mut target_data = RustcTargetData::new(ws, &requested_kinds)?;
    153    // Resolve entire workspace.
    154    let specs = Packages::All(Vec::new()).to_package_id_specs(ws)?;
    155    let force_all = if metadata_opts.filter_platforms.is_empty() {
    156        crate::core::resolver::features::ForceAllTargets::Yes
    157    } else {
    158        crate::core::resolver::features::ForceAllTargets::No
    159    };
    160
    161    // Note that even with --filter-platform we end up downloading host dependencies as well,
    162    // as that is the behavior of download_accessible.
    163    let dry_run = false;
    164    let ws_resolve = ops::resolve_ws_with_opts(
    165        ws,
    166        &mut target_data,
    167        &requested_kinds,
    168        &metadata_opts.cli_features,
    169        &specs,
    170        HasDevUnits::Yes,
    171        force_all,
    172        dry_run,
    173    )?;
    174
    175    let package_map: BTreeMap<PackageId, Package> = ws_resolve
    176        .pkg_set
    177        .packages()
    178        // This is a little lazy, but serde doesn't handle Rc fields very well.
    179        .map(|pkg| (pkg.package_id(), Package::clone(pkg)))
    180        .collect();
    181
    182    // Start from the workspace roots, and recurse through filling out the
    183    // map, filtering targets as necessary.
    184    let mut node_map = BTreeMap::new();
    185    for member_pkg in ws.members() {
    186        build_resolve_graph_r(
    187            &mut node_map,
    188            member_pkg.package_id(),
    189            &ws_resolve.targeted_resolve,
    190            &package_map,
    191            &target_data,
    192            &requested_kinds,
    193        )?;
    194    }
    195    // Get a Vec of Packages.
    196    let actual_packages = package_map
    197        .into_iter()
    198        .filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
    199        .map(|pkg| pkg.serialized(ws.gctx().cli_unstable(), ws.unstable_features()))
    200        .collect();
    201
    202    let mr = MetadataResolve {
    203        nodes: node_map.into_iter().map(|(_pkg_id, node)| node).collect(),
    204        root: ws.current_opt().map(|pkg| pkg.package_id().to_spec()),
    205    };
    206    Ok((actual_packages, mr))
    207}
    208
    209fn build_resolve_graph_r(
    210    node_map: &mut BTreeMap<PackageId, MetadataResolveNode>,
    211    pkg_id: PackageId,
    212    resolve: &Resolve,
    213    package_map: &BTreeMap<PackageId, Package>,
    214    target_data: &RustcTargetData<'_>,
    215    requested_kinds: &[CompileKind],
    216) -> CargoResult<()> {
    217    if node_map.contains_key(&pkg_id) {
    218        return Ok(());
    219    }
    220    // This normalizes the IDs so that they are consistent between the
    221    // `packages` array and the `resolve` map. This is a bit of a hack to
    222    // compensate for the fact that
    223    // SourceKind::Git(GitReference::Branch("master")) is the same as
    224    // SourceKind::Git(GitReference::DefaultBranch). We want IDs in the JSON
    225    // to be opaque, and compare with basic string equality, so this will
    226    // always prefer the style of ID in the Package instead of the resolver.
    227    // Cargo generally only exposes PackageIds from the Package struct, and
    228    // AFAIK this is the only place where the resolver variant is exposed.
    229    //
    230    // This diverges because the SourceIds created for Packages are built
    231    // based on the Dependency declaration, but the SourceIds in the resolver
    232    // are deserialized from Cargo.lock. Cargo.lock may have been generated by
    233    // an older (or newer!) version of Cargo which uses a different style.
    234    let normalize_id = |id| -> PackageId { *package_map.get_key_value(&id).unwrap().0 };
    235    let features = resolve.features(pkg_id).to_vec();
    236
    237    let deps = {
    238        let mut dep_metadatas = Vec::new();
    239        let iter = resolve.deps(pkg_id).filter(|(_dep_id, deps)| {
    240            if requested_kinds == [CompileKind::Host] {
    241                true
    242            } else {
    243                requested_kinds.iter().any(|kind| {
    244                    deps.iter()
    245                        .any(|dep| target_data.dep_platform_activated(dep, *kind))
    246                })
    247            }
    248        });
    249        for (dep_id, deps) in iter {
    250            let mut dep_kinds = Vec::new();
    251
    252            let targets = package_map[&dep_id].targets();
    253
    254            // Try to get the extern name for lib, or crate name for bins.
    255            let extern_name = |target| {
    256                resolve
    257                    .extern_crate_name_and_dep_name(pkg_id, dep_id, target)
    258                    .map(|(ext_crate_name, _)| ext_crate_name)
    259            };
    260
    261            let lib_target = targets.iter().find(|t| t.is_lib());
    262
    263            for dep in deps.iter() {
    264                if let Some(target) = lib_target {
    265                    // When we do have a library target, include them in deps if...
    266                    let included = match dep.artifact() {
    267                        // it is not an artifact dep at all
    268                        None => true,
    269                        // it is also an artifact dep with `{ …, lib = true }`
    270                        Some(a) if a.is_lib() => true,
    271                        _ => false,
    272                    };
    273                    // TODO(bindeps): Cargo shouldn't have `extern_name` field
    274                    // if the user is not using -Zbindeps.
    275                    // Remove this condition ` after -Zbindeps gets stabilized.
    276                    let extern_name = if dep.artifact().is_some() {
    277                        Some(extern_name(target)?)
    278                    } else {
    279                        None
    280                    };
    281                    if included {
    282                        dep_kinds.push(DepKindInfo {
    283                            kind: dep.kind(),
    284                            target: dep.platform().cloned(),
    285                            extern_name,
    286                            artifact: None,
    287                            compile_target: None,
    288                            bin_name: None,
    289                        });
    290                    }
    291                }
    292
    293                // No need to proceed if there is no artifact dependency.
    294                let Some(artifact_requirements) = dep.artifact() else {
    295                    continue;
    296                };
    297
    298                let compile_target = match artifact_requirements.target() {
    299                    Some(t) => t
    300                        .to_compile_target()
    301                        .map(|t| t.rustc_target())
    302                        // Given that Cargo doesn't know which target it should resolve to,
    303                        // when an artifact dep is specified with { target = "target" },
    304                        // keep it with a special "<target>" string,
    305                        .or_else(|| Some("<target>".into())),
    306                    None => None,
    307                };
    308
    309                let target_set =
    310                    match_artifacts_kind_with_targets(dep, targets, pkg_id.name().as_str())?;
    311                dep_kinds.reserve(target_set.len());
    312                for (kind, target) in target_set.into_iter() {
    313                    dep_kinds.push(DepKindInfo {
    314                        kind: dep.kind(),
    315                        target: dep.platform().cloned(),
    316                        extern_name: extern_name(target).ok(),
    317                        artifact: Some(kind.crate_type()),
    318                        compile_target,
    319                        bin_name: target.is_bin().then(|| target.name().to_string()),
    320                    })
    321                }
    322            }
    323
    324            dep_kinds.sort();
    325
    326            let pkg_id = normalize_id(dep_id);
    327
    328            let dep = match (lib_target, dep_kinds.len()) {
    329                (Some(target), _) => Dep {
    330                    name: extern_name(target)?,
    331                    pkg: pkg_id.to_spec(),
    332                    pkg_id,
    333                    dep_kinds,
    334                },
    335                // No lib target exists but contains artifact deps.
    336                (None, 1..) => Dep {
    337                    name: "".into(),
    338                    pkg: pkg_id.to_spec(),
    339                    pkg_id,
    340                    dep_kinds,
    341                },
    342                // No lib or artifact dep exists.
    343                // Usually this mean parent depending on non-lib bin crate.
    344                (None, _) => continue,
    345            };
    346
    347            dep_metadatas.push(dep)
    348        }
    349        dep_metadatas
    350    };
    351
    352    let to_visit: Vec<PackageId> = deps.iter().map(|dep| dep.pkg_id).collect();
    353    let node = MetadataResolveNode {
    354        id: normalize_id(pkg_id).to_spec(),
    355        dependencies: to_visit.iter().map(|id| id.to_spec()).collect(),
    356        deps,
    357        features,
    358    };
    359    node_map.insert(pkg_id, node);
    360    for dep_id in to_visit {
    361        build_resolve_graph_r(
    362            node_map,
    363            dep_id,
    364            resolve,
    365            package_map,
    366            target_data,
    367            requested_kinds,
    368        )?;
    369    }
    370
    371    Ok(())
    372}