From 1956c5d8ba703e8ef02903ff6ae0cac650d599bb Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 9 Apr 2018 11:31:04 -0700 Subject: [PATCH] cargo-fetch: add option to fetch for a target Teach cargo-fetch how to optionally fetch dependencies based on a target platform by specifying the target triple via `--target `. Signed-off-by: Brandon Williams --- src/bin/commands/fetch.rs | 9 +- src/cargo/core/compiler/context/mod.rs | 3 +- src/cargo/core/compiler/mod.rs | 2 +- src/cargo/ops/cargo_fetch.rs | 72 +++++++++++++- src/cargo/ops/mod.rs | 2 +- tests/testsuite/fetch.rs | 132 ++++++++++++++++++++++++- 6 files changed, 209 insertions(+), 11 deletions(-) diff --git a/src/bin/commands/fetch.rs b/src/bin/commands/fetch.rs index 642653fdaa4..f69ed256b18 100644 --- a/src/bin/commands/fetch.rs +++ b/src/bin/commands/fetch.rs @@ -1,11 +1,13 @@ use command_prelude::*; use cargo::ops; +use cargo::ops::FetchOptions; pub fn cli() -> App { subcommand("fetch") .about("Fetch dependencies of a package from the network") .arg_manifest_path() + .arg_target_triple("Fetch dependencies for the target triple") .after_help( "\ If a lockfile is available, this command will ensure that all of the git @@ -22,6 +24,11 @@ all updated. pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { let ws = args.workspace(config)?; - ops::fetch(&ws)?; + + let opts = FetchOptions { + config, + target: args.target(), + }; + ops::fetch(&ws, &opts)?; Ok(()) } diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index a3cd0e19f6e..fdf47780b76 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -29,8 +29,7 @@ use self::compilation_files::{CompilationFiles, OutputFile}; pub use self::compilation_files::Metadata; mod target_info; -pub use self::target_info::FileFlavor; -use self::target_info::TargetInfo; +pub use self::target_info::{FileFlavor, TargetInfo}; /// All information needed to define a Unit. /// diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index c414cafd8b4..d00fa0205c8 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -23,7 +23,7 @@ use self::job_queue::JobQueue; use self::output_depinfo::output_depinfo; pub use self::compilation::Compilation; -pub use self::context::{Context, FileFlavor, Unit}; +pub use self::context::{Context, FileFlavor, TargetInfo, Unit}; pub use self::custom_build::{BuildMap, BuildOutput, BuildScripts}; pub use self::layout::is_bad_artifact_name; diff --git a/src/cargo/ops/cargo_fetch.rs b/src/cargo/ops/cargo_fetch.rs index c9ac0012b3c..f4658167acc 100644 --- a/src/cargo/ops/cargo_fetch.rs +++ b/src/cargo/ops/cargo_fetch.rs @@ -1,12 +1,74 @@ -use core::{PackageSet, Resolve, Workspace}; +use core::compiler::{BuildConfig, Kind, TargetInfo}; +use core::{Package, PackageId, PackageSet, Resolve, Workspace}; use ops; +use std::collections::HashSet; use util::CargoResult; +use util::Config; + +pub struct FetchOptions<'a> { + pub config: &'a Config, + /// The target arch triple to fetch dependencies for + pub target: Option, +} /// Executes `cargo fetch`. -pub fn fetch<'a>(ws: &Workspace<'a>) -> CargoResult<(Resolve, PackageSet<'a>)> { +pub fn fetch<'a>( + ws: &Workspace<'a>, + options: &FetchOptions<'a>, +) -> CargoResult<(Resolve, PackageSet<'a>)> { let (packages, resolve) = ops::resolve_ws(ws)?; - for id in resolve.iter() { - packages.get(id)?; - } + + fetch_for_target(ws, options.config, &options.target, &resolve, &packages)?; + Ok((resolve, packages)) } + +fn fetch_for_target<'a, 'cfg: 'a>( + ws: &'a Workspace<'cfg>, + config: &'cfg Config, + target: &Option, + resolve: &'a Resolve, + packages: &'a PackageSet<'cfg>, +) -> CargoResult> { + let mut fetched_packages = HashSet::new(); + let mut deps_to_fetch = Vec::new(); + let jobs = Some(1); + let build_config = BuildConfig::new(config, jobs, target, None)?; + let target_info = TargetInfo::new(config, &build_config, Kind::Target)?; + let root_package_ids = ws.members().map(Package::package_id).collect::>(); + + deps_to_fetch.extend(root_package_ids); + + while let Some(id) = deps_to_fetch.pop() { + if !fetched_packages.insert(id) { + continue; + } + + let package = packages.get(id)?; + let deps = resolve.deps(id); + let dependency_ids = deps.filter(|dep| { + package + .dependencies() + .iter() + .filter(|d| d.name() == dep.name() && d.version_req().matches(dep.version())) + .any(|d| { + // If no target was specified then all dependencies can be fetched. + let target = match *target { + Some(ref t) => t, + None => return true, + }; + // If this dependency is only available for certain platforms, + // make sure we're only fetching it for that platform. + let platform = match d.platform() { + Some(p) => p, + None => return true, + }; + platform.matches(target, target_info.cfg()) + }) + }).collect::>(); + + deps_to_fetch.extend(dependency_ids); + } + + Ok(fetched_packages) +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 1e805f69acd..c72d03a89e3 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -16,7 +16,7 @@ pub use self::registry::{publish, registry_configuration, RegistryConfig}; pub use self::registry::{http_handle, needs_custom_http_transport, registry_login, search}; pub use self::registry::{modify_owners, yank, OwnersOptions, PublishOpts}; pub use self::registry::configure_http_handle; -pub use self::cargo_fetch::fetch; +pub use self::cargo_fetch::{fetch, FetchOptions}; pub use self::cargo_pkgid::pkgid; pub use self::resolve::{resolve_with_previous, resolve_ws, resolve_ws_precisely, resolve_ws_with_method}; diff --git a/tests/testsuite/fetch.rs b/tests/testsuite/fetch.rs index 5ddd8802dc4..1d7fedab8e2 100644 --- a/tests/testsuite/fetch.rs +++ b/tests/testsuite/fetch.rs @@ -1,4 +1,6 @@ -use cargotest::support::{execs, project}; +use cargotest::rustc_host; +use cargotest::support::registry::Package; +use cargotest::support::{cross_compile, execs, project}; use hamcrest::assert_that; #[test] @@ -24,3 +26,131 @@ fn no_deps() { assert_that(p.cargo("fetch"), execs().with_status(0).with_stdout("")); } + +#[test] +fn fetch_all_platform_dependencies_when_no_target_is_given() { + Package::new("d1", "1.2.3") + .file( + "Cargo.toml", + r#" + [project] + name = "d1" + version = "1.2.3" + "#, + ) + .file("src/lib.rs", "") + .publish(); + + Package::new("d2", "0.1.2") + .file( + "Cargo.toml", + r#" + [project] + name = "d2" + version = "0.1.2" + "#, + ) + .file("src/lib.rs", "") + .publish(); + + let target = cross_compile::alternate(); + let host = rustc_host(); + let p = project("foo") + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [target.{host}.dependencies] + d1 = "1.2.3" + + [target.{target}.dependencies] + d2 = "0.1.2" + "#, + host = host, + target = target + ), + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("fetch"), + execs() + .with_status(0) + .with_stderr_contains("[..] Downloading d1 v1.2.3 [..]") + .with_stderr_contains("[..] Downloading d2 v0.1.2 [..]"), + ); +} + +#[test] +fn fetch_platform_specific_dependencies() { + Package::new("d1", "1.2.3") + .file( + "Cargo.toml", + r#" + [project] + name = "d1" + version = "1.2.3" + "#, + ) + .file("src/lib.rs", "") + .publish(); + + Package::new("d2", "0.1.2") + .file( + "Cargo.toml", + r#" + [project] + name = "d2" + version = "0.1.2" + "#, + ) + .file("src/lib.rs", "") + .publish(); + + let target = cross_compile::alternate(); + let host = rustc_host(); + let p = project("foo") + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [target.{host}.dependencies] + d1 = "1.2.3" + + [target.{target}.dependencies] + d2 = "0.1.2" + "#, + host = host, + target = target + ), + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("fetch").arg("--target").arg(&host), + execs() + .with_status(0) + .with_stderr_contains("[..] Downloading d1 v1.2.3 [..]") + .with_stderr_does_not_contain("[..] Downloading d2 v0.1.2 [..]"), + ); + + assert_that( + p.cargo("fetch").arg("--target").arg(&target), + execs() + .with_status(0) + .with_stderr_contains("[..] Downloading d2 v0.1.2[..]") + .with_stderr_does_not_contain("[..] Downloading d1 v1.2.3 [..]"), + ); +}