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
23pub 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#[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
101fn build_resolve_graph(
103 ws: &Workspace<'_>,
104 metadata_opts: &OutputMetadataOptions,
105) -> CargoResult<(Vec<Package>, MetadataResolve)> {
106 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 let specs = Packages::All.to_package_id_specs(ws)?;
115 let resolve_opts = ResolveOpts::new(
116 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 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 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 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 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}