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 fmt::Display::fmt(
106 &humantime::Duration::from(std::time::Duration::from_secs(s_diff as u64)),
107 f,
108 )
109 } else {
110 let ns_diff = self.new_time.nanoseconds() - self.old_time.nanoseconds();
112 write!(f, "{ns_diff}ns")
113 }
114 }
115}
116
117#[derive(Copy, Clone)]
118struct After {
119 old_time: FileTime,
120 new_time: FileTime,
121 what: &'static str,
122}
123
124impl fmt::Display for After {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 let Self {
127 old_time,
128 new_time,
129 what,
130 } = *self;
131 let diff = FileTimeDiff { old_time, new_time };
132
133 write!(f, "{new_time}, {diff} after {what} at {old_time}")
134 }
135}
136
137impl DirtyReason {
138 pub fn is_fresh_build(&self) -> bool {
140 matches!(self, DirtyReason::FreshBuild)
141 }
142
143 fn after(old_time: FileTime, new_time: FileTime, what: &'static str) -> After {
144 After {
145 old_time,
146 new_time,
147 what,
148 }
149 }
150
151 pub fn present_to(&self, s: &mut Shell, unit: &Unit, root: &Path) -> CargoResult<()> {
152 match self {
153 DirtyReason::RustcChanged => s.dirty_because(unit, "the toolchain changed"),
154 DirtyReason::FeaturesChanged { .. } => {
155 s.dirty_because(unit, "the list of features changed")
156 }
157 DirtyReason::DeclaredFeaturesChanged { .. } => {
158 s.dirty_because(unit, "the list of declared features changed")
159 }
160 DirtyReason::TargetConfigurationChanged => {
161 s.dirty_because(unit, "the target configuration changed")
162 }
163 DirtyReason::PathToSourceChanged => {
164 s.dirty_because(unit, "the path to the source changed")
165 }
166 DirtyReason::ProfileConfigurationChanged => {
167 s.dirty_because(unit, "the profile configuration changed")
168 }
169 DirtyReason::RustflagsChanged { .. } => s.dirty_because(unit, "the rustflags changed"),
170 DirtyReason::ConfigSettingsChanged => {
171 s.dirty_because(unit, "the config settings changed")
172 }
173 DirtyReason::CompileKindChanged => {
174 s.dirty_because(unit, "the rustc compile kind changed")
175 }
176 DirtyReason::LocalLengthsChanged => {
177 s.dirty_because(unit, "the local lengths changed")?;
178 s.note(
179 "this could happen because of added/removed `cargo::rerun-if` instructions in the build script",
180 )?;
181
182 Ok(())
183 }
184 DirtyReason::PrecalculatedComponentsChanged { .. } => {
185 s.dirty_because(unit, "the precalculated components changed")
186 }
187 DirtyReason::ChecksumUseChanged { old } => {
188 if *old {
189 s.dirty_because(
190 unit,
191 "the prior compilation used checksum freshness and this one does not",
192 )
193 } else {
194 s.dirty_because(unit, "checksum freshness requested, prior compilation did not use checksum freshness")
195 }
196 }
197 DirtyReason::DepInfoOutputChanged { .. } => {
198 s.dirty_because(unit, "the dependency info output changed")
199 }
200 DirtyReason::RerunIfChangedOutputFileChanged { .. } => {
201 s.dirty_because(unit, "rerun-if-changed output file path changed")
202 }
203 DirtyReason::RerunIfChangedOutputPathsChanged { .. } => {
204 s.dirty_because(unit, "the rerun-if-changed instructions changed")
205 }
206 DirtyReason::EnvVarsChanged { .. } => {
207 s.dirty_because(unit, "the environment variables changed")
208 }
209 DirtyReason::EnvVarChanged { name, .. } => {
210 s.dirty_because(unit, format_args!("the env variable {name} changed"))
211 }
212 DirtyReason::LocalFingerprintTypeChanged { .. } => {
213 s.dirty_because(unit, "the local fingerprint type changed")
214 }
215 DirtyReason::NumberOfDependenciesChanged { old, new } => s.dirty_because(
216 unit,
217 format_args!("number of dependencies changed ({old} => {new})",),
218 ),
219 DirtyReason::UnitDependencyNameChanged { old, new } => s.dirty_because(
220 unit,
221 format_args!("name of dependency changed ({old} => {new})"),
222 ),
223 DirtyReason::UnitDependencyInfoChanged { .. } => {
224 s.dirty_because(unit, "dependency info changed")
225 }
226 DirtyReason::FsStatusOutdated(status) => match status {
227 FsStatus::Stale => s.dirty_because(unit, "stale, unknown reason"),
228 FsStatus::StaleItem(item) => match item {
229 StaleItem::MissingFile(missing_file) => {
230 let file = missing_file.strip_prefix(root).unwrap_or(&missing_file);
231 s.dirty_because(
232 unit,
233 format_args!("the file `{}` is missing", file.display()),
234 )
235 }
236 StaleItem::UnableToReadFile(file) => {
237 let file = file.strip_prefix(root).unwrap_or(&file);
238 s.dirty_because(
239 unit,
240 format_args!("the file `{}` could not be read", file.display()),
241 )
242 }
243 StaleItem::FailedToReadMetadata(file) => {
244 let file = file.strip_prefix(root).unwrap_or(&file);
245 s.dirty_because(
246 unit,
247 format_args!("couldn't read metadata for file `{}`", file.display()),
248 )
249 }
250 StaleItem::ChangedFile {
251 stale,
252 stale_mtime,
253 reference_mtime,
254 ..
255 } => {
256 let file = stale.strip_prefix(root).unwrap_or(&stale);
257 let after = Self::after(*reference_mtime, *stale_mtime, "last build");
258 s.dirty_because(
259 unit,
260 format_args!("the file `{}` has changed ({after})", file.display()),
261 )
262 }
263 StaleItem::ChangedChecksum {
264 source,
265 stored_checksum,
266 new_checksum,
267 } => {
268 let file = source.strip_prefix(root).unwrap_or(&source);
269 s.dirty_because(
270 unit,
271 format_args!(
272 "the file `{}` has changed (checksum didn't match, {stored_checksum} != {new_checksum})",
273 file.display(),
274 ),
275 )
276 }
277 StaleItem::FileSizeChanged {
278 path,
279 old_size,
280 new_size,
281 } => {
282 let file = path.strip_prefix(root).unwrap_or(&path);
283 s.dirty_because(
284 unit,
285 format_args!(
286 "file size changed ({old_size} != {new_size}) for `{}`",
287 file.display()
288 ),
289 )
290 }
291 StaleItem::MissingChecksum(path) => {
292 let file = path.strip_prefix(root).unwrap_or(&path);
293 s.dirty_because(
294 unit,
295 format_args!("the checksum for file `{}` is missing", file.display()),
296 )
297 }
298 StaleItem::ChangedEnv { var, .. } => s.dirty_because(
299 unit,
300 format_args!("the environment variable {var} changed"),
301 ),
302 },
303 FsStatus::StaleDependency {
304 name,
305 dep_mtime,
306 max_mtime,
307 ..
308 } => {
309 let after = Self::after(*max_mtime, *dep_mtime, "last build");
310 s.dirty_because(
311 unit,
312 format_args!("the dependency {name} was rebuilt ({after})"),
313 )
314 }
315 FsStatus::StaleDepFingerprint { name } => {
316 s.dirty_because(unit, format_args!("the dependency {name} was rebuilt"))
317 }
318 FsStatus::UpToDate { .. } => {
319 unreachable!()
320 }
321 },
322 DirtyReason::NothingObvious => {
323 s.dirty_because(unit, "the fingerprint comparison turned up nothing obvious")
325 }
326 DirtyReason::Forced => s.dirty_because(unit, "forced"),
327 DirtyReason::FreshBuild => s.dirty_because(unit, "fresh build"),
328 }
329 }
330}