Skip to content

Commit 5da4b4d

Browse files
committed
Auto merge of #7376 - ehuss:filter-platform, r=alexcrichton
Add --filter-platform to `cargo metadata`. This adds the `--filter-platform` flag to `cargo metadata` to give users a way to filter the resolve information based on the target triple. This is just a prototype to open for feedback. Some things that need feedback: - Debate the name of the flag. - It uses "host" as a special triple to mean the local host. Does that make sense? It seemed a little weird. - Should it also filter the dependencies in the "packages" array? Right now it only does resolve. I'm on the fence. It probably should, but that would be an intrusive change to rewrite the Package values. - Should the filtering be transitive? That is, if a package is only reachable by a specific platform, should it be removed from the resolve "nodes"? What about "packages"? Currently it is included, with the intent that you walk the resolve starting with a root (like a workspace member). But it might be surprising to see "winapi" when you filter for a unix platform. This will need documentation before it is merged.
2 parents a2f4906 + ca02e03 commit 5da4b4d

File tree

6 files changed

+809
-96
lines changed

6 files changed

+809
-96
lines changed

src/bin/cargo/commands/metadata.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ pub fn cli() -> App {
1212
)
1313
.arg(opt("quiet", "No output printed to stdout").short("q"))
1414
.arg_features()
15+
.arg(
16+
opt(
17+
"filter-platform",
18+
"Only include resolve dependencies matching the given target-triple",
19+
)
20+
.value_name("TRIPLE"),
21+
)
1522
.arg(opt(
1623
"no-deps",
1724
"Output information only about the root package \
@@ -44,6 +51,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
4451
all_features: args.is_present("all-features"),
4552
no_default_features: args.is_present("no-default-features"),
4653
no_deps: args.is_present("no-deps"),
54+
filter_platform: args.value_of("filter-platform").map(|s| s.to_string()),
4755
version,
4856
};
4957

Lines changed: 135 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
use std::collections::HashMap;
2-
use std::path::PathBuf;
3-
4-
use serde::ser;
5-
use serde::Serialize;
6-
1+
use crate::core::compiler::{CompileKind, CompileTarget, TargetInfo};
72
use crate::core::resolver::{Resolve, ResolveOpts};
83
use crate::core::{Package, PackageId, Workspace};
94
use crate::ops::{self, Packages};
105
use crate::util::CargoResult;
116

7+
use serde::Serialize;
8+
use std::collections::HashMap;
9+
use std::path::PathBuf;
10+
1211
const VERSION: u32 = 1;
1312

1413
pub struct OutputMetadataOptions {
@@ -17,6 +16,7 @@ pub struct OutputMetadataOptions {
1716
pub all_features: bool,
1817
pub no_deps: bool,
1918
pub version: u32,
19+
pub filter_platform: Option<String>,
2020
}
2121

2222
/// Loads the manifest, resolves the dependencies of the package to the concrete
@@ -30,54 +30,33 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
3030
VERSION
3131
);
3232
}
33-
if opt.no_deps {
34-
metadata_no_deps(ws, opt)
33+
let (packages, resolve) = if opt.no_deps {
34+
let packages = ws.members().cloned().collect();
35+
(packages, None)
3536
} else {
36-
metadata_full(ws, opt)
37-
}
38-
}
39-
40-
fn metadata_no_deps(ws: &Workspace<'_>, _opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
41-
Ok(ExportInfo {
42-
packages: ws.members().cloned().collect(),
43-
workspace_members: ws.members().map(|pkg| pkg.package_id()).collect(),
44-
resolve: None,
45-
target_directory: ws.target_dir().into_path_unlocked(),
46-
version: VERSION,
47-
workspace_root: ws.root().to_path_buf(),
48-
})
49-
}
50-
51-
fn metadata_full(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
52-
let specs = Packages::All.to_package_id_specs(ws)?;
53-
let opts = ResolveOpts::new(
54-
/*dev_deps*/ true,
55-
&opt.features,
56-
opt.all_features,
57-
!opt.no_default_features,
58-
);
59-
let ws_resolve = ops::resolve_ws_with_opts(ws, opts, &specs)?;
60-
let mut packages = HashMap::new();
61-
for pkg in ws_resolve
62-
.pkg_set
63-
.get_many(ws_resolve.pkg_set.package_ids())?
64-
{
65-
packages.insert(pkg.package_id(), pkg.clone());
66-
}
37+
let resolve_opts = ResolveOpts::new(
38+
/*dev_deps*/ true,
39+
&opt.features,
40+
opt.all_features,
41+
!opt.no_default_features,
42+
);
43+
let (packages, resolve) = build_resolve_graph(ws, resolve_opts, &opt.filter_platform)?;
44+
(packages, Some(resolve))
45+
};
6746

6847
Ok(ExportInfo {
69-
packages: packages.values().map(|p| (*p).clone()).collect(),
48+
packages,
7049
workspace_members: ws.members().map(|pkg| pkg.package_id()).collect(),
71-
resolve: Some(MetadataResolve {
72-
resolve: (packages, ws_resolve.targeted_resolve),
73-
root: ws.current_opt().map(|pkg| pkg.package_id()),
74-
}),
50+
resolve,
7551
target_directory: ws.target_dir().into_path_unlocked(),
7652
version: VERSION,
7753
workspace_root: ws.root().to_path_buf(),
7854
})
7955
}
8056

57+
/// This is the structure that is serialized and displayed to the user.
58+
///
59+
/// See cargo-metadata.adoc for detailed documentation of the format.
8160
#[derive(Serialize)]
8261
pub struct ExportInfo {
8362
packages: Vec<Package>,
@@ -88,52 +67,124 @@ pub struct ExportInfo {
8867
workspace_root: PathBuf,
8968
}
9069

91-
/// Newtype wrapper to provide a custom `Serialize` implementation.
92-
/// The one from lock file does not fit because it uses a non-standard
93-
/// format for `PackageId`s
9470
#[derive(Serialize)]
9571
struct MetadataResolve {
96-
#[serde(rename = "nodes", serialize_with = "serialize_resolve")]
97-
resolve: (HashMap<PackageId, Package>, Resolve),
72+
nodes: Vec<MetadataResolveNode>,
9873
root: Option<PackageId>,
9974
}
10075

101-
fn serialize_resolve<S>(
102-
(packages, resolve): &(HashMap<PackageId, Package>, Resolve),
103-
s: S,
104-
) -> Result<S::Ok, S::Error>
105-
where
106-
S: ser::Serializer,
107-
{
108-
#[derive(Serialize)]
109-
struct Dep {
110-
name: String,
111-
pkg: PackageId,
112-
}
76+
#[derive(Serialize)]
77+
struct MetadataResolveNode {
78+
id: PackageId,
79+
dependencies: Vec<PackageId>,
80+
deps: Vec<Dep>,
81+
features: Vec<String>,
82+
}
11383

114-
#[derive(Serialize)]
115-
struct Node<'a> {
116-
id: PackageId,
117-
dependencies: Vec<PackageId>,
118-
deps: Vec<Dep>,
119-
features: Vec<&'a str>,
120-
}
84+
#[derive(Serialize)]
85+
struct Dep {
86+
name: String,
87+
pkg: PackageId,
88+
}
12189

122-
s.collect_seq(resolve.iter().map(|id| {
123-
Node {
124-
id,
125-
dependencies: resolve.deps(id).map(|(pkg, _deps)| pkg).collect(),
126-
deps: resolve
127-
.deps(id)
128-
.filter_map(|(pkg, _deps)| {
129-
packages
130-
.get(&pkg)
131-
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
132-
.and_then(|lib_target| resolve.extern_crate_name(id, pkg, lib_target).ok())
133-
.map(|name| Dep { name, pkg })
134-
})
135-
.collect(),
136-
features: resolve.features_sorted(id),
90+
/// Builds the resolve graph as it will be displayed to the user.
91+
fn build_resolve_graph(
92+
ws: &Workspace<'_>,
93+
resolve_opts: ResolveOpts,
94+
target: &Option<String>,
95+
) -> CargoResult<(Vec<Package>, MetadataResolve)> {
96+
let target_info = match target {
97+
Some(target) => {
98+
let config = ws.config();
99+
let ct = CompileTarget::new(target)?;
100+
let short_name = ct.short_name().to_string();
101+
let kind = CompileKind::Target(ct);
102+
let rustc = config.load_global_rustc(Some(ws))?;
103+
Some((short_name, TargetInfo::new(config, kind, &rustc, kind)?))
137104
}
138-
}))
105+
None => None,
106+
};
107+
// Resolve entire workspace.
108+
let specs = Packages::All.to_package_id_specs(ws)?;
109+
let ws_resolve = ops::resolve_ws_with_opts(ws, resolve_opts, &specs)?;
110+
// Download all Packages. This is needed to serialize the information
111+
// for every package. In theory this could honor target filtering,
112+
// but that would be somewhat complex.
113+
let mut package_map: HashMap<PackageId, Package> = ws_resolve
114+
.pkg_set
115+
.get_many(ws_resolve.pkg_set.package_ids())?
116+
.into_iter()
117+
.map(|pkg| (pkg.package_id(), pkg.clone()))
118+
.collect();
119+
// Start from the workspace roots, and recurse through filling out the
120+
// map, filtering targets as necessary.
121+
let mut node_map = HashMap::new();
122+
for member_pkg in ws.members() {
123+
build_resolve_graph_r(
124+
&mut node_map,
125+
member_pkg.package_id(),
126+
&ws_resolve.targeted_resolve,
127+
&package_map,
128+
target_info.as_ref(),
129+
);
130+
}
131+
// Get a Vec of Packages.
132+
let actual_packages = package_map
133+
.drain()
134+
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
135+
.collect();
136+
let mr = MetadataResolve {
137+
nodes: node_map.drain().map(|(_pkg_id, node)| node).collect(),
138+
root: ws.current_opt().map(|pkg| pkg.package_id()),
139+
};
140+
Ok((actual_packages, mr))
141+
}
142+
143+
fn build_resolve_graph_r(
144+
node_map: &mut HashMap<PackageId, MetadataResolveNode>,
145+
pkg_id: PackageId,
146+
resolve: &Resolve,
147+
package_map: &HashMap<PackageId, Package>,
148+
target: Option<&(String, TargetInfo)>,
149+
) {
150+
if node_map.contains_key(&pkg_id) {
151+
return;
152+
}
153+
let features = resolve
154+
.features_sorted(pkg_id)
155+
.into_iter()
156+
.map(|s| s.to_string())
157+
.collect();
158+
let deps: Vec<Dep> = resolve
159+
.deps(pkg_id)
160+
.filter(|(_dep_id, deps)| match target {
161+
Some((short_name, info)) => deps.iter().any(|dep| {
162+
let platform = match dep.platform() {
163+
Some(p) => p,
164+
None => return true,
165+
};
166+
platform.matches(short_name, info.cfg())
167+
}),
168+
None => true,
169+
})
170+
.filter_map(|(dep_id, _deps)| {
171+
package_map
172+
.get(&dep_id)
173+
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
174+
.and_then(|lib_target| resolve.extern_crate_name(pkg_id, dep_id, lib_target).ok())
175+
.map(|name| Dep { name, pkg: dep_id })
176+
})
177+
.collect();
178+
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
179+
let to_visit = dumb_deps.clone();
180+
let node = MetadataResolveNode {
181+
id: pkg_id,
182+
dependencies: dumb_deps,
183+
deps,
184+
features,
185+
};
186+
node_map.insert(pkg_id, node);
187+
for dep_id in to_visit {
188+
build_resolve_graph_r(node_map, dep_id, resolve, package_map, target);
189+
}
139190
}

src/doc/man/cargo-metadata.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ The output has the following format:
202202
/* The resolved dependency graph, with the concrete versions and features
203203
selected. The set depends on the enabled features.
204204
This is null if --no-deps is specified.
205+
By default, this includes all dependencies for all target platforms.
206+
The `--filter-platform` flag may be used to narrow to a specific
207+
target triple.
205208
*/
206209
"resolve": {
207210
/* Array of nodes within the dependency graph.
@@ -265,6 +268,14 @@ The output has the following format:
265268
Specify the version of the output format to use. Currently `1` is the only
266269
possible value.
267270

271+
*--filter-platform* _TRIPLE_::
272+
This filters the `resolve` output to only include dependencies for the
273+
given target triple. Without this flag, the resolve includes all targets.
274+
+
275+
Note that the dependencies listed in the "packages" array still includes all
276+
dependencies. Each package definition is intended to be an unaltered
277+
reproduction of the information within `Cargo.toml`.
278+
268279
include::options-features.adoc[]
269280

270281
=== Display Options

src/doc/man/generated/cargo-metadata.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ <h2 id="cargo_metadata_output_format">OUTPUT FORMAT</h2>
209209
/* The resolved dependency graph, with the concrete versions and features
210210
selected. The set depends on the enabled features.
211211
This is null if --no-deps is specified.
212+
By default, this includes all dependencies for all target platforms.
213+
The `--filter-platform` flag may be used to narrow to a specific
214+
target triple.
212215
*/
213216
"resolve": {
214217
/* Array of nodes within the dependency graph.
@@ -279,6 +282,16 @@ <h3 id="cargo_metadata_output_options">Output Options</h3>
279282
<p>Specify the version of the output format to use. Currently <code>1</code> is the only
280283
possible value.</p>
281284
</dd>
285+
<dt class="hdlist1"><strong>--filter-platform</strong> <em>TRIPLE</em></dt>
286+
<dd>
287+
<p>This filters the <code>resolve</code> output to only include dependencies for the
288+
given target triple. Without this flag, the resolve includes all targets.</p>
289+
<div class="paragraph">
290+
<p>Note that the dependencies listed in the "packages" array still includes all
291+
dependencies. Each package definition is intended to be an unaltered
292+
reproduction of the information within <code>Cargo.toml</code>.</p>
293+
</div>
294+
</dd>
282295
</dl>
283296
</div>
284297
</div>

src/etc/man/cargo-metadata.1

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
.\" Title: cargo-metadata
33
.\" Author: [see the "AUTHOR(S)" section]
44
.\" Generator: Asciidoctor 2.0.10
5-
.\" Date: 2019-09-17
5+
.\" Date: 2019-10-28
66
.\" Manual: \ \&
77
.\" Source: \ \&
88
.\" Language: English
99
.\"
10-
.TH "CARGO\-METADATA" "1" "2019-09-17" "\ \&" "\ \&"
10+
.TH "CARGO\-METADATA" "1" "2019-10-28" "\ \&" "\ \&"
1111
.ie \n(.g .ds Aq \(aq
1212
.el .ds Aq '
1313
.ss \n[.ss] 0
@@ -223,6 +223,9 @@ The output has the following format:
223223
/* The resolved dependency graph, with the concrete versions and features
224224
selected. The set depends on the enabled features.
225225
This is null if \-\-no\-deps is specified.
226+
By default, this includes all dependencies for all target platforms.
227+
The `\-\-filter\-platform` flag may be used to narrow to a specific
228+
target triple.
226229
*/
227230
"resolve": {
228231
/* Array of nodes within the dependency graph.
@@ -288,6 +291,16 @@ dependencies.
288291
Specify the version of the output format to use. Currently \fB1\fP is the only
289292
possible value.
290293
.RE
294+
.sp
295+
\fB\-\-filter\-platform\fP \fITRIPLE\fP
296+
.RS 4
297+
This filters the \fBresolve\fP output to only include dependencies for the
298+
given target triple. Without this flag, the resolve includes all targets.
299+
.sp
300+
Note that the dependencies listed in the "packages" array still includes all
301+
dependencies. Each package definition is intended to be an unaltered
302+
reproduction of the information within \fBCargo.toml\fP.
303+
.RE
291304
.SS "Feature Selection"
292305
.sp
293306
When no feature options are given, the \fBdefault\fP feature is activated for

0 commit comments

Comments
 (0)