diff --git a/src/cargo/core/compiler/build_context/mod.rs b/src/cargo/core/compiler/build_context/mod.rs index 03aa4e58514..812306bbf2d 100644 --- a/src/cargo/core/compiler/build_context/mod.rs +++ b/src/cargo/core/compiler/build_context/mod.rs @@ -160,17 +160,22 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> { self.build_config.jobs } - pub fn rustflags_args(&self, unit: &Unit<'_>) -> CargoResult> { + /// Get the RUSTFLAGS argument by its kind + pub fn rustflags_args_for(&self, kind:Kind) -> CargoResult> { env_args( self.config, &self.build_config.requested_target, self.host_triple(), - self.info(unit.kind).cfg(), - unit.kind, + self.info(kind).cfg(), + kind, "RUSTFLAGS", ) } + pub fn rustflags_args(&self, unit: &Unit<'_>) -> CargoResult> { + self.rustflags_args_for(unit.kind) + } + pub fn rustdocflags_args(&self, unit: &Unit<'_>) -> CargoResult> { env_args( self.config, diff --git a/src/cargo/core/compiler/context/compilation_files.rs b/src/cargo/core/compiler/context/compilation_files.rs index ece26253764..52d1240f9f2 100644 --- a/src/cargo/core/compiler/context/compilation_files.rs +++ b/src/cargo/core/compiler/context/compilation_files.rs @@ -1,6 +1,7 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::env; use std::fmt; +use std::fs::metadata; use std::hash::{Hash, Hasher, SipHasher}; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -31,10 +32,15 @@ pub struct CompilationFiles<'a, 'cfg: 'a> { /// The root targets requested by the user on the command line (does not /// include dependencies). roots: Vec>, + primary: HashSet>, ws: &'a Workspace<'cfg>, metas: HashMap, Option>, /// For each Unit, a list all files produced. outputs: HashMap, LazyCell>>>, + /// The directory that is used as the shared targets + shared_target_dir: Option, + /// Flags indicates if we should use the shared dir + shared_dir_enabled: bool } #[derive(Debug)] @@ -70,14 +76,32 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { cx: &Context<'a, 'cfg>, ) -> CompilationFiles<'a, 'cfg> { let mut metas = HashMap::new(); + let mut primary = HashSet::new(); for unit in roots { metadata_of(unit, cx, &mut metas); + if cx.is_primary_package(unit) { + primary.insert(unit.clone()); + } } + + let has_rustflags = [Kind::Host, Kind::Target].iter() + .any(|kind| { + cx.bcx + .rustflags_args_for(*kind) + .ok() + .map_or(true, |args| args.len() > 0) + }); + let outputs = metas .keys() .cloned() .map(|unit| (unit, LazyCell::new())) .collect(); + + let shared_target_dir = env::var("CARGO_SHARED_TARGET_DIR") + .ok() + .map_or(None, |path| Some(PathBuf::from(path))); + CompilationFiles { ws, host, @@ -86,6 +110,9 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { roots: roots.to_vec(), metas, outputs, + primary, + shared_target_dir, + shared_dir_enabled: !has_rustflags } } @@ -122,10 +149,29 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { } else if unit.target.is_example() { self.layout(unit.kind).examples().to_path_buf() } else { + if let Some(ref shared_dir) = self.shared_target_dir { + if self.shared_dir_enabled && !self.primary.contains(unit) { + return shared_dir.clone(); + } + } self.deps_dir(unit).to_path_buf() } } + /// Check if the unit is in the shared dir + pub fn in_shared_dir(&self, unit: &Unit<'a>, bcx: &BuildContext<'a, 'cfg>) -> bool { + if self.shared_dir_enabled && self.shared_target_dir.is_some() && !self.primary.contains(unit) { + if let Ok(outputs) = self.outputs(unit, bcx) { + return !outputs.iter().any(|out| !metadata(out.path.as_path()).is_ok()); + } + } + false + } + + pub fn shared_dir(&self) -> Option<&Path> { + self.shared_target_dir.as_ref().map(|p| p.as_path()) + } + pub fn export_dir(&self) -> Option { self.export_dir.clone() } @@ -150,6 +196,11 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { /// Returns the directories where Rust crate dependencies are found for the /// specified unit. pub fn deps_dir(&self, unit: &Unit<'_>) -> &Path { + if let Some(ref shared_dir) = self.shared_target_dir { + if self.shared_dir_enabled && !self.primary.contains(unit) { + return shared_dir.as_path(); + } + } self.layout(unit.kind).deps() } diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index 8a485db7c5a..37d40de5cc7 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -361,6 +361,11 @@ impl<'a, 'cfg> Context<'a, 'cfg> { self.files.as_ref().unwrap().outputs(unit, self.bcx) } + /// Examine if the target is already in the global cache + pub fn in_shared_dir(&self, unit: &Unit<'a>) -> bool { + self.files.as_ref().unwrap().in_shared_dir(unit, self.bcx) + } + /// For a package, return all targets which are registered as dependencies /// for that package. // TODO: this ideally should be `-> &[Unit<'a>]` diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 3eb157f8cc5..4434d2595dd 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -243,6 +243,8 @@ fn rustc<'a, 'cfg>( .unwrap_or_else(|| cx.bcx.config.cwd()) .to_path_buf(); + let found_shared = cx.in_shared_dir(unit); + return Ok(Work::new(move |state| { // Only at runtime have we discovered what the extra -L and -l // arguments are for native libraries, so we process those here. We @@ -293,24 +295,28 @@ fn rustc<'a, 'cfg>( state.running(&rustc); if json_messages { - exec.exec_json( - rustc, - package_id, - &target, - mode, - &mut assert_is_empty, - &mut |line| json_stderr(line, package_id, &target), - ) - .map_err(internal_if_simple_exit_code) - .chain_err(|| format!("Could not compile `{}`.", name))?; + if !found_shared + { + exec.exec_json( + rustc, + package_id, + &target, + mode, + &mut assert_is_empty, + &mut |line| json_stderr(line, package_id, &target), + ) + .map_err(internal_if_simple_exit_code) + .chain_err(|| format!("Could not compile `{}`.", name))?; + } } else if build_plan { state.build_plan(buildkey, rustc.clone(), outputs.clone()); - } else { + } else if !found_shared { exec.exec_and_capture_output(rustc, package_id, &target, mode, state) .map_err(internal_if_simple_exit_code) .chain_err(|| format!("Could not compile `{}`.", name))?; } + //TODO: handle the rename ? if do_rename && real_name != crate_name { let dst = &outputs[0].path; let src = dst.with_file_name( @@ -920,6 +926,14 @@ fn build_deps_args<'a, 'cfg>( deps.push(cx.files().deps_dir(unit)); deps }); + + if let Some(shared_dir) = cx.files().shared_dir() { + cmd.arg("-L").arg(&{ + let mut deps = OsString::from("dependency="); + deps.push(shared_dir); + deps + }); + } // Be sure that the host path is also listed. This'll ensure that proc-macro // dependencies are correctly found (for reexported macros). diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index c89f7ba8fbc..d2e0d7c8726 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -4727,3 +4727,88 @@ Caused by: .with_status(101) .run(); } + + + +#[test] +fn cargo_compile_with_shared_dir() { + Package::new("bar", "0.0.1") + .file("bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + + [lib] + name = "bar" + "# + ) + .file("src/lib.rs", r#"pub fn bar() { println!("I'm bar") }"#) + .publish(); + + let p = project() + .file("Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [[bin]] + name = "foo" + + [dependencies] + bar = "*" + "# + ) + .file("src/main.rs", r#"fn main() { bar::bar() }"#) + .file("cache/dummy", "") + .build(); + + let cache_dir = p.root().join("cache"); + + p.cargo("build").env("CARGO_SHARED_TARGET_DIR", &cache_dir).run(); + assert!(p.bin("foo").is_file()); + + fn count_binary(path:&std::path::PathBuf) -> (u32, u32) { + + let (mut n_bin_output, mut n_dep_output) = (0, 0); + + if let Ok(dir_ent) = path.as_path().read_dir() { + for file in dir_ent { + if let Ok(entry) = file { + if let Some(file_name) = entry.path().file_name().map_or(None, |x| x.to_str()) { + if file_name.starts_with("libbar-") && + file_name.ends_with(".rlib") { + n_bin_output += 1; + } + else if file_name.starts_with("bar-") && + file_name.ends_with(".d") { + n_dep_output += 1; + } + } + } + } + } + + (n_bin_output, n_dep_output) + } + + let (cache_bins, cache_deps) = count_binary(&cache_dir); + let (target_bins, target_deps) = count_binary(&p.target_debug_dir().join("deps")); + + + assert_eq!(cache_bins, 1); + assert_eq!(cache_deps, 1); + assert_eq!(target_bins, 0); + assert_eq!(target_deps, 0); + + p.process(&p.bin("foo")).with_stdout("I'm bar\n").run(); + + p.cargo("clean").run(); + let (cache_bins, cache_deps) = count_binary(&cache_dir); + let (target_bins, target_deps) = count_binary(&p.target_debug_dir().join("deps")); + assert_eq!(cache_bins, 1); + assert_eq!(cache_deps, 1); + assert_eq!(target_bins, 0); + assert_eq!(target_deps, 0); +}