mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +00:00
Fix fingerprint for canceled build script.
This commit is contained in:
parent
ed858daaf7
commit
c057b8769b
@ -65,6 +65,7 @@ pub struct BuildScripts {
|
||||
pub plugins: BTreeSet<PackageId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuildDeps {
|
||||
pub build_script_output: PathBuf,
|
||||
pub rerun_if_changed: Vec<PathBuf>,
|
||||
|
@ -1,3 +1,166 @@
|
||||
//! # Fingerprints
|
||||
//!
|
||||
//! This module implements change-tracking so that Cargo can know whether or
|
||||
//! not something needs to be recompiled. A Cargo `Unit` can be either "dirty"
|
||||
//! (needs to be recompiled) or "fresh" (it does not need to be recompiled).
|
||||
//! There are several mechanisms that influence a Unit's freshness:
|
||||
//!
|
||||
//! - The `Metadata` hash isolates each Unit on the filesystem by being
|
||||
//! embedded in the filename. If something in the hash changes, then the
|
||||
//! output files will be missing, and the Unit will be dirty (missing
|
||||
//! outputs are considered "dirty").
|
||||
//! - The `Fingerprint` is another hash, saved to the filesystem in the
|
||||
//! `.fingerprint` directory, that tracks information about the inputs to a
|
||||
//! Unit. If any of the inputs changes from the last compilation, then the
|
||||
//! Unit is considered dirty. A missing fingerprint (such as during the
|
||||
//! first build) is also considered dirty.
|
||||
//! - Dirty propagation is done in the `JobQueue`. When a Unit is dirty, the
|
||||
//! `JobQueue` automatically treats anything that depends on it as dirty.
|
||||
//! Anything that relies on this is probably a bug. The fingerprint should
|
||||
//! always be complete (but there are some known limitations). This is a
|
||||
//! problem because not all Units are built all at once. If two separate
|
||||
//! `cargo` commands are run that build different Units, this dirty
|
||||
//! propagation will not work across commands.
|
||||
//!
|
||||
//! Note: Fingerprinting is not a perfect solution. Filesystem mtime tracking
|
||||
//! is notoriously imprecise and problematic. Only a small part of the
|
||||
//! environment is captured. This is a balance of performance, simplicity, and
|
||||
//! completeness. Sandboxing, hashing file contents, tracking every file
|
||||
//! access, environment variable, and network operation would ensure more
|
||||
//! reliable and reproducible builds at the cost of being complex, slow, and
|
||||
//! platform-dependent.
|
||||
//!
|
||||
//! ## Fingerprints and Metadata
|
||||
//!
|
||||
//! Fingerprints and Metadata are similar, and track some of the same things.
|
||||
//! The Metadata contains information that is required to keep Units separate.
|
||||
//! The Fingerprint includes additional information that should cause a
|
||||
//! recompile, but it is desired to reuse the same filenames. Generally the
|
||||
//! items in the Metadata do not need to be in the Fingerprint. A comparison
|
||||
//! of what is tracked:
|
||||
//!
|
||||
//! Value | Fingerprint | Metadata
|
||||
//! -------------------------------------------|-------------|----------
|
||||
//! rustc | ✓ | ✓
|
||||
//! Profile | ✓ | ✓
|
||||
//! `cargo rustc` extra args | ✓ | ✓
|
||||
//! CompileMode | ✓ | ✓
|
||||
//! Target Name | ✓ | ✓
|
||||
//! Target Kind (bin/lib/etc.) | ✓ | ✓
|
||||
//! Enabled Features | ✓ | ✓
|
||||
//! Immediate dependency’s hashes | ✓[^1] | ✓
|
||||
//! Target or Host mode | | ✓
|
||||
//! __CARGO_DEFAULT_LIB_METADATA[^4] | | ✓
|
||||
//! authors, description, homepage | | ✓[^2]
|
||||
//! package_id | | ✓
|
||||
//! Target src path | ✓ |
|
||||
//! Target path relative to ws | ✓ |
|
||||
//! Target flags (test/bench/for_host/edition) | ✓ |
|
||||
//! Edition | ✓ |
|
||||
//! -C incremental=… flag | ✓ |
|
||||
//! mtime of sources | ✓[^3] |
|
||||
//! RUSTFLAGS/RUSTDOCFLAGS | ✓ |
|
||||
//!
|
||||
//! [^1]: Build script and bin dependencies are not included.
|
||||
//!
|
||||
//! [^2]: This is a bug, see https://github.com/rust-lang/cargo/issues/6208
|
||||
//!
|
||||
//! [^3]: The mtime is only tracked for workspace members and path
|
||||
//! dependencies. Git dependencies track the git revision.
|
||||
//!
|
||||
//! [^4]: `__CARGO_DEFAULT_LIB_METADATA` is set by rustbuild to embed the
|
||||
//! release channel (bootstrap/stable/beta/nightly) in libstd.
|
||||
//!
|
||||
//! ## Fingerprint files
|
||||
//!
|
||||
//! Fingerprint information is stored in the
|
||||
//! `target/{debug,release}/.fingerprint/` directory. Each Unit is stored in a
|
||||
//! separate directory. Each Unit directory contains:
|
||||
//!
|
||||
//! - A file with a 16 hex-digit hash. This is the Fingerprint hash, used for
|
||||
//! quick loading and comparison.
|
||||
//! - A `.json` file that contains details about the Fingerprint. This is only
|
||||
//! used to log details about *why* a fingerprint is considered dirty.
|
||||
//! `RUST_LOG=cargo::core::compiler::fingerprint=trace cargo build` can be
|
||||
//! used to display this log information.
|
||||
//! - A "dep-info" file which contains a list of source filenames for the
|
||||
//! target. This is produced by `rustc`'s `--emit=dep-info` flag. Cargo uses
|
||||
//! this to check the mtime of every file to see if any of them have
|
||||
//! changed.
|
||||
//! - An `invoked.timestamp` file whose filesystem mtime is updated every time
|
||||
//! the Unit is built. This is an experimental feature used for cleaning
|
||||
//! unused artifacts.
|
||||
//!
|
||||
//! Note that some units are a little different. A Unit for *running* a build
|
||||
//! script or for `rustdoc` does not have a dep-info file (it's not
|
||||
//! applicable). Build script `invoked.timestamp` files are in the build
|
||||
//! output directory.
|
||||
//!
|
||||
//! ## Fingerprint calculation
|
||||
//!
|
||||
//! After the list of Units has been calculated, the Units are added to the
|
||||
//! `JobQueue`. As each one is added, the fingerprint is calculated, and the
|
||||
//! dirty/fresh status is recorded in the `JobQueue`. A closure is used to
|
||||
//! update the fingerprint on-disk when the Unit successfully finishes. The
|
||||
//! closure will recompute the Fingerprint based on the updated information.
|
||||
//! If the Unit fails to compile, the fingerprint is not updated.
|
||||
//!
|
||||
//! Fingerprints are cached in the `Context`. This makes computing
|
||||
//! Fingerprints faster, but also is necessary for properly updating
|
||||
//! dependency information. Since a Fingerprint includes the Fingerprints of
|
||||
//! all dependencies, when it is updated, by using `Arc` clones, it
|
||||
//! automatically picks up the updates to its dependencies.
|
||||
//!
|
||||
//! ## Build scripts
|
||||
//!
|
||||
//! The *running* of a build script (`CompileMode::RunCustomBuild`) is treated
|
||||
//! significantly different than all other Unit kinds. It has its own function
|
||||
//! for calculating the Fingerprint (`prepare_build_cmd`) and has some unique
|
||||
//! considerations. It does not track the same information as a normal Unit.
|
||||
//! The information tracked depends on the `rerun-if-changed` and
|
||||
//! `rerun-if-env-changed` statements produced by the build script. If the
|
||||
//! script does not emit either of these statements, the Fingerprint runs in
|
||||
//! "old style" mode where an mtime change of *any* file in the package will
|
||||
//! cause the build script to be re-run. Otherwise, the fingerprint *only*
|
||||
//! tracks the individual "rerun-if" items listed by the build script.
|
||||
//!
|
||||
//! The "rerun-if" statements from a *previous* build are stored in the build
|
||||
//! output directory in a file called `output`. Cargo parses this file when
|
||||
//! the Unit for that build script is prepared for the `JobQueue`. The
|
||||
//! Fingerprint code can then use that information to compute the Fingerprint
|
||||
//! and compare against the old fingerprint hash.
|
||||
//!
|
||||
//! Care must be taken with build script Fingerprints because the
|
||||
//! `Fingerprint::local` value may be changed after the build script runs
|
||||
//! (such as if the build script adds or removes "rerun-if" items).
|
||||
//!
|
||||
//! Another complication is if a build script is overridden. In that case, the
|
||||
//! fingerprint is the hash of the output of the override.
|
||||
//!
|
||||
//! ## Special considerations
|
||||
//!
|
||||
//! Registry dependencies do not track the mtime of files. This is because
|
||||
//! registry dependencies are not expected to change (if a new version is
|
||||
//! used, the Package ID will change, causing a rebuild). Cargo currently
|
||||
//! partially works with Docker caching. When a Docker image is built, it has
|
||||
//! normal mtime information. However, when a step is cached, the nanosecond
|
||||
//! portions of all files is zeroed out. Currently this works, but care must
|
||||
//! be taken for situations like these.
|
||||
//!
|
||||
//! HFS on macOS only supports 1 second timestamps. This causes a significant
|
||||
//! number of problems, particularly with Cargo's testsuite which does rapid
|
||||
//! builds in succession. Other filesystems have various degrees of
|
||||
//! resolution.
|
||||
//!
|
||||
//! Various weird filesystems (such as network filesystems) also can cause
|
||||
//! complications. Network filesystems may track the time on the server
|
||||
//! (except when the time is set manually such as with
|
||||
//! `filetime::set_file_times`). Not all filesystems support modifying the
|
||||
//! mtime.
|
||||
//!
|
||||
//! See the `A-rebuild-detection` flag on the issue tracker for more:
|
||||
//! <https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AA-rebuild-detection>
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::hash::{self, Hasher};
|
||||
@ -5,6 +168,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use failure::bail;
|
||||
use filetime::FileTime;
|
||||
use log::{debug, info};
|
||||
use serde::de;
|
||||
@ -213,7 +377,7 @@ impl<'de> Deserialize<'de> for DepFingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash)]
|
||||
#[derive(Debug, Serialize, Deserialize, Hash)]
|
||||
enum LocalFingerprint {
|
||||
Precalculated(String),
|
||||
MtimeBased(MtimeSlot, PathBuf),
|
||||
@ -229,6 +393,7 @@ impl LocalFingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MtimeSlot(Mutex<Option<FileTime>>);
|
||||
|
||||
impl Fingerprint {
|
||||
@ -274,32 +439,32 @@ impl Fingerprint {
|
||||
|
||||
fn compare(&self, old: &Fingerprint) -> CargoResult<()> {
|
||||
if self.rustc != old.rustc {
|
||||
failure::bail!("rust compiler has changed")
|
||||
bail!("rust compiler has changed")
|
||||
}
|
||||
if self.features != old.features {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"features have changed: {} != {}",
|
||||
self.features,
|
||||
old.features
|
||||
)
|
||||
}
|
||||
if self.target != old.target {
|
||||
failure::bail!("target configuration has changed")
|
||||
bail!("target configuration has changed")
|
||||
}
|
||||
if self.path != old.path {
|
||||
failure::bail!("path to the compiler has changed")
|
||||
bail!("path to the compiler has changed")
|
||||
}
|
||||
if self.profile != old.profile {
|
||||
failure::bail!("profile configuration has changed")
|
||||
bail!("profile configuration has changed")
|
||||
}
|
||||
if self.rustflags != old.rustflags {
|
||||
failure::bail!("RUSTFLAGS has changed")
|
||||
bail!("RUSTFLAGS has changed")
|
||||
}
|
||||
if self.local.len() != old.local.len() {
|
||||
failure::bail!("local lens changed");
|
||||
bail!("local lens changed");
|
||||
}
|
||||
if self.edition != old.edition {
|
||||
failure::bail!("edition changed")
|
||||
bail!("edition changed")
|
||||
}
|
||||
for (new, old) in self.local.iter().zip(&old.local) {
|
||||
match (new, old) {
|
||||
@ -308,7 +473,7 @@ impl Fingerprint {
|
||||
&LocalFingerprint::Precalculated(ref b),
|
||||
) => {
|
||||
if a != b {
|
||||
failure::bail!("precalculated components have changed: {} != {}", a, b)
|
||||
bail!("precalculated components have changed: {} != {}", a, b)
|
||||
}
|
||||
}
|
||||
(
|
||||
@ -325,7 +490,7 @@ impl Fingerprint {
|
||||
};
|
||||
|
||||
if should_rebuild {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"mtime based components have changed: previously {:?} now {:?}, \
|
||||
paths are {:?} and {:?}",
|
||||
*previously_built_mtime,
|
||||
@ -340,10 +505,10 @@ impl Fingerprint {
|
||||
&LocalFingerprint::EnvBased(ref bkey, ref bvalue),
|
||||
) => {
|
||||
if *akey != *bkey {
|
||||
failure::bail!("env vars changed: {} != {}", akey, bkey);
|
||||
bail!("env vars changed: {} != {}", akey, bkey);
|
||||
}
|
||||
if *avalue != *bvalue {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"env var `{}` changed: previously {:?} now {:?}",
|
||||
akey,
|
||||
bvalue,
|
||||
@ -351,18 +516,22 @@ impl Fingerprint {
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => failure::bail!("local fingerprint type has changed"),
|
||||
_ => bail!("local fingerprint type has changed"),
|
||||
}
|
||||
}
|
||||
|
||||
if self.deps.len() != old.deps.len() {
|
||||
failure::bail!("number of dependencies has changed")
|
||||
bail!("number of dependencies has changed")
|
||||
}
|
||||
for (a, b) in self.deps.iter().zip(old.deps.iter()) {
|
||||
if a.name != b.name || a.fingerprint.hash() != b.fingerprint.hash() {
|
||||
failure::bail!("new ({}) != old ({})", a.pkg_id, b.pkg_id)
|
||||
bail!("new ({}) != old ({})", a.pkg_id, b.pkg_id)
|
||||
}
|
||||
}
|
||||
// Two fingerprints may have different hash values, but still succeed
|
||||
// in this compare function if the difference is due to a
|
||||
// LocalFingerprint value that changes in a compatible way. For
|
||||
// example, moving the mtime of a file backwards in time,
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -441,9 +610,10 @@ impl<'de> de::Deserialize<'de> for MtimeSlot {
|
||||
/// * The compiler changes
|
||||
/// * The set of features a package is built with changes
|
||||
/// * The profile a target is compiled with changes (e.g., opt-level changes)
|
||||
/// * Any other compiler flags change that will affect the result
|
||||
///
|
||||
/// Information like file modification time is only calculated for path
|
||||
/// dependencies and is calculated in `calculate_target_fresh`.
|
||||
/// dependencies.
|
||||
fn calculate<'a, 'cfg>(
|
||||
cx: &mut Context<'a, 'cfg>,
|
||||
unit: &Unit<'a>,
|
||||
@ -495,29 +665,7 @@ fn calculate<'a, 'cfg>(
|
||||
// and you run two separate commands (`build` then `test`), the second
|
||||
// command will erroneously think it is fresh.
|
||||
// See: https://github.com/rust-lang/cargo/issues/6733
|
||||
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)
|
||||
})
|
||||
}),
|
||||
);
|
||||
local.extend(local_fingerprint_run_custom_build_deps(cx, unit));
|
||||
local
|
||||
} else {
|
||||
let fingerprint = pkg_fingerprint(&cx.bcx, unit.pkg)?;
|
||||
@ -559,21 +707,17 @@ fn use_dep_info(unit: &Unit<'_>) -> bool {
|
||||
|
||||
/// Prepare the necessary work for the fingerprint of a build command.
|
||||
///
|
||||
/// Build commands are located on packages, not on targets. Additionally, we
|
||||
/// don't have --dep-info to drive calculation of the fingerprint of a build
|
||||
/// command. This brings up an interesting predicament which gives us a few
|
||||
/// options to figure out whether a build command is dirty or not:
|
||||
/// The fingerprint for the execution of a build script can be in one of two
|
||||
/// modes:
|
||||
///
|
||||
/// 1. A build command is dirty if *any* file in a package changes. In theory
|
||||
/// all files are candidate for being used by the build command.
|
||||
/// 2. A build command is dirty if any file in a *specific directory* changes.
|
||||
/// This may lose information as it may require files outside of the specific
|
||||
/// directory.
|
||||
/// 3. A build command must itself provide a dep-info-like file stating how it
|
||||
/// should be considered dirty or not.
|
||||
/// - "old style": The fingerprint tracks the mtimes for all files in the
|
||||
/// package.
|
||||
/// - "new style": If the build script emits a "rerun-if" statement, then
|
||||
/// Cargo only tracks the files an environment variables explicitly listed
|
||||
/// by the script.
|
||||
///
|
||||
/// The currently implemented solution is option (1), although it is planned to
|
||||
/// migrate to option (2) in the near future.
|
||||
/// Overridden build scripts are special; only the simulated output is
|
||||
/// tracked.
|
||||
pub fn prepare_build_cmd<'a, 'cfg>(
|
||||
cx: &mut Context<'a, 'cfg>,
|
||||
unit: &Unit<'a>,
|
||||
@ -585,9 +729,44 @@ pub fn prepare_build_cmd<'a, 'cfg>(
|
||||
debug!("fingerprint at: {}", loc.display());
|
||||
|
||||
let (local, output_path) = build_script_local_fingerprints(cx, unit)?;
|
||||
|
||||
// Include the compilation of the build script itself in the fingerprint.
|
||||
// If the build script is rebuilt, then it definitely needs to be run
|
||||
// again. This should only find 1 dependency (for the build script) or 0
|
||||
// (if it is overridden).
|
||||
//
|
||||
// Note that this filters out `RunCustomBuild` units. These are `links`
|
||||
// build scripts. Unfortunately, for many reasons, those would be very
|
||||
// difficult to include, so for now this is slightly wrong. Reasons:
|
||||
// Fingerprint::locals has to be rebuilt in the closure, LocalFingerprint
|
||||
// isn't cloneable, Context is required to recompute them, build script
|
||||
// fingerprints aren't shared in Context::fingerprints, etc.
|
||||
// Ideally this would call local_fingerprint_run_custom_build_deps.
|
||||
// See https://github.com/rust-lang/cargo/issues/6780
|
||||
let deps = if output_path.is_none() {
|
||||
// Overridden build scripts don't need to track deps.
|
||||
vec![]
|
||||
} else {
|
||||
cx.dep_targets(unit)
|
||||
.iter()
|
||||
.filter(|u| !u.mode.is_run_custom_build())
|
||||
.map(|dep| {
|
||||
calculate(cx, dep).and_then(|fingerprint| {
|
||||
let name = cx.bcx.extern_crate_name(unit, dep)?;
|
||||
Ok(DepFingerprint {
|
||||
pkg_id: dep.pkg.package_id().to_string(),
|
||||
name,
|
||||
fingerprint,
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<CargoResult<Vec<_>>>()?
|
||||
};
|
||||
|
||||
let mut fingerprint = Fingerprint {
|
||||
local,
|
||||
rustc: util::hash_u64(&cx.bcx.rustc.verbose_version),
|
||||
deps,
|
||||
..Fingerprint::new()
|
||||
};
|
||||
let mtime_on_use = cx.bcx.config.cli_unstable().mtime_on_use;
|
||||
@ -617,6 +796,10 @@ pub fn prepare_build_cmd<'a, 'cfg>(
|
||||
fingerprint.local = local_fingerprints_deps(&deps, &target_root, &pkg_root);
|
||||
fingerprint.update_local(&target_root)?;
|
||||
}
|
||||
// Note: If a build script switches from new style to old style,
|
||||
// this is bugged. It should recompute Fingerprint::local, but
|
||||
// requires access to Context which we don't have here.
|
||||
// See https://github.com/rust-lang/cargo/issues/6779
|
||||
}
|
||||
write_fingerprint(&loc, &fingerprint)
|
||||
});
|
||||
@ -628,6 +811,10 @@ pub fn prepare_build_cmd<'a, 'cfg>(
|
||||
))
|
||||
}
|
||||
|
||||
/// Compute the `LocalFingerprint` values for a `RunCustomBuild` unit.
|
||||
///
|
||||
/// The second element of the return value is the path to the build script
|
||||
/// `output` file. This is `None` for overridden build scripts.
|
||||
fn build_script_local_fingerprints<'a, 'cfg>(
|
||||
cx: &mut Context<'a, 'cfg>,
|
||||
unit: &Unit<'a>,
|
||||
@ -664,7 +851,7 @@ fn build_script_local_fingerprints<'a, 'cfg>(
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a local fingerprint for an overridden build script.
|
||||
/// Create a `LocalFingerprint` for an overridden build script.
|
||||
/// Returns None if it is not overridden.
|
||||
fn build_script_override_fingerprint<'a, 'cfg>(
|
||||
cx: &mut Context<'a, 'cfg>,
|
||||
@ -682,6 +869,8 @@ fn build_script_override_fingerprint<'a, 'cfg>(
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute the `LocalFingerprint` values for a `RunCustomBuild` unit for
|
||||
/// non-overridden new-style build scripts only.
|
||||
fn local_fingerprints_deps(
|
||||
deps: &BuildDeps,
|
||||
target_root: &Path,
|
||||
@ -704,6 +893,41 @@ fn local_fingerprints_deps(
|
||||
local
|
||||
}
|
||||
|
||||
/// Compute `LocalFingerprint` values for the `RunCustomBuild` dependencies of
|
||||
/// the given unit.
|
||||
fn local_fingerprint_run_custom_build_deps<'a, 'cfg>(
|
||||
cx: &mut Context<'a, 'cfg>,
|
||||
unit: &Unit<'a>,
|
||||
) -> Vec<LocalFingerprint> {
|
||||
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`).
|
||||
//
|
||||
// I suspect there is some edge case where this is incorrect,
|
||||
// because the invoked timestamp is updated even if the build
|
||||
// script fails to finish. However, I can't find any examples
|
||||
// where it doesn't work.
|
||||
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)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn write_fingerprint(loc: &Path, fingerprint: &Fingerprint) -> CargoResult<()> {
|
||||
debug_assert_ne!(fingerprint.rustc, 0);
|
||||
// fingerprint::new().rustc == 0, make sure it doesn't make it to the file system.
|
||||
|
@ -1,11 +1,12 @@
|
||||
use filetime::FileTime;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::prelude::*;
|
||||
use std::net::TcpListener;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::support::paths::CargoPathExt;
|
||||
use crate::support::paths::{self, CargoPathExt};
|
||||
use crate::support::registry::Package;
|
||||
use crate::support::sleep_ms;
|
||||
use crate::support::{basic_manifest, is_coarse_mtime, project};
|
||||
@ -1526,7 +1527,7 @@ fn rebuild_on_mid_build_file_modification() {
|
||||
.file(
|
||||
"root/Cargo.toml",
|
||||
r#"
|
||||
[project]
|
||||
[package]
|
||||
name = "root"
|
||||
version = "0.1.0"
|
||||
authors = []
|
||||
@ -1548,7 +1549,7 @@ fn rebuild_on_mid_build_file_modification() {
|
||||
.file(
|
||||
"proc_macro_dep/Cargo.toml",
|
||||
r#"
|
||||
[project]
|
||||
[package]
|
||||
name = "proc_macro_dep"
|
||||
version = "0.1.0"
|
||||
authors = []
|
||||
@ -1708,3 +1709,179 @@ fn dirty_both_lib_and_test() {
|
||||
// This should recompile with the new static lib, and the test should pass.
|
||||
p.cargo("test --lib").run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_fails_stay_dirty() {
|
||||
// Check if a script is aborted (such as hitting Ctrl-C) that it will re-run.
|
||||
// Steps:
|
||||
// 1. Build to establish fingerprints.
|
||||
// 2. Make a change that triggers the build script to re-run. Abort the
|
||||
// script while it is running.
|
||||
// 3. Run the build again and make sure it re-runs the script.
|
||||
let p = project()
|
||||
.file(
|
||||
"build.rs",
|
||||
r#"
|
||||
mod helper;
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
helper::doit();
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.file("helper.rs", "pub fn doit() {}")
|
||||
.file("src/lib.rs", "")
|
||||
.build();
|
||||
|
||||
p.cargo("build").run();
|
||||
if is_coarse_mtime() {
|
||||
sleep_ms(1000);
|
||||
}
|
||||
p.change_file("helper.rs", r#"pub fn doit() {panic!("Crash!");}"#);
|
||||
p.cargo("build")
|
||||
.with_stderr_contains("[..]Crash![..]")
|
||||
.with_status(101)
|
||||
.run();
|
||||
// There was a bug where this second call would be "fresh".
|
||||
p.cargo("build")
|
||||
.with_stderr_contains("[..]Crash![..]")
|
||||
.with_status(101)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simulated_docker_deps_stay_cached() {
|
||||
// Test what happens in docker where the nanoseconds are zeroed out.
|
||||
Package::new("regdep", "1.0.0").publish();
|
||||
Package::new("regdep_old_style", "1.0.0")
|
||||
.file("build.rs", "fn main() {}")
|
||||
.file("src/lib.rs", "")
|
||||
.publish();
|
||||
Package::new("regdep_env", "1.0.0")
|
||||
.file(
|
||||
"build.rs",
|
||||
r#"
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-env-changed=SOMEVAR");
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.file("src/lib.rs", "")
|
||||
.publish();
|
||||
Package::new("regdep_rerun", "1.0.0")
|
||||
.file(
|
||||
"build.rs",
|
||||
r#"
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.file("src/lib.rs", "")
|
||||
.publish();
|
||||
|
||||
let p = project()
|
||||
.file(
|
||||
"Cargo.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
pathdep = { path = "pathdep" }
|
||||
regdep = "1.0"
|
||||
regdep_old_style = "1.0"
|
||||
regdep_env = "1.0"
|
||||
regdep_rerun = "1.0"
|
||||
"#,
|
||||
)
|
||||
.file(
|
||||
"src/lib.rs",
|
||||
"
|
||||
extern crate pathdep;
|
||||
extern crate regdep;
|
||||
extern crate regdep_old_style;
|
||||
extern crate regdep_env;
|
||||
extern crate regdep_rerun;
|
||||
",
|
||||
)
|
||||
.file("build.rs", "fn main() {}")
|
||||
.file("pathdep/Cargo.toml", &basic_manifest("pathdep", "1.0.0"))
|
||||
.file("pathdep/src/lib.rs", "")
|
||||
.build();
|
||||
|
||||
p.cargo("build").run();
|
||||
|
||||
let already_zero = {
|
||||
// This happens on HFS with 1-second timestamp resolution,
|
||||
// or other filesystems where it just so happens to write exactly on a
|
||||
// 1-second boundary.
|
||||
let metadata = fs::metadata(p.root().join("src/lib.rs")).unwrap();
|
||||
let mtime = FileTime::from_last_modification_time(&metadata);
|
||||
mtime.nanoseconds() == 0
|
||||
};
|
||||
|
||||
// Recursively remove `nanoseconds` from every path.
|
||||
fn zeropath(path: &Path) {
|
||||
for entry in walkdir::WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
{
|
||||
let metadata = fs::metadata(entry.path()).unwrap();
|
||||
let mtime = metadata.modified().unwrap();
|
||||
let mtime_duration = mtime.duration_since(SystemTime::UNIX_EPOCH).unwrap();
|
||||
let trunc_mtime = FileTime::from_unix_time(mtime_duration.as_secs() as i64, 0);
|
||||
let atime = metadata.accessed().unwrap();
|
||||
let atime_duration = atime.duration_since(SystemTime::UNIX_EPOCH).unwrap();
|
||||
let trunc_atime = FileTime::from_unix_time(atime_duration.as_secs() as i64, 0);
|
||||
if let Err(e) = filetime::set_file_times(entry.path(), trunc_atime, trunc_mtime) {
|
||||
// Windows doesn't allow changing filetimes on some things
|
||||
// (directories, other random things I'm not sure why). Just
|
||||
// ignore them.
|
||||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
println!("PermissionDenied filetime on {:?}", entry.path());
|
||||
} else {
|
||||
panic!("FileTime error on {:?}: {:?}", entry.path(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
zeropath(&p.root());
|
||||
zeropath(&paths::home());
|
||||
|
||||
if already_zero {
|
||||
// If it was already truncated, then everything stays fresh.
|
||||
p.cargo("build -v")
|
||||
.with_stderr_unordered(
|
||||
"\
|
||||
[FRESH] pathdep [..]
|
||||
[FRESH] regdep [..]
|
||||
[FRESH] regdep_env [..]
|
||||
[FRESH] regdep_old_style [..]
|
||||
[FRESH] regdep_rerun [..]
|
||||
[FRESH] foo [..]
|
||||
[FINISHED] [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
} else {
|
||||
// It is not ideal that `foo` gets recompiled, but that is the current
|
||||
// behavior. Currently mtimes are ignored for registry deps.
|
||||
p.cargo("build -v")
|
||||
.with_stderr_unordered(
|
||||
"\
|
||||
[FRESH] pathdep [..]
|
||||
[FRESH] regdep [..]
|
||||
[FRESH] regdep_env [..]
|
||||
[FRESH] regdep_old_style [..]
|
||||
[FRESH] regdep_rerun [..]
|
||||
[COMPILING] foo [..]
|
||||
[RUNNING] [..]/foo-[..]/build-script-build[..]
|
||||
[RUNNING] `rustc --crate-name foo[..]
|
||||
[FINISHED] [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user