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