diff --git a/src/bin/cargo/commands/metadata.rs b/src/bin/cargo/commands/metadata.rs index a6deee86717..542bc593168 100644 --- a/src/bin/cargo/commands/metadata.rs +++ b/src/bin/cargo/commands/metadata.rs @@ -12,6 +12,14 @@ 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 \ + (\"host\" for current host)", + ) + .value_name("TRIPLE"), + ) .arg(opt( "no-deps", "Output information only about the root package \ @@ -44,6 +52,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, }; diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs index 8c60712283f..4599b7e004d 100644 --- a/src/cargo/ops/cargo_output_metadata.rs +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -1,13 +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::core::{Dependency, Package, PackageId, Workspace}; use crate::ops::{self, Packages}; use crate::util::CargoResult; +use cargo_platform::Cfg; +use serde::ser; +use serde::Serialize; +use std::collections::HashMap; +use std::path::PathBuf; const VERSION: u32 = 1; @@ -17,6 +17,7 @@ pub struct OutputMetadataOptions { pub all_features: bool, pub no_deps: bool, pub version: u32, + pub filter_platform: Option, } /// Loads the manifest, resolves the dependencies of the package to the concrete @@ -30,48 +31,63 @@ 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 { - 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 { - 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 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 package_map = HashMap::new(); + for pkg in ws_resolve + .pkg_set + .get_many(ws_resolve.pkg_set.package_ids())? + { + package_map.insert(pkg.package_id(), pkg.clone()); + } + let packages = package_map.values().map(|p| (*p).clone()).collect(); + let rustc = ws.config().load_global_rustc(Some(ws))?; + let (target, cfg) = match &opt.filter_platform { + Some(platform) => { + if platform == "host" { + let ti = + TargetInfo::new(ws.config(), CompileKind::Host, &rustc, CompileKind::Host)?; + ( + Some(rustc.host.as_str().to_string()), + Some(ti.cfg().iter().cloned().collect()), + ) + } else { + let kind = CompileKind::Target(CompileTarget::new(platform)?); + let ti = TargetInfo::new(ws.config(), kind, &rustc, kind)?; + ( + Some(platform.clone()), + Some(ti.cfg().iter().cloned().collect()), + ) + } + } + None => (None, None), + }; + let resolve = Some(MetadataResolve { + helper: ResolveHelper { + packages: package_map, + resolve: ws_resolve.targeted_resolve, + target, + cfg, + }, + root: ws.current_opt().map(|pkg| pkg.package_id()), + }); + (packages, 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(), @@ -94,17 +110,28 @@ pub struct ExportInfo { #[derive(Serialize)] struct MetadataResolve { #[serde(rename = "nodes", serialize_with = "serialize_resolve")] - resolve: (HashMap, Resolve), + helper: ResolveHelper, root: Option, } -fn serialize_resolve( - (packages, resolve): &(HashMap, Resolve), - s: S, -) -> Result +struct ResolveHelper { + packages: HashMap, + resolve: Resolve, + target: Option, + cfg: Option>, +} + +fn serialize_resolve(helper: &ResolveHelper, s: S) -> Result where S: ser::Serializer, { + let ResolveHelper { + packages, + resolve, + target, + cfg, + } = helper; + #[derive(Serialize)] struct Dep { name: String, @@ -119,12 +146,30 @@ where features: Vec<&'a str>, } + // A filter for removing platform dependencies. + let dep_filter = |(_pkg, deps): &(PackageId, &[Dependency])| match (target, cfg) { + (Some(target), Some(cfg)) => deps.iter().any(|dep| { + let platform = match dep.platform() { + Some(p) => p, + None => return true, + }; + platform.matches(target, cfg) + }), + (None, None) => true, + _ => unreachable!(), + }; + s.collect_seq(resolve.iter().map(|id| { Node { id, - dependencies: resolve.deps(id).map(|(pkg, _deps)| pkg).collect(), + dependencies: resolve + .deps(id) + .filter(dep_filter) + .map(|(pkg, _deps)| pkg) + .collect(), deps: resolve .deps(id) + .filter(dep_filter) .filter_map(|(pkg, _deps)| { packages .get(&pkg) diff --git a/tests/testsuite/metadata.rs b/tests/testsuite/metadata.rs index ef37256a0ea..08efbd40b06 100644 --- a/tests/testsuite/metadata.rs +++ b/tests/testsuite/metadata.rs @@ -1,5 +1,6 @@ +use cargo_test_support::cross_compile::alternate; use cargo_test_support::registry::Package; -use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, main_file, project}; +use cargo_test_support::{basic_bin_manifest, basic_lib_manifest, main_file, project, rustc_host}; #[cargo_test] fn cargo_metadata_simple() { @@ -1804,3 +1805,533 @@ fn deps_with_bin_only() { assert!(nodes[0]["deps"].as_array().unwrap().is_empty()); assert!(nodes[1]["deps"].as_array().unwrap().is_empty()); } + +#[cargo_test] +fn filter_platform() { + // Testing the --filter-platform flag. + Package::new("normal-dep", "0.0.1").publish(); + Package::new("host-dep", "0.0.1").publish(); + Package::new("alt-dep", "0.0.1").publish(); + Package::new("cfg-dep", "0.0.1").publish(); + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + normal-dep = "0.0.1" + + [target.{}.dependencies] + host-dep = "0.0.1" + + [target.{}.dependencies] + alt-dep = "0.0.1" + + [target.'cfg(foobar)'.dependencies] + cfg-dep = "0.0.1" + "#, + rustc_host(), + alternate() + ), + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("metadata") + .with_json( + &r#" +{ + "packages": [ + { + "name": "alt-dep", + "version": "0.0.1", + "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "license": null, + "license_file": null, + "description": null, + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "alt-dep", + "src_path": "[..]/alt-dep-0.0.1/src/lib.rs", + "edition": "2015", + "doctest": true + } + ], + "features": {}, + "manifest_path": "[..]/alt-dep-0.0.1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "edition": "2015", + "links": null + }, + { + "name": "cfg-dep", + "version": "0.0.1", + "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "license": null, + "license_file": null, + "description": null, + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "cfg-dep", + "src_path": "[..]/cfg-dep-0.0.1/src/lib.rs", + "edition": "2015", + "doctest": true + } + ], + "features": {}, + "manifest_path": "[..]/cfg-dep-0.0.1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "edition": "2015", + "links": null + }, + { + "name": "host-dep", + "version": "0.0.1", + "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "license": null, + "license_file": null, + "description": null, + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "host-dep", + "src_path": "[..]/host-dep-0.0.1/src/lib.rs", + "edition": "2015", + "doctest": true + } + ], + "features": {}, + "manifest_path": "[..]/host-dep-0.0.1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "edition": "2015", + "links": null + }, + { + "name": "normal-dep", + "version": "0.0.1", + "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "license": null, + "license_file": null, + "description": null, + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "normal-dep", + "src_path": "[..]/normal-dep-0.0.1/src/lib.rs", + "edition": "2015", + "doctest": true + } + ], + "features": {}, + "manifest_path": "[..]/normal-dep-0.0.1/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "edition": "2015", + "links": null + }, + { + "name": "foo", + "version": "0.1.0", + "id": "foo 0.1.0 (path+file:[..]foo)", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "normal-dep", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null, + "registry": null + }, + { + "name": "cfg-dep", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": "cfg(foobar)", + "registry": null + }, + { + "name": "alt-dep", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": "$ALT", + "registry": null + }, + { + "name": "host-dep", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.0.1", + "kind": null, + "rename": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": "$HOST", + "registry": null + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "foo", + "src_path": "[..]/foo/src/lib.rs", + "edition": "2015", + "doctest": true + } + ], + "features": {}, + "manifest_path": "[..]/foo/Cargo.toml", + "metadata": null, + "publish": null, + "authors": [], + "categories": [], + "keywords": [], + "readme": null, + "repository": null, + "edition": "2015", + "links": null + } + ], + "workspace_members": [ + "foo 0.1.0 (path+file:[..]foo)" + ], + "resolve": { + "nodes": [ + { + "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "foo 0.1.0 (path+file:[..]foo)", + "dependencies": [ + "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + ], + "deps": [ + { + "name": "alt_dep", + "pkg": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + }, + { + "name": "cfg_dep", + "pkg": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + }, + { + "name": "host_dep", + "pkg": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + }, + { + "name": "normal_dep", + "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + } + ], + "features": [] + }, + { + "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + } + ], + "root": "foo 0.1.0 (path+file:[..]foo)" + }, + "target_directory": "[..]/foo/target", + "version": 1, + "workspace_root": "[..]/foo" +} +"# + .replace("$ALT", &alternate()) + .replace("$HOST", &rustc_host()), + ) + .run(); + + p.cargo("metadata --filter-platform") + .arg(alternate()) + .with_json( + r#" +{ + "packages": "{...}", + "workspace_members": "{...}", + "resolve": { + "nodes": [ + { + "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "foo 0.1.0 (path+file:[..]foo)", + "dependencies": [ + "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + ], + "deps": [ + { + "name": "alt_dep", + "pkg": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + }, + { + "name": "normal_dep", + "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + } + ], + "features": [] + }, + { + "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + } + ], + "root": "foo 0.1.0 (path+file:[..]foo)" + }, + "target_directory": "[..]foo/target", + "version": 1, + "workspace_root": "[..]foo" +} +"#, + ) + .run(); + + let host_json = r#" +{ + "packages": "{...}", + "workspace_members": "{...}", + "resolve": { + "nodes": [ + { + "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "foo 0.1.0 (path+file:[..]foo)", + "dependencies": [ + "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + ], + "deps": [ + { + "name": "host_dep", + "pkg": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + }, + { + "name": "normal_dep", + "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + } + ], + "features": [] + }, + { + "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + } + ], + "root": "foo 0.1.0 (path+file:[..]foo)" + }, + "target_directory": "[..]foo/target", + "version": 1, + "workspace_root": "[..]foo" +} +"#; + + p.cargo("metadata --filter-platform=host") + .with_json(host_json) + .run(); + p.cargo("metadata --filter-platform") + .arg(rustc_host()) + .with_json(host_json) + .run(); + + p.cargo("metadata --filter-platform=host") + .env("RUSTFLAGS", "--cfg=foobar") + .with_json( + r#" +{ + "packages": "{...}", + "workspace_members": "{...}", + "resolve": { + "nodes": [ + { + "id": "alt-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "foo 0.1.0 (path+file:[..]/foo)", + "dependencies": [ + "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + ], + "deps": [ + { + "name": "cfg_dep", + "pkg": "cfg-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + }, + { + "name": "host_dep", + "pkg": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + }, + { + "name": "normal_dep", + "pkg": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" + } + ], + "features": [] + }, + { + "id": "host-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + }, + { + "id": "normal-dep 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [], + "deps": [], + "features": [] + } + ], + "root": "foo 0.1.0 (path+file:[..]/foo)" + }, + "target_directory": "[..]/foo/target", + "version": 1, + "workspace_root": "[..]/foo" +} +"#, + ) + .run(); +}