Skip to main content

cargo/ops/
cargo_output_metadata.rs

1use crate::core::compiler::{CompileKind, CompileTarget, RustcTargetData};
2use crate::core::dependency::DepKind;
3use crate::core::resolver::{HasDevUnits, Resolve, ResolveOpts};
4use crate::core::{Dependency, InternedString, Package, PackageId, Workspace};
5use crate::ops::{self, Packages};
6use crate::util::CargoResult;
7use cargo_platform::Platform;
8use serde::Serialize;
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12const VERSION: u32 = 1;
13
14pub struct OutputMetadataOptions {
15    pub features: Vec<String>,
16    pub no_default_features: bool,
17    pub all_features: bool,
18    pub no_deps: bool,
19    pub version: u32,
20    pub filter_platform: Option<String>,
21}
22
23/// Loads the manifest, resolves the dependencies of the package to the concrete
24/// used versions - considering overrides - and writes all dependencies in a JSON
25/// format to stdout.
26pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
27    if opt.version != VERSION {
28        anyhow::bail!(
29            "metadata version {} not supported, only {} is currently supported",
30            opt.version,
31            VERSION
32        );
33    }
34    let (packages, resolve) = if opt.no_deps {
35        let packages = ws.members().cloned().collect();
36        (packages, None)
37    } else {
38        let (packages, resolve) = build_resolve_graph(ws, opt)?;
39        (packages, Some(resolve))
40    };
41
42    Ok(ExportInfo {
43        packages,
44        workspace_members: ws.members().map(|pkg| pkg.package_id()).collect(),
45        resolve,
46        target_directory: ws.target_dir().into_path_unlocked(),
47        version: VERSION,
48        workspace_root: ws.root().to_path_buf(),
49    })
50}
51
52/// This is the structure that is serialized and displayed to the user.
53///
54/// See cargo-metadata.adoc for detailed documentation of the format.
55#[derive(Serialize)]
56pub struct ExportInfo {
57    packages: Vec<Package>,
58    workspace_members: Vec<PackageId>,
59    resolve: Option<MetadataResolve>,
60    target_directory: PathBuf,
61    version: u32,
62    workspace_root: PathBuf,
63}
64
65#[derive(Serialize)]
66struct MetadataResolve {
67    nodes: Vec<MetadataResolveNode>,
68    root: Option<PackageId>,
69}
70
71#[derive(Serialize)]
72struct MetadataResolveNode {
73    id: PackageId,
74    dependencies: Vec<PackageId>,
75    deps: Vec<Dep>,
76    features: Vec<InternedString>,
77}
78
79#[derive(Serialize)]
80struct Dep {
81    name: String,
82    pkg: PackageId,
83    dep_kinds: Vec<DepKindInfo>,
84}
85
86#[derive(Serialize, PartialEq, Eq, PartialOrd, Ord)]
87struct DepKindInfo {
88    kind: DepKind,
89    target: Option<Platform>,
90}
91
92impl From<&Dependency> for DepKindInfo {
93    fn from(dep: &Dependency) -> DepKindInfo {
94        DepKindInfo {
95            kind: dep.kind(),
96            target: dep.platform().cloned(),
97        }
98    }
99}
100
101/// Builds the resolve graph as it will be displayed to the user.
102fn build_resolve_graph(
103    ws: &Workspace<'_>,
104    metadata_opts: &OutputMetadataOptions,
105) -> CargoResult<(Vec<Package>, MetadataResolve)> {
106    // TODO: Without --filter-platform, features are being resolved for `host` only.
107    // How should this work?
108    let requested_kind = match &metadata_opts.filter_platform {
109        Some(t) => CompileKind::Target(CompileTarget::new(t)?),
110        None => CompileKind::Host,
111    };
112    let target_data = RustcTargetData::new(ws, requested_kind)?;
113    // Resolve entire workspace.
114    let specs = Packages::All.to_package_id_specs(ws)?;
115    let resolve_opts = ResolveOpts::new(
116        /*dev_deps*/ true,
117        &metadata_opts.features,
118        metadata_opts.all_features,
119        !metadata_opts.no_default_features,
120    );
121    let ws_resolve = ops::resolve_ws_with_opts(
122        ws,
123        &target_data,
124        requested_kind,
125        &resolve_opts,
126        &specs,
127        HasDevUnits::Yes,
128    )?;
129    // Download all Packages. This is needed to serialize the information
130    // for every package. In theory this could honor target filtering,
131    // but that would be somewhat complex.
132    let mut package_map: HashMap<PackageId, Package> = ws_resolve
133        .pkg_set
134        .get_many(ws_resolve.pkg_set.package_ids())?
135        .into_iter()
136        .map(|pkg| (pkg.package_id(), pkg.clone()))
137        .collect();
138
139    // Start from the workspace roots, and recurse through filling out the
140    // map, filtering targets as necessary.
141    let mut node_map = HashMap::new();
142    for member_pkg in ws.members() {
143        build_resolve_graph_r(
144            &mut node_map,
145            member_pkg.package_id(),
146            &ws_resolve.targeted_resolve,
147            &package_map,
148            &target_data,
149            requested_kind,
150        );
151    }
152    // Get a Vec of Packages.
153    let actual_packages = package_map
154        .drain()
155        .filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
156        .collect();
157    let mr = MetadataResolve {
158        nodes: node_map.drain().map(|(_pkg_id, node)| node).collect(),
159        root: ws.current_opt().map(|pkg| pkg.package_id()),
160    };
161    Ok((actual_packages, mr))
162}
163
164fn build_resolve_graph_r(
165    node_map: &mut HashMap<PackageId, MetadataResolveNode>,
166    pkg_id: PackageId,
167    resolve: &Resolve,
168    package_map: &HashMap<PackageId, Package>,
169    target_data: &RustcTargetData,
170    requested_kind: CompileKind,
171) {
172    if node_map.contains_key(&pkg_id) {
173        return;
174    }
175    let features = resolve.features(pkg_id).to_vec();
176
177    let deps: Vec<Dep> = resolve
178        .deps(pkg_id)
179        .filter(|(_dep_id, deps)| match requested_kind {
180            CompileKind::Target(_) => deps
181                .iter()
182                .any(|dep| target_data.dep_platform_activated(dep, requested_kind)),
183            // No --filter-platform is interpreted as "all platforms".
184            CompileKind::Host => true,
185        })
186        .filter_map(|(dep_id, deps)| {
187            let dep_kinds: Vec<_> = deps.iter().map(DepKindInfo::from).collect();
188            package_map
189                .get(&dep_id)
190                .and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
191                .and_then(|lib_target| resolve.extern_crate_name(pkg_id, dep_id, lib_target).ok())
192                .map(|name| Dep {
193                    name,
194                    pkg: dep_id,
195                    dep_kinds,
196                })
197        })
198        .collect();
199    let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
200    let to_visit = dumb_deps.clone();
201    let node = MetadataResolveNode {
202        id: pkg_id,
203        dependencies: dumb_deps,
204        deps,
205        features,
206    };
207    node_map.insert(pkg_id, node);
208    for dep_id in to_visit {
209        build_resolve_graph_r(
210            node_map,
211            dep_id,
212            resolve,
213            package_map,
214            target_data,
215            requested_kind,
216        );
217    }
218}