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
24pub 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#[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 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 #[serde(skip_serializing_if = "Option::is_none")]
119 extern_name: Option<InternedString>,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 artifact: Option<&'static str>,
123 #[serde(skip_serializing_if = "Option::is_none")]
128 compile_target: Option<InternedString>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 bin_name: Option<String>,
132 }
134
135fn build_resolve_graph(
137 ws: &Workspace<'_>,
138 metadata_opts: &OutputMetadataOptions,
139) -> CargoResult<(Vec<SerializedPackage>, MetadataResolve)> {
140 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 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 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 .map(|pkg| (pkg.package_id(), Package::clone(pkg)))
180 .collect();
181
182 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 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 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 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 let included = match dep.artifact() {
267 None => true,
269 Some(a) if a.is_lib() => true,
271 _ => false,
272 };
273 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 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 .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 (None, 1..) => Dep {
337 name: "".into(),
338 pkg: pkg_id.to_spec(),
339 pkg_id,
340 dep_kinds,
341 },
342 (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}