diff --git a/Cargo.toml b/Cargo.toml index 21fb83dd47d..5cb74f83e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ features = [ [dev-dependencies] cargo-test-macro = { path = "crates/cargo-test-macro" } cargo-test-support = { path = "crates/cargo-test-support" } +same-file = "1.0.6" snapbox = { version = "0.3.0", features = ["diff", "path"] } [build-dependencies] diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index bfab9cd6581..dac2c0b4db6 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -305,6 +305,19 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc) -> Car paths::remove_file(&dst)?; } } + + // Some linkers do not remove the executable, but truncate and modify it. + // That results in the old hard-link being modified even after renamed. + // We delete the old artifact here to prevent this behavior from confusing users. + // See rust-lang/cargo#8348. + if output.hardlink.is_some() && output.path.exists() { + _ = paths::remove_file(&output.path).map_err(|e| { + log::debug!( + "failed to delete previous output file `{:?}`: {e:?}", + output.path + ); + }); + } } fn verbose_if_simple_exit_code(err: Error) -> Error { diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 8916340d2c9..89e3070765a 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -6247,3 +6247,32 @@ fn primary_package_env_var() { foo.cargo("test").run(); } + +#[cargo_test] +fn renamed_uplifted_artifact_remains_unmodified_after_rebuild() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + authors = [] + version = "0.0.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build").run(); + + let bin = p.bin("foo"); + let renamed_bin = p.bin("foo-renamed"); + + fs::rename(&bin, &renamed_bin).unwrap(); + + p.change_file("src/main.rs", "fn main() { eprintln!(\"hello, world\"); }"); + p.cargo("build").run(); + + let not_the_same = !same_file::is_same_file(bin, renamed_bin).unwrap(); + assert!(not_the_same, "renamed uplifted artifact must be unmodified"); +}