blob: bdd2052c04e660d87d01ccc01ae482fc140c9c03 [file] [log] [blame]
use std::fmt;
use std::fmt::Debug;
use super::*;
use crate::core::Shell;
/// Tells a better story of why a build is considered "dirty" that leads
/// to a recompile. Usually constructed via [`Fingerprint::compare`].
///
/// [`Fingerprint::compare`]: super::Fingerprint::compare
#[derive(Clone, Debug)]
pub enum DirtyReason {
RustcChanged,
FeaturesChanged {
old: String,
new: String,
},
DeclaredFeaturesChanged {
old: String,
new: String,
},
TargetConfigurationChanged,
PathToSourceChanged,
ProfileConfigurationChanged,
RustflagsChanged {
old: Vec<String>,
new: Vec<String>,
},
MetadataChanged,
ConfigSettingsChanged,
CompileKindChanged,
LocalLengthsChanged,
PrecalculatedComponentsChanged {
old: String,
new: String,
},
DepInfoOutputChanged {
old: PathBuf,
new: PathBuf,
},
RerunIfChangedOutputFileChanged {
old: PathBuf,
new: PathBuf,
},
RerunIfChangedOutputPathsChanged {
old: Vec<PathBuf>,
new: Vec<PathBuf>,
},
EnvVarsChanged {
old: String,
new: String,
},
EnvVarChanged {
name: String,
old_value: Option<String>,
new_value: Option<String>,
},
LocalFingerprintTypeChanged {
old: &'static str,
new: &'static str,
},
NumberOfDependenciesChanged {
old: usize,
new: usize,
},
UnitDependencyNameChanged {
old: InternedString,
new: InternedString,
},
UnitDependencyInfoChanged {
old_name: InternedString,
old_fingerprint: u64,
new_name: InternedString,
new_fingerprint: u64,
},
FsStatusOutdated(FsStatus),
NothingObvious,
Forced,
}
trait ShellExt {
fn dirty_because(&mut self, unit: &Unit, s: impl fmt::Display) -> CargoResult<()>;
}
impl ShellExt for Shell {
fn dirty_because(&mut self, unit: &Unit, s: impl fmt::Display) -> CargoResult<()> {
self.status("Dirty", format_args!("{}: {s}", &unit.pkg))
}
}
struct FileTimeDiff {
old_time: FileTime,
new_time: FileTime,
}
impl fmt::Display for FileTimeDiff {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s_diff = self.new_time.seconds() - self.old_time.seconds();
if s_diff >= 1 {
fmt::Display::fmt(
&humantime::Duration::from(std::time::Duration::from_secs(s_diff as u64)),
f,
)
} else {
// format nanoseconds as it is, humantime would display ms, us and ns
let ns_diff = self.new_time.nanoseconds() - self.old_time.nanoseconds();
write!(f, "{ns_diff}ns")
}
}
}
#[derive(Copy, Clone)]
struct After {
old_time: FileTime,
new_time: FileTime,
what: &'static str,
}
impl fmt::Display for After {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
old_time,
new_time,
what,
} = *self;
let diff = FileTimeDiff { old_time, new_time };
write!(f, "{new_time}, {diff} after {what} at {old_time}")
}
}
impl DirtyReason {
fn after(old_time: FileTime, new_time: FileTime, what: &'static str) -> After {
After {
old_time,
new_time,
what,
}
}
pub fn present_to(&self, s: &mut Shell, unit: &Unit, root: &Path) -> CargoResult<()> {
match self {
DirtyReason::RustcChanged => s.dirty_because(unit, "the toolchain changed"),
DirtyReason::FeaturesChanged { .. } => {
s.dirty_because(unit, "the list of features changed")
}
DirtyReason::DeclaredFeaturesChanged { .. } => {
s.dirty_because(unit, "the list of declared features changed")
}
DirtyReason::TargetConfigurationChanged => {
s.dirty_because(unit, "the target configuration changed")
}
DirtyReason::PathToSourceChanged => {
s.dirty_because(unit, "the path to the source changed")
}
DirtyReason::ProfileConfigurationChanged => {
s.dirty_because(unit, "the profile configuration changed")
}
DirtyReason::RustflagsChanged { .. } => s.dirty_because(unit, "the rustflags changed"),
DirtyReason::MetadataChanged => s.dirty_because(unit, "the metadata changed"),
DirtyReason::ConfigSettingsChanged => {
s.dirty_because(unit, "the config settings changed")
}
DirtyReason::CompileKindChanged => {
s.dirty_because(unit, "the rustc compile kind changed")
}
DirtyReason::LocalLengthsChanged => {
s.dirty_because(unit, "the local lengths changed")?;
s.note(
"This could happen because of added/removed `cargo::rerun-if` instructions in the build script",
)?;
Ok(())
}
DirtyReason::PrecalculatedComponentsChanged { .. } => {
s.dirty_because(unit, "the precalculated components changed")
}
DirtyReason::DepInfoOutputChanged { .. } => {
s.dirty_because(unit, "the dependency info output changed")
}
DirtyReason::RerunIfChangedOutputFileChanged { .. } => {
s.dirty_because(unit, "rerun-if-changed output file path changed")
}
DirtyReason::RerunIfChangedOutputPathsChanged { .. } => {
s.dirty_because(unit, "the rerun-if-changed instructions changed")
}
DirtyReason::EnvVarsChanged { .. } => {
s.dirty_because(unit, "the environment variables changed")
}
DirtyReason::EnvVarChanged { name, .. } => {
s.dirty_because(unit, format_args!("the env variable {name} changed"))
}
DirtyReason::LocalFingerprintTypeChanged { .. } => {
s.dirty_because(unit, "the local fingerprint type changed")
}
DirtyReason::NumberOfDependenciesChanged { old, new } => s.dirty_because(
unit,
format_args!("number of dependencies changed ({old} => {new})",),
),
DirtyReason::UnitDependencyNameChanged { old, new } => s.dirty_because(
unit,
format_args!("name of dependency changed ({old} => {new})"),
),
DirtyReason::UnitDependencyInfoChanged { .. } => {
s.dirty_because(unit, "dependency info changed")
}
DirtyReason::FsStatusOutdated(status) => match status {
FsStatus::Stale => s.dirty_because(unit, "stale, unknown reason"),
FsStatus::StaleItem(item) => match item {
StaleItem::MissingFile(missing_file) => {
let file = missing_file.strip_prefix(root).unwrap_or(&missing_file);
s.dirty_because(
unit,
format_args!("the file `{}` is missing", file.display()),
)
}
StaleItem::ChangedFile {
stale,
stale_mtime,
reference_mtime,
..
} => {
let file = stale.strip_prefix(root).unwrap_or(&stale);
let after = Self::after(*reference_mtime, *stale_mtime, "last build");
s.dirty_because(
unit,
format_args!("the file `{}` has changed ({after})", file.display()),
)
}
StaleItem::ChangedEnv { var, .. } => s.dirty_because(
unit,
format_args!("the environment variable {var} changed"),
),
},
FsStatus::StaleDependency {
name,
dep_mtime,
max_mtime,
..
} => {
let after = Self::after(*max_mtime, *dep_mtime, "last build");
s.dirty_because(
unit,
format_args!("the dependency {name} was rebuilt ({after})"),
)
}
FsStatus::StaleDepFingerprint { name } => {
s.dirty_because(unit, format_args!("the dependency {name} was rebuilt"))
}
FsStatus::UpToDate { .. } => {
unreachable!()
}
},
DirtyReason::NothingObvious => {
// See comment in fingerprint compare method.
s.dirty_because(unit, "the fingerprint comparison turned up nothing obvious")
}
DirtyReason::Forced => s.dirty_because(unit, "forced"),
}
}
}