Skip to content

Commit 035913f

Browse files
committed
Include build script execution in the fingerprint.
1 parent 716b02c commit 035913f

File tree

6 files changed

+170
-38
lines changed

6 files changed

+170
-38
lines changed

src/cargo/core/compiler/context/compilation_files.rs

+13-4
Original file line numberDiff line numberDiff line change
@@ -186,20 +186,29 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
186186
self.layout(unit.kind).fingerprint().join(dir)
187187
}
188188

189-
/// Returns the appropriate directory layout for either a plugin or not.
189+
/// Returns the directory where a compiled build script is stored.
190+
/// `/path/to/target/{debug,release}/build/PKG-HASH`
190191
pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf {
191192
assert!(unit.target.is_custom_build());
192193
assert!(!unit.mode.is_run_custom_build());
193194
let dir = self.pkg_dir(unit);
194195
self.layout(Kind::Host).build().join(dir)
195196
}
196197

197-
/// Returns the appropriate directory layout for either a plugin or not.
198-
pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf {
198+
/// Returns the directory where information about running a build script
199+
/// is stored.
200+
/// `/path/to/target/{debug,release}/build/PKG-HASH`
201+
pub fn build_script_run_dir(&self, unit: &Unit<'a>) -> PathBuf {
199202
assert!(unit.target.is_custom_build());
200203
assert!(unit.mode.is_run_custom_build());
201204
let dir = self.pkg_dir(unit);
202-
self.layout(unit.kind).build().join(dir).join("out")
205+
self.layout(unit.kind).build().join(dir)
206+
}
207+
208+
/// Returns the "OUT_DIR" directory for running a build script.
209+
/// `/path/to/target/{debug,release}/build/PKG-HASH/out`
210+
pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf {
211+
self.build_script_run_dir(unit).join("out")
203212
}
204213

205214
/// Returns the file stem for a given target/profile combo (with metadata).

src/cargo/core/compiler/custom_build.rs

+5-8
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
132132
.expect("running a script not depending on an actual script");
133133
let script_dir = cx.files().build_script_dir(build_script_unit);
134134
let script_out_dir = cx.files().build_script_out_dir(unit);
135+
let script_run_dir = cx.files().build_script_run_dir(unit);
135136
let build_plan = bcx.build_config.build_plan;
136137
let invocation_name = unit.buildkey();
137138

@@ -241,13 +242,9 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
241242
let pkg_name = unit.pkg.to_string();
242243
let build_state = Arc::clone(&cx.build_state);
243244
let id = unit.pkg.package_id();
244-
let (output_file, err_file, root_output_file) = {
245-
let build_output_parent = script_out_dir.parent().unwrap();
246-
let output_file = build_output_parent.join("output");
247-
let err_file = build_output_parent.join("stderr");
248-
let root_output_file = build_output_parent.join("root-output");
249-
(output_file, err_file, root_output_file)
250-
};
245+
let output_file = script_run_dir.join("output");
246+
let err_file = script_run_dir.join("stderr");
247+
let root_output_file = script_run_dir.join("root-output");
251248
let host_target_root = cx.files().target_root().to_path_buf();
252249
let all = (
253250
id,
@@ -332,7 +329,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
332329
state.build_plan(invocation_name, cmd.clone(), Arc::new(Vec::new()));
333330
} else {
334331
state.running(&cmd);
335-
let timestamp = paths::get_current_filesystem_time(&output_file)?;
332+
let timestamp = paths::set_invocation_time(&script_run_dir)?;
336333
let output = if extra_verbose {
337334
let prefix = format!("[{} {}] ", id.name(), id.version());
338335
state.capture_output(&cmd, Some(prefix), true)

src/cargo/core/compiler/fingerprint.rs

+57-21
Original file line numberDiff line numberDiff line change
@@ -455,13 +455,12 @@ fn calculate<'a, 'cfg>(
455455

456456
// Next, recursively calculate the fingerprint for all of our dependencies.
457457
//
458-
// Skip the fingerprints of build scripts as they may not always be
459-
// available and the dirtiness propagation for modification is tracked
460-
// elsewhere. Also skip fingerprints of binaries because they don't actually
461-
// induce a recompile, they're just dependencies in the sense that they need
462-
// to be built.
463-
let deps = cx.dep_targets(unit);
464-
let deps = deps
458+
// Skip the fingerprints of build scripts, they are included below in the
459+
// `local` vec. Also skip fingerprints of binaries because they don't
460+
// actually induce a recompile, they're just dependencies in the sense
461+
// that they need to be built.
462+
let mut deps = cx
463+
.dep_targets(unit)
465464
.iter()
466465
.filter(|u| !u.target.is_custom_build() && !u.target.is_bin())
467466
.map(|dep| {
@@ -475,8 +474,9 @@ fn calculate<'a, 'cfg>(
475474
})
476475
})
477476
.collect::<CargoResult<Vec<_>>>()?;
477+
deps.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id));
478478

479-
// And finally, calculate what our own local fingerprint is
479+
// And finally, calculate what our own local fingerprint is.
480480
let local = if use_dep_info(unit) {
481481
let dep_info = dep_info_loc(cx, unit);
482482
let mtime = dep_info_mtime_if_fresh(unit.pkg, &dep_info)?;
@@ -485,8 +485,32 @@ fn calculate<'a, 'cfg>(
485485
let fingerprint = pkg_fingerprint(&cx.bcx, unit.pkg)?;
486486
LocalFingerprint::Precalculated(fingerprint)
487487
};
488-
let mut deps = deps;
489-
deps.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id));
488+
let mut local = vec![local];
489+
// Include fingerprint for any build scripts this unit requires.
490+
local.extend(
491+
cx.dep_targets(unit)
492+
.iter()
493+
.filter(|u| u.mode.is_run_custom_build())
494+
.map(|dep| {
495+
// If the build script is overridden, use the override info as
496+
// the override. Otherwise, use the last invocation time of
497+
// the build script. If the build script re-runs during this
498+
// run, dirty propagation within the JobQueue will ensure that
499+
// this gets invalidated. This is only here to catch the
500+
// situation when cargo is run a second time for another
501+
// target that wasn't built previously (such as `cargo build`
502+
// then `cargo test`).
503+
build_script_override_fingerprint(cx, unit).unwrap_or_else(|| {
504+
let ts_path = cx
505+
.files()
506+
.build_script_run_dir(dep)
507+
.join("invoked.timestamp");
508+
let ts_path_mtime = paths::mtime(&ts_path).ok();
509+
LocalFingerprint::mtime(cx.files().target_root(), ts_path_mtime, &ts_path)
510+
})
511+
}),
512+
);
513+
490514
let extra_flags = if unit.mode.is_doc() {
491515
bcx.rustdocflags_args(unit)?
492516
} else {
@@ -502,7 +526,7 @@ fn calculate<'a, 'cfg>(
502526
path: util::hash_u64(&super::path_args(&cx.bcx, unit).0),
503527
features: format!("{:?}", bcx.resolve.features_sorted(unit.pkg.package_id())),
504528
deps,
505-
local: vec![local],
529+
local,
506530
memoized_hash: Mutex::new(None),
507531
edition: unit.target.edition(),
508532
rustflags: extra_flags,
@@ -595,19 +619,13 @@ fn build_script_local_fingerprints<'a, 'cfg>(
595619
cx: &mut Context<'a, 'cfg>,
596620
unit: &Unit<'a>,
597621
) -> CargoResult<(Vec<LocalFingerprint>, Option<PathBuf>)> {
598-
let state = cx.build_state.outputs.lock().unwrap();
599622
// First up, if this build script is entirely overridden, then we just
600623
// return the hash of what we overrode it with.
601-
//
602-
// Note that the `None` here means that we don't want to update the local
603-
// fingerprint afterwards because this is all just overridden.
604-
if let Some(output) = state.get(&(unit.pkg.package_id(), unit.kind)) {
624+
if let Some(fingerprint) = build_script_override_fingerprint(cx, unit) {
605625
debug!("override local fingerprints deps");
606-
let s = format!(
607-
"overridden build state with hash: {}",
608-
util::hash_u64(output)
609-
);
610-
return Ok((vec![LocalFingerprint::Precalculated(s)], None));
626+
// Note that the `None` here means that we don't want to update the local
627+
// fingerprint afterwards because this is all just overridden.
628+
return Ok((vec![fingerprint], None));
611629
}
612630

613631
// Next up we look at the previously listed dependencies for the build
@@ -633,6 +651,24 @@ fn build_script_local_fingerprints<'a, 'cfg>(
633651
))
634652
}
635653

654+
/// Create a local fingerprint for an overridden build script.
655+
/// Returns None if it is not overridden.
656+
fn build_script_override_fingerprint<'a, 'cfg>(
657+
cx: &mut Context<'a, 'cfg>,
658+
unit: &Unit<'a>,
659+
) -> Option<LocalFingerprint> {
660+
let state = cx.build_state.outputs.lock().unwrap();
661+
state
662+
.get(&(unit.pkg.package_id(), unit.kind))
663+
.map(|output| {
664+
let s = format!(
665+
"overridden build state with hash: {}",
666+
util::hash_u64(output)
667+
);
668+
LocalFingerprint::Precalculated(s)
669+
})
670+
}
671+
636672
fn local_fingerprints_deps(
637673
deps: &BuildDeps,
638674
target_root: &Path,

src/cargo/core/compiler/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ fn rustc<'a, 'cfg>(
239239
.get_cwd()
240240
.unwrap_or_else(|| cx.bcx.config.cwd())
241241
.to_path_buf();
242+
let fingerprint_dir = cx.files().fingerprint_dir(unit);
242243

243244
return Ok(Work::new(move |state| {
244245
// Only at runtime have we discovered what the extra -L and -l
@@ -289,7 +290,7 @@ fn rustc<'a, 'cfg>(
289290
}
290291

291292
state.running(&rustc);
292-
let timestamp = paths::get_current_filesystem_time(&dep_info_loc)?;
293+
let timestamp = paths::set_invocation_time(&fingerprint_dir)?;
293294
if json_messages {
294295
exec.exec_json(
295296
rustc,

src/cargo/util/paths.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -187,16 +187,17 @@ pub fn mtime(path: &Path) -> CargoResult<FileTime> {
187187
Ok(FileTime::from_last_modification_time(&meta))
188188
}
189189

190-
/// get `FileTime::from_system_time(SystemTime::now());` using the exact clock that this file system is using.
191-
pub fn get_current_filesystem_time(path: &Path) -> CargoResult<FileTime> {
190+
/// Record the current time on the filesystem (using the filesystem's clock)
191+
/// using a file at the given directory. Returns the current time.
192+
pub fn set_invocation_time(path: &Path) -> CargoResult<FileTime> {
192193
// note that if `FileTime::from_system_time(SystemTime::now());` is determined to be sufficient,
193194
// then this can be removed.
194-
let timestamp = path.with_file_name("invoked.timestamp");
195+
let timestamp = path.join("invoked.timestamp");
195196
write(
196197
&timestamp,
197198
b"This file has an mtime of when this was started.",
198199
)?;
199-
Ok(mtime(&timestamp)?)
200+
mtime(&timestamp)
200201
}
201202

202203
#[cfg(unix)]

tests/testsuite/freshness.rs

+88
Original file line numberDiff line numberDiff line change
@@ -1616,3 +1616,91 @@ fn rebuild_on_mid_build_file_modification() {
16161616

16171617
t.join().ok().unwrap();
16181618
}
1619+
1620+
#[test]
1621+
fn dirty_both_lib_and_test() {
1622+
// This tests that all artifacts that depend on the results of a build
1623+
// script will get rebuilt when the build script reruns, even for separate
1624+
// commands. It does the following:
1625+
//
1626+
// 1. Project "foo" has a build script which will compile a small
1627+
// staticlib to link against. Normally this would use the `cc` crate,
1628+
// but here we just use rustc to avoid the `cc` dependency.
1629+
// 2. Build the library.
1630+
// 3. Build the unit test. The staticlib intentionally has a bad value.
1631+
// 4. Rewrite the staticlib with the correct value.
1632+
// 5. Build the library again.
1633+
// 6. Build the unit test. This should recompile.
1634+
1635+
let slib = |n| {
1636+
format!(
1637+
r#"
1638+
#[no_mangle]
1639+
pub extern "C" fn doit() -> i32 {{
1640+
return {};
1641+
}}
1642+
"#,
1643+
n
1644+
)
1645+
};
1646+
1647+
let p = project()
1648+
.file(
1649+
"src/lib.rs",
1650+
r#"
1651+
extern "C" {
1652+
fn doit() -> i32;
1653+
}
1654+
1655+
#[test]
1656+
fn t1() {
1657+
assert_eq!(unsafe { doit() }, 1, "doit assert failure");
1658+
}
1659+
"#,
1660+
)
1661+
.file(
1662+
"build.rs",
1663+
r#"
1664+
use std::env;
1665+
use std::path::PathBuf;
1666+
use std::process::Command;
1667+
1668+
fn main() {
1669+
let rustc = env::var_os("RUSTC").unwrap();
1670+
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
1671+
assert!(
1672+
Command::new(rustc)
1673+
.args(&[
1674+
"--crate-type=staticlib",
1675+
"--out-dir",
1676+
out_dir.to_str().unwrap(),
1677+
"slib.rs"
1678+
])
1679+
.status()
1680+
.unwrap()
1681+
.success(),
1682+
"slib build failed"
1683+
);
1684+
println!("cargo:rustc-link-lib=slib");
1685+
println!("cargo:rustc-link-search={}", out_dir.display());
1686+
}
1687+
"#,
1688+
)
1689+
.file("slib.rs", &slib(2))
1690+
.build();
1691+
1692+
p.cargo("build").run();
1693+
1694+
// 2 != 1
1695+
p.cargo("test --lib")
1696+
.with_status(101)
1697+
.with_stdout_contains("[..]doit assert failure[..]")
1698+
.run();
1699+
1700+
// Fix the mistake.
1701+
p.change_file("slib.rs", &slib(1));
1702+
1703+
p.cargo("build").run();
1704+
// This should recompile with the new static lib, and the test should pass.
1705+
p.cargo("test --lib").run();
1706+
}

0 commit comments

Comments
 (0)