diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 9f7287baace..8928c93be10 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -1124,7 +1124,7 @@ fn prepare_metabuild( let path = unit .pkg .manifest() - .metabuild_path(build_runner.bcx.ws.target_dir()); + .metabuild_path(build_runner.bcx.ws.build_dir()); paths::create_dir_all(path.parent().unwrap())?; paths::write_if_changed(path, &output)?; Ok(()) diff --git a/src/cargo/core/compiler/fingerprint/mod.rs b/src/cargo/core/compiler/fingerprint/mod.rs index b35a592ab11..77d53b71f36 100644 --- a/src/cargo/core/compiler/fingerprint/mod.rs +++ b/src/cargo/core/compiler/fingerprint/mod.rs @@ -1857,7 +1857,7 @@ pub fn dep_info_loc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> Path /// Returns an absolute path that target directory. /// All paths are rewritten to be relative to this. fn target_root(build_runner: &BuildRunner<'_, '_>) -> PathBuf { - build_runner.bcx.ws.target_dir().into_path_unlocked() + build_runner.bcx.ws.build_dir().into_path_unlocked() } /// Reads the value from the old fingerprint hash file and compare. diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index abe3de3e2a8..2d19ed26c52 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -166,7 +166,7 @@ impl OnDiskReports { } let on_disk = serde_json::to_vec(&self).unwrap(); if let Err(e) = ws - .target_dir() + .build_dir() .open_rw_exclusive_create( FUTURE_INCOMPAT_FILE, ws.gctx(), @@ -191,7 +191,7 @@ impl OnDiskReports { /// Loads the on-disk reports. pub fn load(ws: &Workspace<'_>) -> CargoResult { - let report_file = match ws.target_dir().open_ro_shared( + let report_file = match ws.build_dir().open_ro_shared( FUTURE_INCOMPAT_FILE, ws.gctx(), "Future incompatible report", diff --git a/src/cargo/core/compiler/layout.rs b/src/cargo/core/compiler/layout.rs index 30062164d3c..09618d82f9e 100644 --- a/src/cargo/core/compiler/layout.rs +++ b/src/cargo/core/compiler/layout.rs @@ -135,6 +135,11 @@ pub struct Layout { /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this /// struct is `drop`ped. _lock: FileLock, + /// Same as `_lock` but for the build directory. + /// + /// Will be `None` when the build-dir and target-dir are the same path as we cannot + /// lock the same path twice. + _build_lock: Option, } impl Layout { @@ -150,15 +155,22 @@ impl Layout { dest: &str, ) -> CargoResult { let mut root = ws.target_dir(); + let mut build_root = ws.build_dir(); if let Some(target) = target { root.push(target.short_name()); + build_root.push(target.short_name()); } + let build_dest = build_root.join(dest); let dest = root.join(dest); // If the root directory doesn't already exist go ahead and create it // here. Use this opportunity to exclude it from backups as well if the // system supports it since this is a freshly created folder. // paths::create_dir_all_excluded_from_backups_atomic(root.as_path_unlocked())?; + if root != build_root { + paths::create_dir_all_excluded_from_backups_atomic(build_root.as_path_unlocked())?; + } + // Now that the excluded from backups target root is created we can create the // actual destination (sub)subdirectory. paths::create_dir_all(dest.as_path_unlocked())?; @@ -167,23 +179,36 @@ impl Layout { // directory, so just lock the entire thing for the duration of this // compile. let lock = dest.open_rw_exclusive_create(".cargo-lock", ws.gctx(), "build directory")?; + + let build_lock = if root != build_root { + Some(build_dest.open_rw_exclusive_create( + ".cargo-lock", + ws.gctx(), + "build directory", + )?) + } else { + None + }; let root = root.into_path_unlocked(); + let build_root = build_root.into_path_unlocked(); let dest = dest.into_path_unlocked(); - let deps = dest.join("deps"); + let build_dest = build_dest.as_path_unlocked(); + let deps = build_dest.join("deps"); let artifact = deps.join("artifact"); Ok(Layout { deps, - build: dest.join("build"), + build: build_dest.join("build"), artifact, - incremental: dest.join("incremental"), - fingerprint: dest.join(".fingerprint"), + incremental: build_dest.join("incremental"), + fingerprint: build_dest.join(".fingerprint"), examples: dest.join("examples"), doc: root.join("doc"), - tmp: root.join("tmp"), + tmp: build_root.join("tmp"), root, dest, _lock: lock, + _build_lock: build_lock, }) } diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 277039685d6..a5436233d3d 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -297,7 +297,7 @@ fn rustc( let exec = exec.clone(); let root_output = build_runner.files().host_dest().to_path_buf(); - let target_dir = build_runner.bcx.ws.target_dir().into_path_unlocked(); + let target_dir = build_runner.bcx.ws.build_dir().into_path_unlocked(); let pkg_root = unit.pkg.root().to_path_buf(); let cwd = rustc .get_cwd() @@ -555,7 +555,7 @@ fn link_targets( let path = unit .pkg .manifest() - .metabuild_path(build_runner.bcx.ws.target_dir()); + .metabuild_path(build_runner.bcx.ws.build_dir()); target.set_src_path(TargetSourcePath::Path(path)); } diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index a6c1306419b..5ed83f0498c 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -418,9 +418,7 @@ impl<'gctx> Workspace<'gctx> { if !self.gctx().cli_unstable().build_dir { return self.target_dir(); } - self.build_dir - .clone() - .unwrap_or_else(|| self.default_target_dir()) + self.build_dir.clone().unwrap_or_else(|| self.target_dir()) } fn default_target_dir(&self) -> Filesystem { diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 5297430b175..cf9c716eef2 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -41,6 +41,7 @@ pub struct CleanContext<'gctx> { /// Cleans various caches. pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { let mut target_dir = ws.target_dir(); + let mut build_dir = ws.build_dir(); let gctx = opts.gctx; let mut clean_ctx = CleanContext::new(gctx); clean_ctx.dry_run = opts.dry_run; @@ -67,6 +68,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // that profile. let dir_name = profiles.get_dir_name(); target_dir = target_dir.join(dir_name); + build_dir = build_dir.join(dir_name); } // If we have a spec, then we need to delete some packages, otherwise, just @@ -75,7 +77,15 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // Note that we don't bother grabbing a lock here as we're just going to // blow it all away anyway. if opts.spec.is_empty() { - clean_ctx.remove_paths(&[target_dir.into_path_unlocked()])?; + let paths: &[PathBuf] = if gctx.cli_unstable().build_dir && build_dir != target_dir { + &[ + target_dir.into_path_unlocked(), + build_dir.into_path_unlocked(), + ] + } else { + &[target_dir.into_path_unlocked()] + }; + clean_ctx.remove_paths(paths)?; } else { clean_specs( &mut clean_ctx, diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs index 6c920eec4e3..349d6beb999 100644 --- a/src/cargo/ops/cargo_package/mod.rs +++ b/src/cargo/ops/cargo_package/mod.rs @@ -217,7 +217,7 @@ fn do_package<'a>( }; let mut local_reg = if ws.gctx().cli_unstable().package_workspace { - let reg_dir = ws.target_dir().join("package").join("tmp-registry"); + let reg_dir = ws.build_dir().join("package").join("tmp-registry"); sid.map(|sid| TmpRegistry::new(ws.gctx(), reg_dir, sid)) .transpose()? } else { diff --git a/src/cargo/ops/cargo_package/verify.rs b/src/cargo/ops/cargo_package/verify.rs index 794e5d30581..dc8dda50291 100644 --- a/src/cargo/ops/cargo_package/verify.rs +++ b/src/cargo/ops/cargo_package/verify.rs @@ -45,9 +45,14 @@ pub fn run_verify( tar.file().seek(SeekFrom::Start(0))?; let f = GzDecoder::new(tar.file()); - let dst = tar - .parent() - .join(&format!("{}-{}", pkg.name(), pkg.version())); + let dst = if gctx.cli_unstable().build_dir { + ws.build_dir() + .as_path_unlocked() + .join(&format!("package/{}-{}", pkg.name(), pkg.version())) + } else { + tar.parent() + .join(&format!("{}-{}", pkg.name(), pkg.version())) + }; if dst.exists() { paths::remove_dir_all(&dst)?; } @@ -63,7 +68,14 @@ pub fn run_verify( let mut src = PathSource::new(&dst, id, ws.gctx()); let new_pkg = src.root_package()?; let pkg_fingerprint = hash_all(&dst)?; - let mut ws = Workspace::ephemeral(new_pkg, gctx, None, true)?; + + let target_dir = if gctx.cli_unstable().build_dir { + Some(ws.build_dir()) + } else { + None + }; + + let mut ws = Workspace::ephemeral(new_pkg, gctx, target_dir, true)?; if let Some(local_reg) = local_reg { ws.add_local_overlay( local_reg.upstream, diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs index b02227359f6..1253bed5e53 100644 --- a/src/cargo/util/context/mod.rs +++ b/src/cargo/util/context/mod.rs @@ -421,11 +421,8 @@ impl GlobalContext { /// Gets the path to the `rustc` executable. pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult { - let cache_location = ws.map(|ws| { - ws.target_dir() - .join(".rustc_info.json") - .into_path_unlocked() - }); + let cache_location = + ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked()); let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper); let rustc_workspace_wrapper = self.maybe_get_tool( "rustc_workspace_wrapper", diff --git a/src/cargo/util/workspace.rs b/src/cargo/util/workspace.rs index 9039cae8439..efc7aa7d8dd 100644 --- a/src/cargo/util/workspace.rs +++ b/src/cargo/util/workspace.rs @@ -114,7 +114,7 @@ pub fn print_available_tests(ws: &Workspace<'_>, options: &CompileOptions) -> Ca pub fn path_args(ws: &Workspace<'_>, unit: &Unit) -> (PathBuf, PathBuf) { let src = match unit.target.src_path() { TargetSourcePath::Path(path) => path.to_path_buf(), - TargetSourcePath::Metabuild => unit.pkg.manifest().metabuild_path(ws.target_dir()), + TargetSourcePath::Metabuild => unit.pkg.manifest().metabuild_path(ws.build_dir()), }; assert!(src.is_absolute()); if unit.pkg.package_id().source_id().is_path() { diff --git a/tests/testsuite/build_dir.rs b/tests/testsuite/build_dir.rs index 21830f9ed4f..bcecf967b89 100644 --- a/tests/testsuite/build_dir.rs +++ b/tests/testsuite/build_dir.rs @@ -5,14 +5,14 @@ use cargo_test_support::prelude::*; use cargo_test_support::project; #[cargo_test] -fn binary_with_debug() { +fn verify_feature_is_disabled_by_feature_flag() { let p = project() .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) .file( ".cargo/config.toml", r#" [build] - target-dir = "build" + build-dir = "build" "#, ) .build(); @@ -22,9 +22,35 @@ fn binary_with_debug() { .enable_mac_dsym() .run(); + assert_build_dir(p.root().join("target"), "debug", true); + assert!(p.root().join("target/debug/foo").is_file()); + assert!(!p.root().join("build").exists()); +} + +#[cargo_test] +fn binary_with_debug() { + let p = project() + .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) + .file( + ".cargo/config.toml", + r#" + [build] + target-dir = "target" + build-dir = "build" + "#, + ) + .build(); + + p.cargo("build -Z unstable-options -Z build-dir") + .masquerade_as_nightly_cargo(&["build-dir"]) + .enable_mac_dsym() + .run(); + assert_build_dir(p.root().join("build"), "debug", true); - assert!(p.root().join("build/debug/foo").is_file()); - assert!(!p.root().join("target").exists()); + assert_build_dir(p.root().join("target"), "debug", false); + + // Verify the binary was copied to the `target` dir + assert!(p.root().join("target/debug/foo").is_file()); } #[cargo_test] @@ -35,19 +61,22 @@ fn binary_with_release() { ".cargo/config.toml", r#" [build] - target-dir = "build" + target-dir = "target" + build-dir = "build" "#, ) .build(); - p.cargo("build --release") - .masquerade_as_nightly_cargo(&[]) + p.cargo("build -Z unstable-options -Z build-dir --release") + .masquerade_as_nightly_cargo(&["build-dir"]) .enable_mac_dsym() .run(); assert_build_dir(p.root().join("build"), "release", true); - assert!(p.root().join("build/release/foo").is_file()); - assert!(!p.root().join("target").exists()); + assert_build_dir(p.root().join("target"), "release", false); + + // Verify the binary was copied to the `target` dir + assert!(p.root().join("target/release/foo").is_file()); } #[cargo_test] @@ -56,12 +85,13 @@ fn should_default_to_target() { .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) .build(); - p.cargo("build") - .masquerade_as_nightly_cargo(&[]) + p.cargo("build -Z unstable-options -Z build-dir") + .masquerade_as_nightly_cargo(&["build-dir"]) .enable_mac_dsym() .run(); assert_build_dir(p.root().join("target"), "debug", true); + // Verify the binary exists in the correct location assert!(p.root().join("target/debug/foo").is_file()); } @@ -73,17 +103,18 @@ fn cargo_doc_should_output_to_target_dir() { ".cargo/config.toml", r#" [build] - target-dir = "build" + target-dir = "target" + build-dir = "build" "#, ) .build(); - p.cargo("doc") - .masquerade_as_nightly_cargo(&[]) + p.cargo("doc -Z unstable-options -Z build-dir") + .masquerade_as_nightly_cargo(&["build-dir"]) .enable_mac_dsym() .run(); - let docs_dir = p.root().join("build/doc"); + let docs_dir = p.root().join("target/doc"); assert!(docs_dir.exists()); assert!(docs_dir.join("foo/index.html").exists()); } @@ -96,52 +127,58 @@ fn cargo_package_should_output_to_target_dir() { ".cargo/config.toml", r#" [build] - target-dir = "build" + target-dir = "target" + build-dir = "build" "#, ) .build(); - p.cargo("package") - .masquerade_as_nightly_cargo(&[]) + p.cargo("package -Z unstable-options -Z build-dir") + .masquerade_as_nightly_cargo(&["build-dir"]) .enable_mac_dsym() .run(); assert_build_dir(p.root().join("build"), "debug", true); - let package_dir = p.root().join("build/package"); - assert!(package_dir.exists()); - assert!(package_dir.join("foo-0.0.1.crate").exists()); - assert!(package_dir.join("foo-0.0.1.crate").is_file()); - assert!(package_dir.join("foo-0.0.1").exists()); - assert!(package_dir.join("foo-0.0.1").is_dir()); + // TODO: FIX + let package_build_dir = p.root().join("build/package"); + assert!(package_build_dir.exists()); + assert!(package_build_dir.join("foo-0.0.1").exists()); + assert!(package_build_dir.join("foo-0.0.1").is_dir()); + let package_artifact_dir = p.root().join("target/package"); + assert!(package_artifact_dir.exists()); + assert!(package_artifact_dir.join("foo-0.0.1.crate").exists()); + assert!(package_artifact_dir.join("foo-0.0.1.crate").is_file()); } #[cargo_test] -fn cargo_clean_should_clean_the_target_dir() { +fn cargo_clean_should_clean_the_target_dir_and_build_dir() { let p = project() .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) .file( ".cargo/config.toml", r#" [build] - target-dir = "build" + target-dir = "target" + build-dir = "build" "#, ) .build(); - p.cargo("build") - .masquerade_as_nightly_cargo(&[]) + p.cargo("build -Z unstable-options -Z build-dir") + .masquerade_as_nightly_cargo(&["build-dir"]) .enable_mac_dsym() .run(); assert_build_dir(p.root().join("build"), "debug", true); - p.cargo("clean") - .masquerade_as_nightly_cargo(&[]) + p.cargo("clean -Z unstable-options -Z build-dir") + .masquerade_as_nightly_cargo(&["build-dir"]) .enable_mac_dsym() .run(); assert!(!p.root().join("build").exists()); + assert!(!p.root().join("target").exists()); } #[track_caller]