Skip to content

Add --filter-platform to cargo metadata. #7376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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