Fix fingerprint for canceled build script.

This commit is contained in:
Eric Huss 2019-03-23 15:58:11 -07:00
parent ed858daaf7
commit c057b8769b
3 changed files with 460 additions and 58 deletions

View File

@ -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>,

View File

@ -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 dependencys 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.

View File

@ -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();
}
}