Skip to content

Commit

Permalink
Auto merge of rust-lang#7376 - ehuss:filter-platform, r=alexcrichton
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bors committed Oct 28, 2019
2 parents a2f4906 + ca02e03 commit 5da4b4d
Show file tree
Hide file tree
Showing 6 changed files with 809 additions and 96 deletions.
8 changes: 8 additions & 0 deletions src/bin/cargo/commands/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ pub fn cli() -> App {
)
.arg(opt("quiet", "No output printed to stdout").short("q"))
.arg_features()
.arg(
opt(
"filter-platform",
"Only include resolve dependencies matching the given target-triple",
)
.value_name("TRIPLE"),
)
.arg(opt(
"no-deps",
"Output information only about the root package \
Expand Down Expand Up @@ -44,6 +51,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
all_features: args.is_present("all-features"),
no_default_features: args.is_present("no-default-features"),
no_deps: args.is_present("no-deps"),
filter_platform: args.value_of("filter-platform").map(|s| s.to_string()),
version,
};

Expand Down
219 changes: 135 additions & 84 deletions src/cargo/ops/cargo_output_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::collections::HashMap;
use std::path::PathBuf;

use serde::ser;
use serde::Serialize;

use crate::core::compiler::{CompileKind, CompileTarget, TargetInfo};
use crate::core::resolver::{Resolve, ResolveOpts};
use crate::core::{Package, PackageId, Workspace};
use crate::ops::{self, Packages};
use crate::util::CargoResult;

use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;

const VERSION: u32 = 1;

pub struct OutputMetadataOptions {
Expand All @@ -17,6 +16,7 @@ pub struct OutputMetadataOptions {
pub all_features: bool,
pub no_deps: bool,
pub version: u32,
pub filter_platform: Option<String>,
}

/// Loads the manifest, resolves the dependencies of the package to the concrete
Expand All @@ -30,54 +30,33 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
VERSION
);
}
if opt.no_deps {
metadata_no_deps(ws, opt)
let (packages, resolve) = if opt.no_deps {
let packages = ws.members().cloned().collect();
(packages, None)
} else {
metadata_full(ws, opt)
}
}

fn metadata_no_deps(ws: &Workspace<'_>, _opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
Ok(ExportInfo {
packages: ws.members().cloned().collect(),
workspace_members: ws.members().map(|pkg| pkg.package_id()).collect(),
resolve: None,
target_directory: ws.target_dir().into_path_unlocked(),
version: VERSION,
workspace_root: ws.root().to_path_buf(),
})
}

fn metadata_full(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> CargoResult<ExportInfo> {
let specs = Packages::All.to_package_id_specs(ws)?;
let opts = ResolveOpts::new(
/*dev_deps*/ true,
&opt.features,
opt.all_features,
!opt.no_default_features,
);
let ws_resolve = ops::resolve_ws_with_opts(ws, opts, &specs)?;
let mut packages = HashMap::new();
for pkg in ws_resolve
.pkg_set
.get_many(ws_resolve.pkg_set.package_ids())?
{
packages.insert(pkg.package_id(), pkg.clone());
}
let resolve_opts = ResolveOpts::new(
/*dev_deps*/ true,
&opt.features,
opt.all_features,
!opt.no_default_features,
);
let (packages, resolve) = build_resolve_graph(ws, resolve_opts, &opt.filter_platform)?;
(packages, Some(resolve))
};

Ok(ExportInfo {
packages: packages.values().map(|p| (*p).clone()).collect(),
packages,
workspace_members: ws.members().map(|pkg| pkg.package_id()).collect(),
resolve: Some(MetadataResolve {
resolve: (packages, ws_resolve.targeted_resolve),
root: ws.current_opt().map(|pkg| pkg.package_id()),
}),
resolve,
target_directory: ws.target_dir().into_path_unlocked(),
version: VERSION,
workspace_root: ws.root().to_path_buf(),
})
}

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

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

fn serialize_resolve<S>(
(packages, resolve): &(HashMap<PackageId, Package>, Resolve),
s: S,
) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
#[derive(Serialize)]
struct Dep {
name: String,
pkg: PackageId,
}
#[derive(Serialize)]
struct MetadataResolveNode {
id: PackageId,
dependencies: Vec<PackageId>,
deps: Vec<Dep>,
features: Vec<String>,
}

#[derive(Serialize)]
struct Node<'a> {
id: PackageId,
dependencies: Vec<PackageId>,
deps: Vec<Dep>,
features: Vec<&'a str>,
}
#[derive(Serialize)]
struct Dep {
name: String,
pkg: PackageId,
}

s.collect_seq(resolve.iter().map(|id| {
Node {
id,
dependencies: resolve.deps(id).map(|(pkg, _deps)| pkg).collect(),
deps: resolve
.deps(id)
.filter_map(|(pkg, _deps)| {
packages
.get(&pkg)
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
.and_then(|lib_target| resolve.extern_crate_name(id, pkg, lib_target).ok())
.map(|name| Dep { name, pkg })
})
.collect(),
features: resolve.features_sorted(id),
/// Builds the resolve graph as it will be displayed to the user.
fn build_resolve_graph(
ws: &Workspace<'_>,
resolve_opts: ResolveOpts,
target: &Option<String>,
) -> CargoResult<(Vec<Package>, MetadataResolve)> {
let target_info = match target {
Some(target) => {
let config = ws.config();
let ct = CompileTarget::new(target)?;
let short_name = ct.short_name().to_string();
let kind = CompileKind::Target(ct);
let rustc = config.load_global_rustc(Some(ws))?;
Some((short_name, TargetInfo::new(config, kind, &rustc, kind)?))
}
}))
None => None,
};
// Resolve entire workspace.
let specs = Packages::All.to_package_id_specs(ws)?;
let ws_resolve = ops::resolve_ws_with_opts(ws, resolve_opts, &specs)?;
// Download all Packages. This is needed to serialize the information
// for every package. In theory this could honor target filtering,
// but that would be somewhat complex.
let mut package_map: HashMap<PackageId, Package> = ws_resolve
.pkg_set
.get_many(ws_resolve.pkg_set.package_ids())?
.into_iter()
.map(|pkg| (pkg.package_id(), pkg.clone()))
.collect();
// Start from the workspace roots, and recurse through filling out the
// map, filtering targets as necessary.
let mut node_map = HashMap::new();
for member_pkg in ws.members() {
build_resolve_graph_r(
&mut node_map,
member_pkg.package_id(),
&ws_resolve.targeted_resolve,
&package_map,
target_info.as_ref(),
);
}
// Get a Vec of Packages.
let actual_packages = package_map
.drain()
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
.collect();
let mr = MetadataResolve {
nodes: node_map.drain().map(|(_pkg_id, node)| node).collect(),
root: ws.current_opt().map(|pkg| pkg.package_id()),
};
Ok((actual_packages, mr))
}

fn build_resolve_graph_r(
node_map: &mut HashMap<PackageId, MetadataResolveNode>,
pkg_id: PackageId,
resolve: &Resolve,
package_map: &HashMap<PackageId, Package>,
target: Option<&(String, TargetInfo)>,
) {
if node_map.contains_key(&pkg_id) {
return;
}
let features = resolve
.features_sorted(pkg_id)
.into_iter()
.map(|s| s.to_string())
.collect();
let deps: Vec<Dep> = resolve
.deps(pkg_id)
.filter(|(_dep_id, deps)| match target {
Some((short_name, info)) => deps.iter().any(|dep| {
let platform = match dep.platform() {
Some(p) => p,
None => return true,
};
platform.matches(short_name, info.cfg())
}),
None => true,
})
.filter_map(|(dep_id, _deps)| {
package_map
.get(&dep_id)
.and_then(|pkg| pkg.targets().iter().find(|t| t.is_lib()))
.and_then(|lib_target| resolve.extern_crate_name(pkg_id, dep_id, lib_target).ok())
.map(|name| Dep { name, pkg: dep_id })
})
.collect();
let dumb_deps: Vec<PackageId> = deps.iter().map(|dep| dep.pkg).collect();
let to_visit = dumb_deps.clone();
let node = MetadataResolveNode {
id: pkg_id,
dependencies: dumb_deps,
deps,
features,
};
node_map.insert(pkg_id, node);
for dep_id in to_visit {
build_resolve_graph_r(node_map, dep_id, resolve, package_map, target);
}
}
11 changes: 11 additions & 0 deletions src/doc/man/cargo-metadata.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ The output has the following format:
/* The resolved dependency graph, with the concrete versions and features
selected. The set depends on the enabled features.
This is null if --no-deps is specified.
By default, this includes all dependencies for all target platforms.
The `--filter-platform` flag may be used to narrow to a specific
target triple.
*/
"resolve": {
/* Array of nodes within the dependency graph.
Expand Down Expand Up @@ -265,6 +268,14 @@ The output has the following format:
Specify the version of the output format to use. Currently `1` is the only
possible value.

*--filter-platform* _TRIPLE_::
This filters the `resolve` output to only include dependencies for the
given target triple. Without this flag, the resolve includes all targets.
+
Note that the dependencies listed in the "packages" array still includes all
dependencies. Each package definition is intended to be an unaltered
reproduction of the information within `Cargo.toml`.

include::options-features.adoc[]

=== Display Options
Expand Down
13 changes: 13 additions & 0 deletions src/doc/man/generated/cargo-metadata.html
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ <h2 id="cargo_metadata_output_format">OUTPUT FORMAT</h2>
/* The resolved dependency graph, with the concrete versions and features
selected. The set depends on the enabled features.
This is null if --no-deps is specified.
By default, this includes all dependencies for all target platforms.
The `--filter-platform` flag may be used to narrow to a specific
target triple.
*/
"resolve": {
/* Array of nodes within the dependency graph.
Expand Down Expand Up @@ -279,6 +282,16 @@ <h3 id="cargo_metadata_output_options">Output Options</h3>
<p>Specify the version of the output format to use. Currently <code>1</code> is the only
possible value.</p>
</dd>
<dt class="hdlist1"><strong>--filter-platform</strong> <em>TRIPLE</em></dt>
<dd>
<p>This filters the <code>resolve</code> output to only include dependencies for the
given target triple. Without this flag, the resolve includes all targets.</p>
<div class="paragraph">
<p>Note that the dependencies listed in the "packages" array still includes all
dependencies. Each package definition is intended to be an unaltered
reproduction of the information within <code>Cargo.toml</code>.</p>
</div>
</dd>
</dl>
</div>
</div>
Expand Down
17 changes: 15 additions & 2 deletions src/etc/man/cargo-metadata.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
.\" Title: cargo-metadata
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Date: 2019-09-17
.\" Date: 2019-10-28
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "CARGO\-METADATA" "1" "2019-09-17" "\ \&" "\ \&"
.TH "CARGO\-METADATA" "1" "2019-10-28" "\ \&" "\ \&"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
Expand Down Expand Up @@ -223,6 +223,9 @@ The output has the following format:
/* The resolved dependency graph, with the concrete versions and features
selected. The set depends on the enabled features.
This is null if \-\-no\-deps is specified.
By default, this includes all dependencies for all target platforms.
The `\-\-filter\-platform` flag may be used to narrow to a specific
target triple.
*/
"resolve": {
/* Array of nodes within the dependency graph.
Expand Down Expand Up @@ -288,6 +291,16 @@ dependencies.
Specify the version of the output format to use. Currently \fB1\fP is the only
possible value.
.RE
.sp
\fB\-\-filter\-platform\fP \fITRIPLE\fP
.RS 4
This filters the \fBresolve\fP output to only include dependencies for the
given target triple. Without this flag, the resolve includes all targets.
.sp
Note that the dependencies listed in the "packages" array still includes all
dependencies. Each package definition is intended to be an unaltered
reproduction of the information within \fBCargo.toml\fP.
.RE
.SS "Feature Selection"
.sp
When no feature options are given, the \fBdefault\fP feature is activated for
Expand Down
Loading

0 comments on commit 5da4b4d

Please sign in to comment.