#
cargo/core/compiler/fingerprint/
dirty_reason.rs

    1use std::fmt;
    2use std::fmt::Debug;
    3
    4use super::*;
    5use crate::core::Shell;
    6
    7/// Tells a better story of why a build is considered "dirty" that leads
    8/// to a recompile. Usually constructed via [`Fingerprint::compare`].
    9///
    10/// [`Fingerprint::compare`]: super::Fingerprint::compare
    11#[derive(Clone, Debug)]
    12pub enum DirtyReason {
    13    RustcChanged,
    14    FeaturesChanged {
    15        old: String,
    16        new: String,
    17    },
    18    DeclaredFeaturesChanged {
    19        old: String,
    20        new: String,
    21    },
    22    TargetConfigurationChanged,
    23    PathToSourceChanged,
    24    ProfileConfigurationChanged,
    25    RustflagsChanged {
    26        old: Vec<String>,
    27        new: Vec<String>,
    28    },
    29    ConfigSettingsChanged,
    30    CompileKindChanged,
    31    LocalLengthsChanged,
    32    PrecalculatedComponentsChanged {
    33        old: String,
    34        new: String,
    35    },
    36    ChecksumUseChanged {
    37        old: bool,
    38    },
    39    DepInfoOutputChanged {
    40        old: PathBuf,
    41        new: PathBuf,
    42    },
    43    RerunIfChangedOutputFileChanged {
    44        old: PathBuf,
    45        new: PathBuf,
    46    },
    47    RerunIfChangedOutputPathsChanged {
    48        old: Vec<PathBuf>,
    49        new: Vec<PathBuf>,
    50    },
    51    EnvVarsChanged {
    52        old: String,
    53        new: String,
    54    },
    55    EnvVarChanged {
    56        name: String,
    57        old_value: Option<String>,
    58        new_value: Option<String>,
    59    },
    60    LocalFingerprintTypeChanged {
    61        old: &'static str,
    62        new: &'static str,
    63    },
    64    NumberOfDependenciesChanged {
    65        old: usize,
    66        new: usize,
    67    },
    68    UnitDependencyNameChanged {
    69        old: InternedString,
    70        new: InternedString,
    71    },
    72    UnitDependencyInfoChanged {
    73        old_name: InternedString,
    74        old_fingerprint: u64,
    75
    76        new_name: InternedString,
    77        new_fingerprint: u64,
    78    },
    79    FsStatusOutdated(FsStatus),
    80    NothingObvious,
    81    Forced,
    82    /// First time to build something.
    83    FreshBuild,
    84}
    85
    86trait ShellExt {
    87    fn dirty_because(&mut self, unit: &Unit, s: impl fmt::Display) -> CargoResult<()>;
    88}
    89
    90impl ShellExt for Shell {
    91    fn dirty_because(&mut self, unit: &Unit, s: impl fmt::Display) -> CargoResult<()> {
    92        self.status("Dirty", format_args!("{}: {s}", &unit.pkg))
    93    }
    94}
    95
    96struct FileTimeDiff {
    97    old_time: FileTime,
    98    new_time: FileTime,
    99}
    100
    101impl fmt::Display for FileTimeDiff {
    102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    103        let s_diff = self.new_time.seconds() - self.old_time.seconds();
    104        if s_diff >= 1 {
    105            write!(f, "{:#}", jiff::SignedDuration::from_secs(s_diff))
    106        } else {
    107            // format nanoseconds as it is, jiff would display ms, us and ns
    108            let ns_diff = self.new_time.nanoseconds() - self.old_time.nanoseconds();
    109            write!(f, "{ns_diff}ns")
    110        }
    111    }
    112}
    113
    114#[derive(Copy, Clone)]
    115struct After {
    116    old_time: FileTime,
    117    new_time: FileTime,
    118    what: &'static str,
    119}
    120
    121impl fmt::Display for After {
    122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    123        let Self {
    124            old_time,
    125            new_time,
    126            what,
    127        } = *self;
    128        let diff = FileTimeDiff { old_time, new_time };
    129
    130        write!(f, "{new_time}, {diff} after {what} at {old_time}")
    131    }
    132}
    133
    134impl DirtyReason {
    135    /// Whether a build is dirty because it is a fresh build being kicked off.
    136    pub fn is_fresh_build(&self) -> bool {
    137        matches!(self, DirtyReason::FreshBuild)
    138    }
    139
    140    fn after(old_time: FileTime, new_time: FileTime, what: &'static str) -> After {
    141        After {
    142            old_time,
    143            new_time,
    144            what,
    145        }
    146    }
    147
    148    pub fn present_to(&self, s: &mut Shell, unit: &Unit, root: &Path) -> CargoResult<()> {
    149        match self {
    150            DirtyReason::RustcChanged => s.dirty_because(unit, "the toolchain changed"),
    151            DirtyReason::FeaturesChanged { .. } => {
    152                s.dirty_because(unit, "the list of features changed")
    153            }
    154            DirtyReason::DeclaredFeaturesChanged { .. } => {
    155                s.dirty_because(unit, "the list of declared features changed")
    156            }
    157            DirtyReason::TargetConfigurationChanged => {
    158                s.dirty_because(unit, "the target configuration changed")
    159            }
    160            DirtyReason::PathToSourceChanged => {
    161                s.dirty_because(unit, "the path to the source changed")
    162            }
    163            DirtyReason::ProfileConfigurationChanged => {
    164                s.dirty_because(unit, "the profile configuration changed")
    165            }
    166            DirtyReason::RustflagsChanged { .. } => s.dirty_because(unit, "the rustflags changed"),
    167            DirtyReason::ConfigSettingsChanged => {
    168                s.dirty_because(unit, "the config settings changed")
    169            }
    170            DirtyReason::CompileKindChanged => {
    171                s.dirty_because(unit, "the rustc compile kind changed")
    172            }
    173            DirtyReason::LocalLengthsChanged => {
    174                s.dirty_because(unit, "the local lengths changed")?;
    175                s.note(
    176                    "this could happen because of added/removed `cargo::rerun-if` instructions in the build script",
    177                )?;
    178
    179                Ok(())
    180            }
    181            DirtyReason::PrecalculatedComponentsChanged { .. } => {
    182                s.dirty_because(unit, "the precalculated components changed")
    183            }
    184            DirtyReason::ChecksumUseChanged { old } => {
    185                if *old {
    186                    s.dirty_because(
    187                        unit,
    188                        "the prior compilation used checksum freshness and this one does not",
    189                    )
    190                } else {
    191                    s.dirty_because(unit, "checksum freshness requested, prior compilation did not use checksum freshness")
    192                }
    193            }
    194            DirtyReason::DepInfoOutputChanged { .. } => {
    195                s.dirty_because(unit, "the dependency info output changed")
    196            }
    197            DirtyReason::RerunIfChangedOutputFileChanged { .. } => {
    198                s.dirty_because(unit, "rerun-if-changed output file path changed")
    199            }
    200            DirtyReason::RerunIfChangedOutputPathsChanged { .. } => {
    201                s.dirty_because(unit, "the rerun-if-changed instructions changed")
    202            }
    203            DirtyReason::EnvVarsChanged { .. } => {
    204                s.dirty_because(unit, "the environment variables changed")
    205            }
    206            DirtyReason::EnvVarChanged { name, .. } => {
    207                s.dirty_because(unit, format_args!("the env variable {name} changed"))
    208            }
    209            DirtyReason::LocalFingerprintTypeChanged { .. } => {
    210                s.dirty_because(unit, "the local fingerprint type changed")
    211            }
    212            DirtyReason::NumberOfDependenciesChanged { old, new } => s.dirty_because(
    213                unit,
    214                format_args!("number of dependencies changed ({old} => {new})",),
    215            ),
    216            DirtyReason::UnitDependencyNameChanged { old, new } => s.dirty_because(
    217                unit,
    218                format_args!("name of dependency changed ({old} => {new})"),
    219            ),
    220            DirtyReason::UnitDependencyInfoChanged { .. } => {
    221                s.dirty_because(unit, "dependency info changed")
    222            }
    223            DirtyReason::FsStatusOutdated(status) => match status {
    224                FsStatus::Stale => s.dirty_because(unit, "stale, unknown reason"),
    225                FsStatus::StaleItem(item) => match item {
    226                    StaleItem::MissingFile(missing_file) => {
    227                        let file = missing_file.strip_prefix(root).unwrap_or(&missing_file);
    228                        s.dirty_because(
    229                            unit,
    230                            format_args!("the file `{}` is missing", file.display()),
    231                        )
    232                    }
    233                    StaleItem::UnableToReadFile(file) => {
    234                        let file = file.strip_prefix(root).unwrap_or(&file);
    235                        s.dirty_because(
    236                            unit,
    237                            format_args!("the file `{}` could not be read", file.display()),
    238                        )
    239                    }
    240                    StaleItem::FailedToReadMetadata(file) => {
    241                        let file = file.strip_prefix(root).unwrap_or(&file);
    242                        s.dirty_because(
    243                            unit,
    244                            format_args!("couldn't read metadata for file `{}`", file.display()),
    245                        )
    246                    }
    247                    StaleItem::ChangedFile {
    248                        stale,
    249                        stale_mtime,
    250                        reference_mtime,
    251                        ..
    252                    } => {
    253                        let file = stale.strip_prefix(root).unwrap_or(&stale);
    254                        let after = Self::after(*reference_mtime, *stale_mtime, "last build");
    255                        s.dirty_because(
    256                            unit,
    257                            format_args!("the file `{}` has changed ({after})", file.display()),
    258                        )
    259                    }
    260                    StaleItem::ChangedChecksum {
    261                        source,
    262                        stored_checksum,
    263                        new_checksum,
    264                    } => {
    265                        let file = source.strip_prefix(root).unwrap_or(&source);
    266                        s.dirty_because(
    267                            unit,
    268                            format_args!(
    269                                "the file `{}` has changed (checksum didn't match, {stored_checksum} != {new_checksum})",
    270                                file.display(),
    271                            ),
    272                        )
    273                    }
    274                    StaleItem::FileSizeChanged {
    275                        path,
    276                        old_size,
    277                        new_size,
    278                    } => {
    279                        let file = path.strip_prefix(root).unwrap_or(&path);
    280                        s.dirty_because(
    281                            unit,
    282                            format_args!(
    283                                "file size changed ({old_size} != {new_size}) for `{}`",
    284                                file.display()
    285                            ),
    286                        )
    287                    }
    288                    StaleItem::MissingChecksum(path) => {
    289                        let file = path.strip_prefix(root).unwrap_or(&path);
    290                        s.dirty_because(
    291                            unit,
    292                            format_args!("the checksum for file `{}` is missing", file.display()),
    293                        )
    294                    }
    295                    StaleItem::ChangedEnv { var, .. } => s.dirty_because(
    296                        unit,
    297                        format_args!("the environment variable {var} changed"),
    298                    ),
    299                },
    300                FsStatus::StaleDependency {
    301                    name,
    302                    dep_mtime,
    303                    max_mtime,
    304                    ..
    305                } => {
    306                    let after = Self::after(*max_mtime, *dep_mtime, "last build");
    307                    s.dirty_because(
    308                        unit,
    309                        format_args!("the dependency {name} was rebuilt ({after})"),
    310                    )
    311                }
    312                FsStatus::StaleDepFingerprint { name } => {
    313                    s.dirty_because(unit, format_args!("the dependency {name} was rebuilt"))
    314                }
    315                FsStatus::UpToDate { .. } => {
    316                    unreachable!()
    317                }
    318            },
    319            DirtyReason::NothingObvious => {
    320                // See comment in fingerprint compare method.
    321                s.dirty_because(unit, "the fingerprint comparison turned up nothing obvious")
    322            }
    323            DirtyReason::Forced => s.dirty_because(unit, "forced"),
    324            DirtyReason::FreshBuild => s.dirty_because(unit, "fresh build"),
    325        }
    326    }
    327}