cargo/core/compiler/fingerprint/
dirty_reason.rs
1use std::fmt;
2use std::fmt::Debug;
3
4use super::*;
5use crate::core::Shell;
6
7#[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 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 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 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 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}