diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 0d07dafa347..7f32cb108b2 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -186,7 +186,6 @@ impl<'cfg> Workspace<'cfg> { /// before returning it, so `Ok` is only returned for valid workspaces. pub fn new(manifest_path: &Path, config: &'cfg Config) -> CargoResult> { let mut ws = Workspace::new_default(manifest_path.to_path_buf(), config); - ws.target_dir = config.target_dir()?; if manifest_path.is_relative() { bail!( @@ -197,6 +196,12 @@ impl<'cfg> Workspace<'cfg> { ws.root_manifest = ws.find_root(manifest_path)?; } + if let Some(ref root_manifest) = ws.root_manifest { + ws.target_dir = config.target_dir(root_manifest)?; + } else { + ws.target_dir = config.target_dir(manifest_path)?; + } + ws.custom_metadata = ws .load_workspace_config()? .and_then(|cfg| cfg.custom_metadata); @@ -236,7 +241,11 @@ impl<'cfg> Workspace<'cfg> { ) -> CargoResult> { let mut ws = Workspace::new_default(current_manifest, config); ws.root_manifest = Some(root_path.join("Cargo.toml")); - ws.target_dir = config.target_dir()?; + if let Some(ref root_manifest) = ws.root_manifest { + ws.target_dir = config.target_dir(root_manifest)?; + } else { + ws.target_dir = config.target_dir(&ws.current_manifest)?; + } ws.packages .packages .insert(root_path, MaybePackage::Virtual(manifest)); @@ -267,13 +276,13 @@ impl<'cfg> Workspace<'cfg> { ws.require_optional_deps = require_optional_deps; let key = ws.current_manifest.parent().unwrap(); let id = package.package_id(); - let package = MaybePackage::Package(package); - ws.packages.packages.insert(key.to_path_buf(), package); ws.target_dir = if let Some(dir) = target_dir { Some(dir) } else { - ws.config.target_dir()? + ws.config.target_dir(package.manifest_path())? }; + let package = MaybePackage::Package(package); + ws.packages.packages.insert(key.to_path_buf(), package); ws.members.push(ws.current_manifest.clone()); ws.member_ids.insert(id); ws.default_members.push(ws.current_manifest.clone()); diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index dd22cc8efb6..300592b7543 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -281,7 +281,8 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> { let mut td_opt = None; let mut needs_cleanup = false; if !self.source_id.is_path() { - let target_dir = if let Some(dir) = self.config.target_dir()? { + let manifest_path = self.pkg.manifest_path().to_path_buf(); + let target_dir = if let Some(dir) = self.config.target_dir(manifest_path)? { dir } else if let Ok(td) = TempFileBuilder::new().prefix("cargo-install").tempdir() { let p = td.path().to_owned(); diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index dd3f6ae933e..8a8be41c02b 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -493,7 +493,7 @@ impl Config { /// Returns `None` if the user has not chosen an explicit directory. /// /// Callers should prefer `Workspace::target_dir` instead. - pub fn target_dir(&self) -> CargoResult> { + pub fn target_dir(&self, manifest: impl Into) -> CargoResult> { if let Some(dir) = &self.target_dir { Ok(Some(dir.clone())) } else if let Some(dir) = self.env.get("CARGO_TARGET_DIR") { @@ -506,6 +506,21 @@ impl Config { } Ok(Some(Filesystem::new(self.cwd.join(dir)))) + } else if let Some(dir) = env::var_os("CARGO_TARGET_DIR_PREFIX") { + let prefix = Path::new(&dir); + if !prefix.is_absolute() { + bail!("CARGO_TARGET_DIR_PREFIX must describe an absolute path"); + } + let mut manifest = manifest.into(); + let result = manifest.pop(); + assert!(result); + + match manifest.strip_prefix("/") { + Ok(dir) => Ok(Some(Filesystem::new(prefix.join(&dir).join("target")))), + // FIXME: This logic is probably not safe on Windows. Not sure how + // to make a path relative there. + Err(_) => bail!("Current directory must be an absolute path"), + } } else if let Some(val) = &self.build_config()?.target_dir { let path = val.resolve_path(self); diff --git a/src/doc/src/reference/environment-variables.md b/src/doc/src/reference/environment-variables.md index b1a5db4c4e4..5047d7fb8b3 100644 --- a/src/doc/src/reference/environment-variables.md +++ b/src/doc/src/reference/environment-variables.md @@ -15,6 +15,10 @@ system: location of this directory. Once a crate is cached it is not removed by the clean command. For more details refer to the [guide](../guide/cargo-home.md). +* `CARGO_TARGET_DIR_PREFIX` — Prefix to the location where to place all + generated artifacts. The current working directory will be appended to this + prefix to form the final path for generated artifacts. Note that + `CARGO_TARGET_DIR`, if set, takes precedence over this variable. * `CARGO_TARGET_DIR` — Location of where to place all generated artifacts, relative to the current working directory. See [`build.target-dir`] to set via config. diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index ace734ba48a..a783cf9edd2 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -3737,6 +3737,63 @@ fn panic_abort_compiles_with_panic_abort() { .run(); } +#[cargo_test] +fn custom_target_dir_prefix() { + fn test(cwd: &str) { + let tmpdir = tempfile::Builder::new() + .tempdir() + .unwrap() + .path() + .to_path_buf(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + let root = p.root(); + let root_suffix = root.strip_prefix("/").unwrap(); + let exe_name = format!("foo{}", env::consts::EXE_SUFFIX); + + p.cargo("build") + .env("CARGO_TARGET_DIR_PREFIX", tmpdir.clone()) + .cwd(p.root().join(cwd)) + .run(); + + assert!( + tmpdir + .clone() + .join(root_suffix) + .join("target/debug") + .join(&exe_name) + .is_file() + ); + assert!(!&p.root().join("target/debug").join(&exe_name).is_file()); + + p.cargo("build").run(); + assert!( + tmpdir + .clone() + .join(root_suffix) + .join("target/debug") + .join(&exe_name) + .is_file() + ); + assert!(&p.root().join("target/debug").join(&exe_name).is_file()) + }; + + test("."); + test("src"); +} + #[cargo_test] fn compiler_json_error_format() { let p = project()