mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00
Include build script execution in the fingerprint.
This commit is contained in:
parent
716b02cb4c
commit
035913ff6e
@ -186,7 +186,8 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
|
|||||||
self.layout(unit.kind).fingerprint().join(dir)
|
self.layout(unit.kind).fingerprint().join(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the appropriate directory layout for either a plugin or not.
|
/// Returns the directory where a compiled build script is stored.
|
||||||
|
/// `/path/to/target/{debug,release}/build/PKG-HASH`
|
||||||
pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf {
|
pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf {
|
||||||
assert!(unit.target.is_custom_build());
|
assert!(unit.target.is_custom_build());
|
||||||
assert!(!unit.mode.is_run_custom_build());
|
assert!(!unit.mode.is_run_custom_build());
|
||||||
@ -194,12 +195,20 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
|
|||||||
self.layout(Kind::Host).build().join(dir)
|
self.layout(Kind::Host).build().join(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the appropriate directory layout for either a plugin or not.
|
/// Returns the directory where information about running a build script
|
||||||
pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf {
|
/// is stored.
|
||||||
|
/// `/path/to/target/{debug,release}/build/PKG-HASH`
|
||||||
|
pub fn build_script_run_dir(&self, unit: &Unit<'a>) -> PathBuf {
|
||||||
assert!(unit.target.is_custom_build());
|
assert!(unit.target.is_custom_build());
|
||||||
assert!(unit.mode.is_run_custom_build());
|
assert!(unit.mode.is_run_custom_build());
|
||||||
let dir = self.pkg_dir(unit);
|
let dir = self.pkg_dir(unit);
|
||||||
self.layout(unit.kind).build().join(dir).join("out")
|
self.layout(unit.kind).build().join(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the "OUT_DIR" directory for running a build script.
|
||||||
|
/// `/path/to/target/{debug,release}/build/PKG-HASH/out`
|
||||||
|
pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf {
|
||||||
|
self.build_script_run_dir(unit).join("out")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the file stem for a given target/profile combo (with metadata).
|
/// Returns the file stem for a given target/profile combo (with metadata).
|
||||||
|
@ -132,6 +132,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
|
|||||||
.expect("running a script not depending on an actual script");
|
.expect("running a script not depending on an actual script");
|
||||||
let script_dir = cx.files().build_script_dir(build_script_unit);
|
let script_dir = cx.files().build_script_dir(build_script_unit);
|
||||||
let script_out_dir = cx.files().build_script_out_dir(unit);
|
let script_out_dir = cx.files().build_script_out_dir(unit);
|
||||||
|
let script_run_dir = cx.files().build_script_run_dir(unit);
|
||||||
let build_plan = bcx.build_config.build_plan;
|
let build_plan = bcx.build_config.build_plan;
|
||||||
let invocation_name = unit.buildkey();
|
let invocation_name = unit.buildkey();
|
||||||
|
|
||||||
@ -241,13 +242,9 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
|
|||||||
let pkg_name = unit.pkg.to_string();
|
let pkg_name = unit.pkg.to_string();
|
||||||
let build_state = Arc::clone(&cx.build_state);
|
let build_state = Arc::clone(&cx.build_state);
|
||||||
let id = unit.pkg.package_id();
|
let id = unit.pkg.package_id();
|
||||||
let (output_file, err_file, root_output_file) = {
|
let output_file = script_run_dir.join("output");
|
||||||
let build_output_parent = script_out_dir.parent().unwrap();
|
let err_file = script_run_dir.join("stderr");
|
||||||
let output_file = build_output_parent.join("output");
|
let root_output_file = script_run_dir.join("root-output");
|
||||||
let err_file = build_output_parent.join("stderr");
|
|
||||||
let root_output_file = build_output_parent.join("root-output");
|
|
||||||
(output_file, err_file, root_output_file)
|
|
||||||
};
|
|
||||||
let host_target_root = cx.files().target_root().to_path_buf();
|
let host_target_root = cx.files().target_root().to_path_buf();
|
||||||
let all = (
|
let all = (
|
||||||
id,
|
id,
|
||||||
@ -332,7 +329,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
|
|||||||
state.build_plan(invocation_name, cmd.clone(), Arc::new(Vec::new()));
|
state.build_plan(invocation_name, cmd.clone(), Arc::new(Vec::new()));
|
||||||
} else {
|
} else {
|
||||||
state.running(&cmd);
|
state.running(&cmd);
|
||||||
let timestamp = paths::get_current_filesystem_time(&output_file)?;
|
let timestamp = paths::set_invocation_time(&script_run_dir)?;
|
||||||
let output = if extra_verbose {
|
let output = if extra_verbose {
|
||||||
let prefix = format!("[{} {}] ", id.name(), id.version());
|
let prefix = format!("[{} {}] ", id.name(), id.version());
|
||||||
state.capture_output(&cmd, Some(prefix), true)
|
state.capture_output(&cmd, Some(prefix), true)
|
||||||
|
@ -455,13 +455,12 @@ fn calculate<'a, 'cfg>(
|
|||||||
|
|
||||||
// Next, recursively calculate the fingerprint for all of our dependencies.
|
// Next, recursively calculate the fingerprint for all of our dependencies.
|
||||||
//
|
//
|
||||||
// Skip the fingerprints of build scripts as they may not always be
|
// Skip the fingerprints of build scripts, they are included below in the
|
||||||
// available and the dirtiness propagation for modification is tracked
|
// `local` vec. Also skip fingerprints of binaries because they don't
|
||||||
// elsewhere. Also skip fingerprints of binaries because they don't actually
|
// actually induce a recompile, they're just dependencies in the sense
|
||||||
// induce a recompile, they're just dependencies in the sense that they need
|
// that they need to be built.
|
||||||
// to be built.
|
let mut deps = cx
|
||||||
let deps = cx.dep_targets(unit);
|
.dep_targets(unit)
|
||||||
let deps = deps
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|u| !u.target.is_custom_build() && !u.target.is_bin())
|
.filter(|u| !u.target.is_custom_build() && !u.target.is_bin())
|
||||||
.map(|dep| {
|
.map(|dep| {
|
||||||
@ -475,8 +474,9 @@ fn calculate<'a, 'cfg>(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<CargoResult<Vec<_>>>()?;
|
.collect::<CargoResult<Vec<_>>>()?;
|
||||||
|
deps.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id));
|
||||||
|
|
||||||
// And finally, calculate what our own local fingerprint is
|
// And finally, calculate what our own local fingerprint is.
|
||||||
let local = if use_dep_info(unit) {
|
let local = if use_dep_info(unit) {
|
||||||
let dep_info = dep_info_loc(cx, unit);
|
let dep_info = dep_info_loc(cx, unit);
|
||||||
let mtime = dep_info_mtime_if_fresh(unit.pkg, &dep_info)?;
|
let mtime = dep_info_mtime_if_fresh(unit.pkg, &dep_info)?;
|
||||||
@ -485,8 +485,32 @@ fn calculate<'a, 'cfg>(
|
|||||||
let fingerprint = pkg_fingerprint(&cx.bcx, unit.pkg)?;
|
let fingerprint = pkg_fingerprint(&cx.bcx, unit.pkg)?;
|
||||||
LocalFingerprint::Precalculated(fingerprint)
|
LocalFingerprint::Precalculated(fingerprint)
|
||||||
};
|
};
|
||||||
let mut deps = deps;
|
let mut local = vec![local];
|
||||||
deps.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id));
|
// Include fingerprint for any build scripts this unit requires.
|
||||||
|
local.extend(
|
||||||
|
cx.dep_targets(unit)
|
||||||
|
.iter()
|
||||||
|
.filter(|u| u.mode.is_run_custom_build())
|
||||||
|
.map(|dep| {
|
||||||
|
// If the build script is overridden, use the override info as
|
||||||
|
// the override. Otherwise, use the last invocation time of
|
||||||
|
// the build script. If the build script re-runs during this
|
||||||
|
// run, dirty propagation within the JobQueue will ensure that
|
||||||
|
// this gets invalidated. This is only here to catch the
|
||||||
|
// situation when cargo is run a second time for another
|
||||||
|
// target that wasn't built previously (such as `cargo build`
|
||||||
|
// then `cargo test`).
|
||||||
|
build_script_override_fingerprint(cx, unit).unwrap_or_else(|| {
|
||||||
|
let ts_path = cx
|
||||||
|
.files()
|
||||||
|
.build_script_run_dir(dep)
|
||||||
|
.join("invoked.timestamp");
|
||||||
|
let ts_path_mtime = paths::mtime(&ts_path).ok();
|
||||||
|
LocalFingerprint::mtime(cx.files().target_root(), ts_path_mtime, &ts_path)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
let extra_flags = if unit.mode.is_doc() {
|
let extra_flags = if unit.mode.is_doc() {
|
||||||
bcx.rustdocflags_args(unit)?
|
bcx.rustdocflags_args(unit)?
|
||||||
} else {
|
} else {
|
||||||
@ -502,7 +526,7 @@ fn calculate<'a, 'cfg>(
|
|||||||
path: util::hash_u64(&super::path_args(&cx.bcx, unit).0),
|
path: util::hash_u64(&super::path_args(&cx.bcx, unit).0),
|
||||||
features: format!("{:?}", bcx.resolve.features_sorted(unit.pkg.package_id())),
|
features: format!("{:?}", bcx.resolve.features_sorted(unit.pkg.package_id())),
|
||||||
deps,
|
deps,
|
||||||
local: vec![local],
|
local,
|
||||||
memoized_hash: Mutex::new(None),
|
memoized_hash: Mutex::new(None),
|
||||||
edition: unit.target.edition(),
|
edition: unit.target.edition(),
|
||||||
rustflags: extra_flags,
|
rustflags: extra_flags,
|
||||||
@ -595,19 +619,13 @@ fn build_script_local_fingerprints<'a, 'cfg>(
|
|||||||
cx: &mut Context<'a, 'cfg>,
|
cx: &mut Context<'a, 'cfg>,
|
||||||
unit: &Unit<'a>,
|
unit: &Unit<'a>,
|
||||||
) -> CargoResult<(Vec<LocalFingerprint>, Option<PathBuf>)> {
|
) -> CargoResult<(Vec<LocalFingerprint>, Option<PathBuf>)> {
|
||||||
let state = cx.build_state.outputs.lock().unwrap();
|
|
||||||
// First up, if this build script is entirely overridden, then we just
|
// First up, if this build script is entirely overridden, then we just
|
||||||
// return the hash of what we overrode it with.
|
// return the hash of what we overrode it with.
|
||||||
//
|
if let Some(fingerprint) = build_script_override_fingerprint(cx, unit) {
|
||||||
|
debug!("override local fingerprints deps");
|
||||||
// Note that the `None` here means that we don't want to update the local
|
// Note that the `None` here means that we don't want to update the local
|
||||||
// fingerprint afterwards because this is all just overridden.
|
// fingerprint afterwards because this is all just overridden.
|
||||||
if let Some(output) = state.get(&(unit.pkg.package_id(), unit.kind)) {
|
return Ok((vec![fingerprint], None));
|
||||||
debug!("override local fingerprints deps");
|
|
||||||
let s = format!(
|
|
||||||
"overridden build state with hash: {}",
|
|
||||||
util::hash_u64(output)
|
|
||||||
);
|
|
||||||
return Ok((vec![LocalFingerprint::Precalculated(s)], None));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next up we look at the previously listed dependencies for the build
|
// Next up we look at the previously listed dependencies for the build
|
||||||
@ -633,6 +651,24 @@ fn build_script_local_fingerprints<'a, 'cfg>(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a local fingerprint for an overridden build script.
|
||||||
|
/// Returns None if it is not overridden.
|
||||||
|
fn build_script_override_fingerprint<'a, 'cfg>(
|
||||||
|
cx: &mut Context<'a, 'cfg>,
|
||||||
|
unit: &Unit<'a>,
|
||||||
|
) -> Option<LocalFingerprint> {
|
||||||
|
let state = cx.build_state.outputs.lock().unwrap();
|
||||||
|
state
|
||||||
|
.get(&(unit.pkg.package_id(), unit.kind))
|
||||||
|
.map(|output| {
|
||||||
|
let s = format!(
|
||||||
|
"overridden build state with hash: {}",
|
||||||
|
util::hash_u64(output)
|
||||||
|
);
|
||||||
|
LocalFingerprint::Precalculated(s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn local_fingerprints_deps(
|
fn local_fingerprints_deps(
|
||||||
deps: &BuildDeps,
|
deps: &BuildDeps,
|
||||||
target_root: &Path,
|
target_root: &Path,
|
||||||
|
@ -239,6 +239,7 @@ fn rustc<'a, 'cfg>(
|
|||||||
.get_cwd()
|
.get_cwd()
|
||||||
.unwrap_or_else(|| cx.bcx.config.cwd())
|
.unwrap_or_else(|| cx.bcx.config.cwd())
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
|
let fingerprint_dir = cx.files().fingerprint_dir(unit);
|
||||||
|
|
||||||
return Ok(Work::new(move |state| {
|
return Ok(Work::new(move |state| {
|
||||||
// Only at runtime have we discovered what the extra -L and -l
|
// Only at runtime have we discovered what the extra -L and -l
|
||||||
@ -289,7 +290,7 @@ fn rustc<'a, 'cfg>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.running(&rustc);
|
state.running(&rustc);
|
||||||
let timestamp = paths::get_current_filesystem_time(&dep_info_loc)?;
|
let timestamp = paths::set_invocation_time(&fingerprint_dir)?;
|
||||||
if json_messages {
|
if json_messages {
|
||||||
exec.exec_json(
|
exec.exec_json(
|
||||||
rustc,
|
rustc,
|
||||||
|
@ -187,16 +187,17 @@ pub fn mtime(path: &Path) -> CargoResult<FileTime> {
|
|||||||
Ok(FileTime::from_last_modification_time(&meta))
|
Ok(FileTime::from_last_modification_time(&meta))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get `FileTime::from_system_time(SystemTime::now());` using the exact clock that this file system is using.
|
/// Record the current time on the filesystem (using the filesystem's clock)
|
||||||
pub fn get_current_filesystem_time(path: &Path) -> CargoResult<FileTime> {
|
/// using a file at the given directory. Returns the current time.
|
||||||
|
pub fn set_invocation_time(path: &Path) -> CargoResult<FileTime> {
|
||||||
// note that if `FileTime::from_system_time(SystemTime::now());` is determined to be sufficient,
|
// note that if `FileTime::from_system_time(SystemTime::now());` is determined to be sufficient,
|
||||||
// then this can be removed.
|
// then this can be removed.
|
||||||
let timestamp = path.with_file_name("invoked.timestamp");
|
let timestamp = path.join("invoked.timestamp");
|
||||||
write(
|
write(
|
||||||
×tamp,
|
×tamp,
|
||||||
b"This file has an mtime of when this was started.",
|
b"This file has an mtime of when this was started.",
|
||||||
)?;
|
)?;
|
||||||
Ok(mtime(×tamp)?)
|
mtime(×tamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -1616,3 +1616,91 @@ fn rebuild_on_mid_build_file_modification() {
|
|||||||
|
|
||||||
t.join().ok().unwrap();
|
t.join().ok().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dirty_both_lib_and_test() {
|
||||||
|
// This tests that all artifacts that depend on the results of a build
|
||||||
|
// script will get rebuilt when the build script reruns, even for separate
|
||||||
|
// commands. It does the following:
|
||||||
|
//
|
||||||
|
// 1. Project "foo" has a build script which will compile a small
|
||||||
|
// staticlib to link against. Normally this would use the `cc` crate,
|
||||||
|
// but here we just use rustc to avoid the `cc` dependency.
|
||||||
|
// 2. Build the library.
|
||||||
|
// 3. Build the unit test. The staticlib intentionally has a bad value.
|
||||||
|
// 4. Rewrite the staticlib with the correct value.
|
||||||
|
// 5. Build the library again.
|
||||||
|
// 6. Build the unit test. This should recompile.
|
||||||
|
|
||||||
|
let slib = |n| {
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn doit() -> i32 {{
|
||||||
|
return {};
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
n
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let p = project()
|
||||||
|
.file(
|
||||||
|
"src/lib.rs",
|
||||||
|
r#"
|
||||||
|
extern "C" {
|
||||||
|
fn doit() -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn t1() {
|
||||||
|
assert_eq!(unsafe { doit() }, 1, "doit assert failure");
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.file(
|
||||||
|
"build.rs",
|
||||||
|
r#"
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let rustc = env::var_os("RUSTC").unwrap();
|
||||||
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
|
assert!(
|
||||||
|
Command::new(rustc)
|
||||||
|
.args(&[
|
||||||
|
"--crate-type=staticlib",
|
||||||
|
"--out-dir",
|
||||||
|
out_dir.to_str().unwrap(),
|
||||||
|
"slib.rs"
|
||||||
|
])
|
||||||
|
.status()
|
||||||
|
.unwrap()
|
||||||
|
.success(),
|
||||||
|
"slib build failed"
|
||||||
|
);
|
||||||
|
println!("cargo:rustc-link-lib=slib");
|
||||||
|
println!("cargo:rustc-link-search={}", out_dir.display());
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.file("slib.rs", &slib(2))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
p.cargo("build").run();
|
||||||
|
|
||||||
|
// 2 != 1
|
||||||
|
p.cargo("test --lib")
|
||||||
|
.with_status(101)
|
||||||
|
.with_stdout_contains("[..]doit assert failure[..]")
|
||||||
|
.run();
|
||||||
|
|
||||||
|
// Fix the mistake.
|
||||||
|
p.change_file("slib.rs", &slib(1));
|
||||||
|
|
||||||
|
p.cargo("build").run();
|
||||||
|
// This should recompile with the new static lib, and the test should pass.
|
||||||
|
p.cargo("test --lib").run();
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user