Skip to content

Commit

Permalink
Fix freshness when linking is interrupted.
Browse files Browse the repository at this point in the history
  • Loading branch information
ehuss committed Apr 9, 2020
1 parent cc53eca commit cd396f3
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 1 deletion.
23 changes: 22 additions & 1 deletion src/cargo/core/compiler/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,24 @@ pub fn prepare_target<'a, 'cfg>(
return Ok(Job::new(Work::noop(), Fresh));
}

// Clear out the old fingerprint file if it exists. This protects when
// compilation is interrupted leaving a corrupt file. For example, a
// project with a lib.rs and integration test:
//
// 1. Build the integration test.
// 2. Make a change to lib.rs.
// 3. Build the integration test, hit Ctrl-C while linking (with gcc).
// 4. Build the integration test again.
//
// Without this line, then step 4 will think the integration test is
// "fresh" because the mtime of the output file is newer than all of its
// dependencies. But the executable is corrupt and needs to be rebuilt.
// Clearing the fingerprint ensures that Cargo never mistakes it as
// up-to-date until after a successful build.
if loc.exists() {
paths::write(&loc, b"")?;
}

let write_fingerprint = if unit.mode.is_run_custom_build() {
// For build scripts the `local` field of the fingerprint may change
// while we're executing it. For example it could be in the legacy
Expand Down Expand Up @@ -1501,7 +1519,10 @@ fn compare_old_fingerprint(
let old_fingerprint_json = paths::read(&loc.with_extension("json"))?;
let old_fingerprint: Fingerprint = serde_json::from_str(&old_fingerprint_json)
.chain_err(|| internal("failed to deserialize json"))?;
debug_assert_eq!(util::to_hex(old_fingerprint.hash()), old_fingerprint_short);
// Fingerprint can be empty after a failed rebuild (see comment in prepare_target).
if !old_fingerprint_short.is_empty() {
debug_assert_eq!(util::to_hex(old_fingerprint.hash()), old_fingerprint_short);
}
let result = new_fingerprint.compare(&old_fingerprint);
assert!(result.is_err());
result
Expand Down
85 changes: 85 additions & 0 deletions tests/testsuite/freshness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::io;
use std::io::prelude::*;
use std::net::TcpListener;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::thread;
use std::time::SystemTime;

Expand Down Expand Up @@ -2323,3 +2324,87 @@ LLVM version: 9.0
assert_eq!(check("beta1", true), beta1_name);
assert_eq!(check("nightly1", true), nightly1_name);
}

#[cargo_test]
fn linking_interrupted() {
// Interrupt during the linking phase shouldn't leave test executable as "fresh".

let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();

// Create a linker that we can interrupt.
let linker = project()
.at("linker")
.file("Cargo.toml", &basic_manifest("linker", "1.0.0"))
.file(
"src/main.rs",
&r#"
use std::io::Read;
fn main() {
// Figure out the output filename.
let output = match std::env::args().find(|a| a.starts_with("/OUT:")) {
Some(s) => s[5..].to_string(),
None => {
let mut args = std::env::args();
loop {
if args.next().unwrap() == "-o" {
break;
}
}
args.next().unwrap()
}
};
std::fs::remove_file(&output).unwrap();
std::fs::write(&output, "").unwrap();
// Tell the test that we are ready to be interrupted.
let mut socket = std::net::TcpStream::connect("__ADDR__").unwrap();
// Wait for the test to tell us to exit.
let _ = socket.read(&mut [0; 1]);
}
"#
.replace("__ADDR__", &addr.to_string()),
)
.build();
linker.cargo("build").run();

// Build it once so that the fingerprint gets saved to disk.
let p = project()
.file("src/lib.rs", "")
.file("tests/t1.rs", "")
.build();
p.cargo("test --test t1 --no-run").run();
// Make a change, start a build, then interrupt it.
p.change_file("src/lib.rs", "// modified");
let linker_env = format!(
"CARGO_TARGET_{}_LINKER",
rustc_host().to_uppercase().replace('-', "_")
);
let mut cmd = p
.cargo("test --test t1 --no-run")
.env(&linker_env, linker.bin("linker"))
.build_command();
let mut child = cmd
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
// Wait for linking to start.
let mut conn = listener.accept().unwrap().0;

// Interrupt the child.
child.kill().unwrap();
// Note: rustc and the linker are still running, let them exit here.
conn.write(b"X").unwrap();

// Build again, shouldn't be fresh.
p.cargo("test --test t1")
.with_stderr(
"\
[COMPILING] foo [..]
[FINISHED] [..]
[RUNNING] target/debug/deps/t1[..]
",
)
.run();
}

0 comments on commit cd396f3

Please sign in to comment.