cargo-fetch: add option to fetch for a target
authorBrandon Williams <bmwill@google.com>
Mon, 9 Apr 2018 18:31:04 +0000 (11:31 -0700)
committerBrandon Williams <bmwill@google.com>
Thu, 26 Apr 2018 17:07:01 +0000 (10:07 -0700)
Teach cargo-fetch how to optionally fetch dependencies based on a target
platform by specifying the target triple via `--target <TRIPLE>`.

Signed-off-by: Brandon Williams <bmwill@google.com>
src/bin/commands/fetch.rs
src/cargo/core/compiler/context/mod.rs
src/cargo/core/compiler/mod.rs
src/cargo/ops/cargo_fetch.rs
src/cargo/ops/mod.rs
tests/testsuite/fetch.rs

index 642653fdaa451e15d6b93c0ebd8bda6a601a5e20..f69ed256b18810292fa27d01a58387498588ec3a 100644 (file)
@@ -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(())
 }
index a3cd0e19f6e8d63887c8d74eb04f2f88d3161519..fdf47780b763ab2f007dee8340e516f5745bd2af 100644 (file)
@@ -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.
 ///
index c414cafd8b42b60d6eb9b910905e4d9da443b7eb..d00fa0205c837373a2860f8ec4f03b976057b286 100644 (file)
@@ -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;
 
index c9ac0012b3ca4c0596ce2191edc4aed6a7d9e0cb..f4658167acc8b2e47899e129bbab290a674c8a86 100644 (file)
@@ -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<String>,
+}
 
 /// 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<String>,
+    resolve: &'a Resolve,
+    packages: &'a PackageSet<'cfg>,
+) -> CargoResult<HashSet<&'a PackageId>> {
+    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::<Vec<_>>();
+
+    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::<Vec<_>>();
+
+        deps_to_fetch.extend(dependency_ids);
+    }
+
+    Ok(fetched_packages)
+}
index 1e805f69acd894a5aca1785105a5deab4b10ad60..c72d03a89e30af1523da642e048ddaff39efe5bf 100644 (file)
@@ -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};
index 5ddd8802dc4a6ba99d93b876a4f361da09380aba..1d7fedab8e2fbc3298f3a2eee607f0d2cfe535ef 100644 (file)
@@ -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 [..]"),
+    );
+}