Purge mtime information from Fingerprint

This has proven to be a very unreliable piece of information to hash, so
let's not! Instead we track what files are supposed to be relative to,
and we check both mtimes when necessary.
This commit is contained in:
Alex Crichton 2019-04-09 11:37:31 -07:00
parent e9428cbadd
commit 8df842f594
7 changed files with 269 additions and 171 deletions

View File

@ -166,8 +166,13 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
} }
} }
/// Returns the root of the build output tree. /// Returns the root of the build output tree for the target
pub fn target_root(&self) -> &Path { pub fn target_root(&self) -> &Path {
self.target.as_ref().unwrap_or(&self.host).dest()
}
/// Returns the root of the build output tree for the host
pub fn host_root(&self) -> &Path {
self.host.dest() self.host.dest()
} }

View File

@ -235,7 +235,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
let output_file = script_run_dir.join("output"); let output_file = script_run_dir.join("output");
let err_file = script_run_dir.join("stderr"); let err_file = script_run_dir.join("stderr");
let root_output_file = script_run_dir.join("root-output"); let root_output_file = script_run_dir.join("root-output");
let host_target_root = cx.files().target_root().to_path_buf(); let host_target_root = cx.files().host_root().to_path_buf();
let all = ( let all = (
id, id,
pkg_name.clone(), pkg_name.clone(),

View File

@ -183,7 +183,7 @@ use super::job::{
Freshness::{Dirty, Fresh}, Freshness::{Dirty, Fresh},
Job, Work, Job, Work,
}; };
use super::{BuildContext, Context, FileFlavor, Unit}; use super::{BuildContext, Context, FileFlavor, Kind, Unit};
/// Determines if a `unit` is up-to-date, and if not prepares necessary work to /// Determines if a `unit` is up-to-date, and if not prepares necessary work to
/// update the persisted fingerprint. /// update the persisted fingerprint.
@ -242,49 +242,12 @@ pub fn prepare_target<'a, 'cfg>(
source.verify(unit.pkg.package_id())?; source.verify(unit.pkg.package_id())?;
} }
// If the comparison succeeded but we're missing outputs, we might be if compare.is_ok() && !force {
// dealing with manual changes in the target directory or various kinds of
// manual edits. Try to handle these gracefully by rebuilding the unit.
let missing_outputs = {
let t = FileTime::from_system_time(SystemTime::now());
if unit.mode.is_doc() {
!cx.files()
.out_dir(unit)
.join(unit.target.crate_name())
.join("index.html")
.exists()
} else if unit.mode.is_run_custom_build() {
false
} else {
match cx
.outputs(unit)?
.iter()
.filter(|output| output.flavor != FileFlavor::DebugInfo)
.find(|output| {
if output.path.exists() {
if mtime_on_use {
// update the mtime so other cleaners know we used it
let _ = filetime::set_file_times(&output.path, t, t);
}
false
} else {
true
}
}) {
None => false,
Some(output) => {
info!("missing output path {:?}", output.path);
true
}
}
}
};
if compare.is_ok() && !missing_outputs && !force {
return Ok(Job::new(Work::noop(), Fresh)); return Ok(Job::new(Work::noop(), Fresh));
} }
let allow_failure = bcx.extra_args_for(unit).is_some(); let pkg_root = unit.pkg.root().to_path_buf();
let target_root = cx.files().target_root().to_path_buf(); let target_root = target_root(cx, unit);
let write_fingerprint = if unit.mode.is_run_custom_build() { let write_fingerprint = if unit.mode.is_run_custom_build() {
// For build scripts the `local` field of the fingerprint may change // For build scripts the `local` field of the fingerprint may change
// while we're executing it. For example it could be in the legacy // while we're executing it. For example it could be in the legacy
@ -312,7 +275,8 @@ pub fn prepare_target<'a, 'cfg>(
// `new_fingerprint`. This means usages of `fingerprint` in // `new_fingerprint`. This means usages of `fingerprint` in
// various dependencies should work correctly because the hash // various dependencies should work correctly because the hash
// is still memoized to the correct value. // is still memoized to the correct value.
let new_fingerprint = fingerprint.with_local(new_local); let mut new_fingerprint = fingerprint.with_local(new_local);
new_fingerprint.check_filesystem(&pkg_root, &target_root, false)?;
*fingerprint.memoized_hash.lock().unwrap() = Some(new_fingerprint.hash()); *fingerprint.memoized_hash.lock().unwrap() = Some(new_fingerprint.hash());
write_fingerprint(&loc, &new_fingerprint) write_fingerprint(&loc, &new_fingerprint)
} else { } else {
@ -321,19 +285,11 @@ pub fn prepare_target<'a, 'cfg>(
// `build_script_local_fingerprints` below for more // `build_script_local_fingerprints` below for more
// information. Despite this just try to proceed and hobble // information. Despite this just try to proceed and hobble
// along. // along.
fingerprint.update_local(&target_root)?;
write_fingerprint(&loc, &fingerprint) write_fingerprint(&loc, &fingerprint)
} }
}) })
} else { } else {
Work::new(move |_| { Work::new(move |_| write_fingerprint(&loc, &*fingerprint))
match fingerprint.update_local(&target_root) {
Ok(()) => {}
Err(..) if allow_failure => return Ok(()),
Err(e) => return Err(e),
}
write_fingerprint(&loc, &*fingerprint)
})
}; };
Ok(Job::new(write_fingerprint, Dirty)) Ok(Job::new(write_fingerprint, Dirty))
@ -401,10 +357,36 @@ pub struct Fingerprint {
/// "description", which are exposed as environment variables during /// "description", which are exposed as environment variables during
/// compilation. /// compilation.
metadata: u64, metadata: u64,
/// Whether any of the `local` inputs are missing files, or if any of our /// Whether any of the `local` inputs, on the filesystem, are considered out
/// dependencies have missing input files. /// of date by looking at mtimes.
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
inputs_missing: bool, fs_status: FsStatus,
/// A file, relative to `target_root`, that is produced by the step that
/// this `Fingerprint` represents. This is used to detect when the whole
/// fingerprint is out of date if this is missing, or if previous
/// fingerprints output files are regenerated and look newer than this one.
#[serde(skip_serializing, skip_deserializing)]
outputs: Vec<PathBuf>,
}
enum FsStatus {
Stale,
UpToDate(Option<FileTime>),
}
impl FsStatus {
fn up_to_date(&self) -> bool {
match self {
FsStatus::UpToDate(_) => true,
_ => false,
}
}
}
impl Default for FsStatus {
fn default() -> FsStatus {
FsStatus::Stale
}
} }
impl Serialize for DepFingerprint { impl Serialize for DepFingerprint {
@ -437,30 +419,54 @@ impl<'de> Deserialize<'de> for DepFingerprint {
#[derive(Debug, Serialize, Deserialize, Hash)] #[derive(Debug, Serialize, Deserialize, Hash)]
enum LocalFingerprint { enum LocalFingerprint {
Precalculated(String), Precalculated(String),
MtimeBased(MtimeSlot, PathBuf), CheckDepInfo {
EnvBased(String, Option<String>), dep_info: PathBuf,
},
RerunIfChanged {
output: PathBuf,
paths: Vec<PathBuf>,
},
RerunIfEnvChanged {
var: String,
val: Option<String>,
},
}
enum StaleFile {
Missing(PathBuf),
Changed {
reference: PathBuf,
reference_mtime: FileTime,
stale: PathBuf,
stale_mtime: FileTime,
},
} }
impl LocalFingerprint { impl LocalFingerprint {
fn mtime(root: &Path, mtime: Option<FileTime>, path: &Path) -> LocalFingerprint { fn find_stale_file(
let mtime = MtimeSlot(Mutex::new(mtime)); &self,
assert!(path.is_absolute()); pkg_root: &Path,
let path = path.strip_prefix(root).unwrap_or(path); target_root: &Path,
LocalFingerprint::MtimeBased(mtime, path.to_path_buf()) ) -> CargoResult<Option<StaleFile>> {
}
fn missing(&self) -> bool {
match self { match self {
LocalFingerprint::MtimeBased(slot, _) => slot.0.lock().unwrap().is_none(), LocalFingerprint::CheckDepInfo { dep_info } => {
_ => false, find_stale_file_from_dep_info(pkg_root, &target_root.join(dep_info))
}
LocalFingerprint::RerunIfChanged { output, paths } => Ok(find_stale_file(
&target_root.join(output),
paths.iter().map(|p| pkg_root.join(p)),
)),
LocalFingerprint::RerunIfEnvChanged { .. } => Ok(None),
LocalFingerprint::Precalculated(..) => Ok(None),
} }
} }
fn kind(&self) -> &'static str { fn kind(&self) -> &'static str {
match self { match self {
LocalFingerprint::Precalculated(..) => "precalculated", LocalFingerprint::Precalculated(..) => "precalculated",
LocalFingerprint::MtimeBased(..) => "mtime-based", LocalFingerprint::CheckDepInfo { .. } => "dep-info",
LocalFingerprint::EnvBased(..) => "env-based", LocalFingerprint::RerunIfChanged { .. } => "rerun-if-changed",
LocalFingerprint::RerunIfEnvChanged { .. } => "rerun-if-env-changed",
} }
} }
} }
@ -481,14 +487,14 @@ impl Fingerprint {
memoized_hash: Mutex::new(None), memoized_hash: Mutex::new(None),
rustflags: Vec::new(), rustflags: Vec::new(),
metadata: 0, metadata: 0,
inputs_missing: false, fs_status: FsStatus::Stale,
outputs: Vec::new(),
} }
} }
fn with_local(&self, local: Vec<LocalFingerprint>) -> Fingerprint { fn with_local(&self, local: Vec<LocalFingerprint>) -> Fingerprint {
Fingerprint { Fingerprint {
inputs_missing: local.iter().any(|l| l.missing()) fs_status: FsStatus::Stale,
|| self.deps.iter().any(|d| d.fingerprint.inputs_missing),
rustc: self.rustc, rustc: self.rustc,
target: self.target, target: self.target,
profile: self.profile, profile: self.profile,
@ -499,25 +505,10 @@ impl Fingerprint {
memoized_hash: Mutex::new(None), memoized_hash: Mutex::new(None),
rustflags: self.rustflags.clone(), rustflags: self.rustflags.clone(),
metadata: self.metadata, metadata: self.metadata,
outputs: self.outputs.clone(),
} }
} }
fn update_local(&self, root: &Path) -> CargoResult<()> {
for local in self.local.iter() {
match *local {
LocalFingerprint::MtimeBased(ref slot, ref path) => {
let path = root.join(path);
let mtime = paths::mtime(&path)?;
*slot.0.lock().unwrap() = Some(mtime);
}
LocalFingerprint::EnvBased(..) | LocalFingerprint::Precalculated(..) => continue,
}
}
*self.memoized_hash.lock().unwrap() = None;
Ok(())
}
fn hash(&self) -> u64 { fn hash(&self) -> u64 {
if let Some(s) = *self.memoized_hash.lock().unwrap() { if let Some(s) = *self.memoized_hash.lock().unwrap() {
return s; return s;
@ -558,41 +549,49 @@ impl Fingerprint {
} }
for (new, old) in self.local.iter().zip(&old.local) { for (new, old) in self.local.iter().zip(&old.local) {
match (new, old) { match (new, old) {
( (LocalFingerprint::Precalculated(a), LocalFingerprint::Precalculated(b)) => {
&LocalFingerprint::Precalculated(ref a),
&LocalFingerprint::Precalculated(ref b),
) => {
if a != b { if a != b {
bail!("precalculated components have changed: {} != {}", a, b) bail!("precalculated components have changed: {} != {}", a, b)
} }
} }
( (
&LocalFingerprint::MtimeBased(ref on_disk_mtime, ref ap), LocalFingerprint::CheckDepInfo { dep_info: adep },
&LocalFingerprint::MtimeBased(ref previously_built_mtime, ref bp), LocalFingerprint::CheckDepInfo { dep_info: bdep },
) => { ) => {
let on_disk_mtime = on_disk_mtime.0.lock().unwrap(); if adep != bdep {
let previously_built_mtime = previously_built_mtime.0.lock().unwrap(); bail!("dep info output changed: {:?} != {:?}", adep, bdep)
}
let should_rebuild = match (*on_disk_mtime, *previously_built_mtime) { }
(None, None) => true, (
(Some(_), None) | (None, Some(_)) => true, LocalFingerprint::RerunIfChanged {
(Some(on_disk), Some(previously_built)) => on_disk > previously_built, output: aout,
}; paths: apaths,
},
if should_rebuild { LocalFingerprint::RerunIfChanged {
output: bout,
paths: bpaths,
},
) => {
if aout != bout {
bail!("rerun-if-changed output changed: {:?} != {:?}", aout, bout)
}
if apaths != bpaths {
bail!( bail!(
"mtime based components have changed: previously {:?} now {:?}, \ "rerun-if-changed output changed: {:?} != {:?}",
paths are {:?} and {:?}", apaths,
*previously_built_mtime, bpaths,
*on_disk_mtime,
ap,
bp
) )
} }
} }
( (
&LocalFingerprint::EnvBased(ref akey, ref avalue), LocalFingerprint::RerunIfEnvChanged {
&LocalFingerprint::EnvBased(ref bkey, ref bvalue), var: akey,
val: avalue,
},
LocalFingerprint::RerunIfEnvChanged {
var: bkey,
val: bvalue,
},
) => { ) => {
if *akey != *bkey { if *akey != *bkey {
bail!("env vars changed: {} != {}", akey, bkey); bail!("env vars changed: {} != {}", akey, bkey);
@ -637,19 +636,57 @@ impl Fingerprint {
} }
} }
if self.inputs_missing { if !self.fs_status.up_to_date() {
bail!("some inputs are missing"); bail!("current filesystem status shows we're outdated");
}
if old.inputs_missing {
bail!("some inputs were missing");
} }
debug!("two fingerprint comparison turned up nothing obvious"); bail!("two fingerprint comparison turned up nothing obvious");
}
// Two fingerprints may have different hash values, but still succeed fn check_filesystem(
// in this compare function if the difference is due to a &mut self,
// LocalFingerprint value that changes in a compatible way. For pkg_root: &Path,
// example, moving the mtime of a file backwards in time, target_root: &Path,
mtime_on_use: bool,
) -> CargoResult<()> {
let status = self
.outputs
.iter()
.map(|f| {
let mtime = paths::mtime(f).ok();
if mtime_on_use {
let t = FileTime::from_system_time(SystemTime::now());
drop(filetime::set_file_times(f, t, t));
}
return mtime;
})
.min();
let mtime = match status {
Some(Some(mtime)) => mtime,
Some(None) => return Ok(()),
None => {
self.fs_status = FsStatus::UpToDate(None);
return Ok(());
}
};
for dep in self.deps.iter() {
let dep_mtime = match dep.fingerprint.fs_status {
FsStatus::UpToDate(Some(mtime)) => mtime,
FsStatus::UpToDate(None) => continue,
FsStatus::Stale => return Ok(()),
};
if dep_mtime > mtime {
return Ok(());
}
}
for local in self.local.iter() {
if let Some(file) = local.find_stale_file(pkg_root, target_root)? {
file.log();
return Ok(());
}
}
self.fs_status = FsStatus::UpToDate(Some(mtime));
Ok(()) Ok(())
} }
} }
@ -752,6 +789,26 @@ impl DepFingerprint {
} }
} }
impl StaleFile {
fn log(&self) {
match self {
StaleFile::Missing(path) => {
log::info!("stale: missing {:?}", path);
}
StaleFile::Changed {
reference,
reference_mtime,
stale,
stale_mtime,
} => {
log::info!("stale: changed {:?}", stale);
log::info!(" (vs) {:?}", reference);
log::info!(" {:?} != {:?}", reference_mtime, stale_mtime);
}
}
}
}
/// Calculates the fingerprint for a `unit`. /// Calculates the fingerprint for a `unit`.
/// ///
/// This fingerprint is used by Cargo to learn about when information such as: /// This fingerprint is used by Cargo to learn about when information such as:
@ -773,11 +830,14 @@ fn calculate<'a, 'cfg>(
if let Some(s) = cx.fingerprints.get(unit) { if let Some(s) = cx.fingerprints.get(unit) {
return Ok(Arc::clone(s)); return Ok(Arc::clone(s));
} }
let fingerprint = if unit.mode.is_run_custom_build() { let mut fingerprint = if unit.mode.is_run_custom_build() {
calculate_run_custom_build(cx, unit)? calculate_run_custom_build(cx, unit)?
} else { } else {
calculate_normal(cx, unit)? calculate_normal(cx, unit)?
}; };
let target_root = target_root(cx, unit);
let mtime_on_use = cx.bcx.config.cli_unstable().mtime_on_use;
fingerprint.check_filesystem(unit.pkg.root(), &target_root, mtime_on_use)?;
let fingerprint = Arc::new(fingerprint); let fingerprint = Arc::new(fingerprint);
cx.fingerprints.insert(*unit, Arc::clone(&fingerprint)); cx.fingerprints.insert(*unit, Arc::clone(&fingerprint));
Ok(fingerprint) Ok(fingerprint)
@ -807,19 +867,32 @@ fn calculate_normal<'a, 'cfg>(
// correctly, but otherwise upstream packages like from crates.io or git // correctly, but otherwise upstream packages like from crates.io or git
// get bland fingerprints because they don't change without their // get bland fingerprints because they don't change without their
// `PackageId` changing. // `PackageId` changing.
let target_root = target_root(cx, unit);
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 dep_info = dep_info.strip_prefix(&target_root).unwrap().to_path_buf();
vec![LocalFingerprint::mtime( vec![LocalFingerprint::CheckDepInfo { dep_info }]
cx.files().target_root(),
mtime,
&dep_info,
)]
} else { } else {
let fingerprint = pkg_fingerprint(cx.bcx, unit.pkg)?; let fingerprint = pkg_fingerprint(cx.bcx, unit.pkg)?;
vec![LocalFingerprint::Precalculated(fingerprint)] vec![LocalFingerprint::Precalculated(fingerprint)]
}; };
// Figure out what the outputs of our unit is, and we'll be storing them
// into the fingerprint as well.
let outputs = if unit.mode.is_doc() {
vec![cx
.files()
.out_dir(unit)
.join(unit.target.crate_name())
.join("index.html")]
} else {
cx.outputs(unit)?
.iter()
.filter(|output| output.flavor != FileFlavor::DebugInfo)
.map(|output| output.path.clone())
.collect()
};
// Fill out a bunch more information that we'll be tracking typically // Fill out a bunch more information that we'll be tracking typically
// hashed to take up less space on disk as we just need to know when things // hashed to take up less space on disk as we just need to know when things
// change. // change.
@ -833,8 +906,6 @@ fn calculate_normal<'a, 'cfg>(
let m = unit.pkg.manifest().metadata(); let m = unit.pkg.manifest().metadata();
let metadata = util::hash_u64((&m.authors, &m.description, &m.homepage, &m.repository)); let metadata = util::hash_u64((&m.authors, &m.description, &m.homepage, &m.repository));
Ok(Fingerprint { Ok(Fingerprint {
inputs_missing: deps.iter().any(|d| d.fingerprint.inputs_missing)
|| local.iter().any(|l| l.missing()),
rustc: util::hash_u64(&cx.bcx.rustc.verbose_version), rustc: util::hash_u64(&cx.bcx.rustc.verbose_version),
target: util::hash_u64(&unit.target), target: util::hash_u64(&unit.target),
profile: profile_hash, profile: profile_hash,
@ -850,6 +921,8 @@ fn calculate_normal<'a, 'cfg>(
memoized_hash: Mutex::new(None), memoized_hash: Mutex::new(None),
metadata, metadata,
rustflags: extra_flags, rustflags: extra_flags,
fs_status: FsStatus::Stale,
outputs,
}) })
} }
@ -879,6 +952,7 @@ fn calculate_run_custom_build<'a, 'cfg>(
let local = gen_local let local = gen_local
.call_box(deps, Some(&|| pkg_fingerprint(cx.bcx, unit.pkg)))? .call_box(deps, Some(&|| pkg_fingerprint(cx.bcx, unit.pkg)))?
.unwrap(); .unwrap();
let output = deps.build_script_output.clone();
// Include any dependencies of our execution, which is typically just the // Include any dependencies of our execution, which is typically just the
// compilation of the build script itself. (if the build script changes we // compilation of the build script itself. (if the build script changes we
@ -895,11 +969,10 @@ fn calculate_run_custom_build<'a, 'cfg>(
}; };
Ok(Fingerprint { Ok(Fingerprint {
inputs_missing: deps.iter().any(|d| d.fingerprint.inputs_missing)
|| local.iter().any(|l| l.missing()),
local, local,
rustc: util::hash_u64(&cx.bcx.rustc.verbose_version), rustc: util::hash_u64(&cx.bcx.rustc.verbose_version),
deps, deps,
outputs: if overridden { Vec::new() } else { vec![output] },
// Most of the other info is blank here as we don't really include it // Most of the other info is blank here as we don't really include it
// in the execution of the build script, but... this may be a latent // in the execution of the build script, but... this may be a latent
@ -1020,15 +1093,25 @@ fn local_fingerprints_deps(
let mut local = Vec::new(); let mut local = Vec::new();
if !deps.rerun_if_changed.is_empty() { if !deps.rerun_if_changed.is_empty() {
let output = &deps.build_script_output; let output = deps
let deps = deps.rerun_if_changed.iter().map(|p| pkg_root.join(p)); .build_script_output
let mtime = mtime_if_fresh(output, deps); .strip_prefix(target_root)
local.push(LocalFingerprint::mtime(target_root, mtime, output)); .unwrap()
.to_path_buf();
let paths = deps
.rerun_if_changed
.iter()
.map(|p| p.strip_prefix(pkg_root).unwrap_or(p).to_path_buf())
.collect();
local.push(LocalFingerprint::RerunIfChanged { output, paths });
} }
for var in deps.rerun_if_env_changed.iter() { for var in deps.rerun_if_env_changed.iter() {
let val = env::var(var).ok(); let val = env::var(var).ok();
local.push(LocalFingerprint::EnvBased(var.clone(), val)); local.push(LocalFingerprint::RerunIfEnvChanged {
var: var.clone(),
val,
});
} }
local local
@ -1069,6 +1152,16 @@ pub fn dep_info_loc<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> Pa
.join(&format!("dep-{}", filename(cx, unit))) .join(&format!("dep-{}", filename(cx, unit)))
} }
pub fn target_root<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> PathBuf {
if unit.mode.is_run_custom_build() {
cx.files().build_script_run_dir(unit)
} else if unit.kind == Kind::Host {
cx.files().host_root().to_path_buf()
} else {
cx.files().target_root().to_path_buf()
}
}
fn compare_old_fingerprint( fn compare_old_fingerprint(
loc: &Path, loc: &Path,
new_fingerprint: &Fingerprint, new_fingerprint: &Fingerprint,
@ -1084,7 +1177,7 @@ fn compare_old_fingerprint(
let new_hash = new_fingerprint.hash(); let new_hash = new_fingerprint.hash();
if util::to_hex(new_hash) == old_fingerprint_short && !new_fingerprint.inputs_missing { if util::to_hex(new_hash) == old_fingerprint_short && new_fingerprint.fs_status.up_to_date() {
return Ok(()); return Ok(());
} }
@ -1112,7 +1205,7 @@ fn log_compare(unit: &Unit<'_>, compare: &CargoResult<()>) {
} }
// Parse the dep-info into a list of paths // Parse the dep-info into a list of paths
pub fn parse_dep_info(pkg: &Package, dep_info: &Path) -> CargoResult<Option<Vec<PathBuf>>> { pub fn parse_dep_info(pkg_root: &Path, dep_info: &Path) -> CargoResult<Option<Vec<PathBuf>>> {
let data = match paths::read_bytes(dep_info) { let data = match paths::read_bytes(dep_info) {
Ok(data) => data, Ok(data) => data,
Err(_) => return Ok(None), Err(_) => return Ok(None),
@ -1120,7 +1213,7 @@ pub fn parse_dep_info(pkg: &Package, dep_info: &Path) -> CargoResult<Option<Vec<
let paths = data let paths = data
.split(|&x| x == 0) .split(|&x| x == 0)
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|p| util::bytes2path(p).map(|p| pkg.root().join(p))) .map(|p| util::bytes2path(p).map(|p| pkg_root.join(p)))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
if paths.is_empty() { if paths.is_empty() {
Ok(None) Ok(None)
@ -1129,11 +1222,14 @@ pub fn parse_dep_info(pkg: &Package, dep_info: &Path) -> CargoResult<Option<Vec<
} }
} }
fn dep_info_mtime_if_fresh(pkg: &Package, dep_info: &Path) -> CargoResult<Option<FileTime>> { fn find_stale_file_from_dep_info(
if let Some(paths) = parse_dep_info(pkg, dep_info)? { pkg_root: &Path,
Ok(mtime_if_fresh(dep_info, paths.iter())) dep_info: &Path,
) -> CargoResult<Option<StaleFile>> {
if let Some(paths) = parse_dep_info(pkg_root, dep_info)? {
Ok(find_stale_file(dep_info, paths.iter()))
} else { } else {
Ok(None) Ok(Some(StaleFile::Missing(dep_info.to_path_buf())))
} }
} }
@ -1147,24 +1243,21 @@ fn pkg_fingerprint(bcx: &BuildContext<'_, '_>, pkg: &Package) -> CargoResult<Str
source.fingerprint(pkg) source.fingerprint(pkg)
} }
fn mtime_if_fresh<I>(output: &Path, paths: I) -> Option<FileTime> fn find_stale_file<I>(reference: &Path, paths: I) -> Option<StaleFile>
where where
I: IntoIterator, I: IntoIterator,
I::Item: AsRef<Path>, I::Item: AsRef<Path>,
{ {
let mtime = match paths::mtime(output) { let reference_mtime = match paths::mtime(reference) {
Ok(mtime) => mtime, Ok(mtime) => mtime,
Err(..) => return None, Err(..) => return Some(StaleFile::Missing(reference.to_path_buf())),
}; };
let any_stale = paths.into_iter().any(|path| { for path in paths {
let path = path.as_ref(); let path = path.as_ref();
let mtime2 = match paths::mtime(path) { let path_mtime = match paths::mtime(path) {
Ok(mtime) => mtime, Ok(mtime) => mtime,
Err(..) => { Err(..) => return Some(StaleFile::Missing(path.to_path_buf())),
info!("stale: {} -- missing", path.display());
return true;
}
}; };
// TODO: fix #5918. // TODO: fix #5918.
@ -1185,19 +1278,19 @@ where
// if equal, files were changed just after a previous build finished. // if equal, files were changed just after a previous build finished.
// Unfortunately this became problematic when (in #6484) cargo switch to more accurately // Unfortunately this became problematic when (in #6484) cargo switch to more accurately
// measuring the start time of builds. // measuring the start time of builds.
if mtime2 > mtime { if path_mtime <= reference_mtime {
info!("stale: {} -- {} vs {}", path.display(), mtime2, mtime); continue;
true
} else {
false
} }
});
if any_stale { return Some(StaleFile::Changed {
None reference: reference.to_path_buf(),
} else { reference_mtime,
Some(mtime) stale: path.to_path_buf(),
stale_mtime: path_mtime,
});
} }
return None;
} }
fn filename<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> String { fn filename<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> String {

View File

@ -231,7 +231,7 @@ fn rustc<'a, 'cfg>(
exec.init(cx, unit); exec.init(cx, unit);
let exec = exec.clone(); let exec = exec.clone();
let root_output = cx.files().target_root().to_path_buf(); let root_output = cx.files().host_root().to_path_buf();
let pkg_root = unit.pkg.root().to_path_buf(); let pkg_root = unit.pkg.root().to_path_buf();
let cwd = rustc let cwd = rustc
.get_cwd() .get_cwd()

View File

@ -39,7 +39,7 @@ fn add_deps_for_unit<'a, 'b>(
if !unit.mode.is_run_custom_build() { if !unit.mode.is_run_custom_build() {
// Add dependencies from rustc dep-info output (stored in fingerprint directory) // Add dependencies from rustc dep-info output (stored in fingerprint directory)
let dep_info_loc = fingerprint::dep_info_loc(context, unit); let dep_info_loc = fingerprint::dep_info_loc(context, unit);
if let Some(paths) = fingerprint::parse_dep_info(unit.pkg, &dep_info_loc)? { if let Some(paths) = fingerprint::parse_dep_info(unit.pkg.root(), &dep_info_loc)? {
for path in paths { for path in paths {
deps.insert(path); deps.insert(path);
} }

View File

@ -2406,7 +2406,6 @@ fn fresh_builds_possible_with_link_libs() {
.run(); .run();
p.cargo("build -v") p.cargo("build -v")
.env("RUST_LOG", "cargo::ops::cargo_rustc::fingerprint=info")
.with_stderr( .with_stderr(
"\ "\
[FRESH] foo v0.5.0 ([..]) [FRESH] foo v0.5.0 ([..])

View File

@ -1811,7 +1811,10 @@ fn simulated_docker_deps_stay_cached() {
.file("pathdep/src/lib.rs", "") .file("pathdep/src/lib.rs", "")
.build(); .build();
p.cargo("build").run(); p.cargo("build")
.env("RUST_LOG", "cargo::core::compiler::fingerprint")
.stream()
.run();
let already_zero = { let already_zero = {
// This happens on HFS with 1-second timestamp resolution, // This happens on HFS with 1-second timestamp resolution,
@ -1854,7 +1857,6 @@ fn simulated_docker_deps_stay_cached() {
println!("already zero"); println!("already zero");
// If it was already truncated, then everything stays fresh. // If it was already truncated, then everything stays fresh.
p.cargo("build -v") p.cargo("build -v")
.env("RUST_LOG", "cargo::core::compiler::fingerprint")
.with_stderr_unordered( .with_stderr_unordered(
"\ "\
[FRESH] pathdep [..] [FRESH] pathdep [..]
@ -1879,7 +1881,6 @@ fn simulated_docker_deps_stay_cached() {
// in it. It differs between builds because one has nsec=0 and the other // in it. It differs between builds because one has nsec=0 and the other
// likely has a nonzero nsec. Hence, the rebuild. // likely has a nonzero nsec. Hence, the rebuild.
p.cargo("build -v") p.cargo("build -v")
.env("RUST_LOG", "cargo::core::compiler::fingerprint")
.with_stderr_unordered( .with_stderr_unordered(
"\ "\
[FRESH] pathdep [..] [FRESH] pathdep [..]