Skip to main content

cargo/core/compiler/fingerprint/
mod.rs

1//! Tracks changes to determine if something needs to be recompiled.
2//!
3//! This module implements change-tracking so that Cargo can know whether or
4//! not something needs to be recompiled. A Cargo [`Unit`] can be either "dirty"
5//! (needs to be recompiled) or "fresh" (it does not need to be recompiled).
6//!
7//! ## Mechanisms affecting freshness
8//!
9//! There are several mechanisms that influence a Unit's freshness:
10//!
11//! - The [`Fingerprint`] is a hash, saved to the filesystem in the
12//!   `.fingerprint` directory, that tracks information about the Unit. If the
13//!   fingerprint is missing (such as the first time the unit is being
14//!   compiled), then the unit is dirty. If any of the fingerprint fields
15//!   change (like the name of the source file), then the Unit is considered
16//!   dirty.
17//!
18//!   The `Fingerprint` also tracks the fingerprints of all its dependencies,
19//!   so a change in a dependency will propagate the "dirty" status up.
20//!
21//! - Filesystem mtime tracking is also used to check if a unit is dirty.
22//!   See the section below on "Mtime comparison" for more details. There
23//!   are essentially two parts to mtime tracking:
24//!
25//!   1. The mtime of a Unit's output files is compared to the mtime of all
26//!      its dependencies' output file mtimes (see
27//!      [`check_filesystem`]). If any output is missing, or is
28//!      older than a dependency's output, then the unit is dirty.
29//!   2. The mtime of a Unit's source files is compared to the mtime of its
30//!      dep-info file in the fingerprint directory (see [`find_stale_file`]).
31//!      The dep-info file is used as an anchor to know when the last build of
32//!      the unit was done. See the "dep-info files" section below for more
33//!      details. If any input files are missing, or are newer than the
34//!      dep-info, then the unit is dirty.
35//!
36//!  - Alternatively if you're using the unstable feature `checksum-freshness`
37//!    mtimes are ignored entirely in favor of comparing first the file size, and
38//!    then the checksum with a known prior value emitted by rustc. Only nightly
39//!    rustc will emit the needed metadata at the time of writing. This is dependent
40//!    on the unstable feature `-Z checksum-hash-algorithm`.
41//!
42//! Note: Fingerprinting is not a perfect solution. Filesystem mtime tracking
43//! is notoriously imprecise and problematic. Only a small part of the
44//! environment is captured. This is a balance of performance, simplicity, and
45//! completeness. Sandboxing, hashing file contents, tracking every file
46//! access, environment variable, and network operation would ensure more
47//! reliable and reproducible builds at the cost of being complex, slow, and
48//! platform-dependent.
49//!
50//! ## Fingerprints and [`UnitHash`]s
51//!
52//! [`Metadata`] tracks several [`UnitHash`]s, including
53//! [`Metadata::unit_id`], [`Metadata::c_metadata`], and [`Metadata::c_extra_filename`].
54//! See its documentation for more details.
55//!
56//! NOTE: Not all output files are isolated via filename hashes (like dylibs).
57//! The fingerprint directory uses a hash, but sometimes units share the same
58//! fingerprint directory (when they don't have Metadata) so care should be
59//! taken to handle this!
60//!
61//! Fingerprints and [`UnitHash`]s are similar, and track some of the same things.
62//! [`UnitHash`]s contains information that is required to keep Units separate.
63//! The Fingerprint includes additional information that should cause a
64//! recompile, but it is desired to reuse the same filenames. A comparison
65//! of what is tracked:
66//!
67//! Value                                      | Fingerprint | `Metadata::unit_id` [^8] | `Metadata::c_metadata`
68//! -------------------------------------------|-------------|--------------------------|-----------------------
69//! rustc                                      | ✓           | ✓                        | ✓
70//! [`Profile`]                                | ✓           | ✓                        | ✓
71//! `cargo rustc` extra args                   | ✓           | ✓[^7]                    |
72//! [`CompileMode`]                            | ✓           | ✓                        | ✓
73//! Target Name                                | ✓           | ✓                        | ✓
74//! `TargetKind` (bin/lib/etc.)                | ✓           | ✓                        | ✓
75//! Enabled Features                           | ✓           | ✓                        | ✓
76//! Declared Features                          | ✓           |                          |
77//! Immediate dependency’s hashes              | ✓[^1]       | ✓                        | ✓
78//! [`CompileKind`] (host/target)              | ✓           | ✓                        | ✓
79//! `__CARGO_DEFAULT_LIB_METADATA`[^4]         |             | ✓                        | ✓
80//! `package_id`                               |             | ✓                        | ✓
81//! Target src path relative to ws             | ✓           |                          |
82//! Target flags (test/bench/for_host/edition) | ✓           |                          |
83//! -C incremental=… flag                      | ✓           |                          |
84//! mtime of sources                           | ✓[^3]       |                          |
85//! RUSTFLAGS/RUSTDOCFLAGS                     | ✓           | ✓[^7]                    |
86//! [`Lto`] flags                              | ✓           | ✓                        | ✓
87//! config settings[^5]                        | ✓           |                          |
88//! `is_std`                                   |             | ✓                        | ✓
89//! `[lints]` table[^6]                        | ✓           |                          |
90//! `[lints.rust.unexpected_cfgs.check-cfg]`   | ✓           |                          |
91//! `--extern priv:`                           | ✓           |                          |
92//!
93//! [^1]: Bin dependencies are not included.
94//!
95//! [^3]: See below for details on mtime tracking.
96//!
97//! [^4]: `__CARGO_DEFAULT_LIB_METADATA` is set by rustbuild to embed the
98//!        release channel (bootstrap/stable/beta/nightly) in libstd.
99//!
100//! [^5]: Config settings that are not otherwise captured anywhere else.
101//!       Currently, this is only `doc.extern-map`.
102//!
103//! [^6]: Via [`Manifest::lint_rustflags`][crate::core::Manifest::lint_rustflags]
104//!
105//! [^7]: extra-flags and RUSTFLAGS are conditionally excluded when `--remap-path-prefix` is
106//!       present to avoid breaking build reproducibility while we wait for trim-paths
107//!
108//! [^8]: including `-Cextra-filename`
109//!
110//! When deciding what should go in the Metadata vs the Fingerprint, consider
111//! that some files (like dylibs) do not have a hash in their filename. Thus,
112//! if a value changes, only the fingerprint will detect the change (consider,
113//! for example, swapping between different features). Fields that are only in
114//! Metadata generally aren't relevant to the fingerprint because they
115//! fundamentally change the output (like target vs host changes the directory
116//! where it is emitted).
117//!
118//! ## Fingerprint files
119//!
120//! Fingerprint information is stored in the
121//! `target/{debug,release}/.fingerprint/` directory. Each Unit is stored in a
122//! separate directory. Each Unit directory contains:
123//!
124//! - A file with a 16 hex-digit hash. This is the Fingerprint hash, used for
125//!   quick loading and comparison.
126//! - A `.json` file that contains details about the Fingerprint. This is only
127//!   used to log details about *why* a fingerprint is considered dirty.
128//!   `CARGO_LOG=cargo::core::compiler::fingerprint=trace cargo build` can be
129//!   used to display this log information.
130//! - A "dep-info" file which is a translation of rustc's `*.d` dep-info files
131//!   to a Cargo-specific format that tweaks file names and is optimized for
132//!   reading quickly.
133//! - An `invoked.timestamp` file whose filesystem mtime is updated every time
134//!   the Unit is built. This is used for capturing the time when the build
135//!   starts, to detect if files are changed in the middle of the build. See
136//!   below for more details.
137//!
138//! Note that some units are a little different. A Unit for *running* a build
139//! script or for `rustdoc` does not have a dep-info file (it's not
140//! applicable). Build script `invoked.timestamp` files are in the build
141//! output directory.
142//!
143//! ## Fingerprint calculation
144//!
145//! After the list of Units has been calculated, the Units are added to the
146//! [`JobQueue`]. As each one is added, the fingerprint is calculated, and the
147//! dirty/fresh status is recorded. A closure is used to update the fingerprint
148//! on-disk when the Unit successfully finishes. The closure will recompute the
149//! Fingerprint based on the updated information. If the Unit fails to compile,
150//! the fingerprint is not updated.
151//!
152//! Fingerprints are cached in the [`BuildRunner`]. This makes computing
153//! Fingerprints faster, but also is necessary for properly updating
154//! dependency information. Since a Fingerprint includes the Fingerprints of
155//! all dependencies, when it is updated, by using `Arc` clones, it
156//! automatically picks up the updates to its dependencies.
157//!
158//! ### dep-info files
159//!
160//! Cargo has several kinds of "dep info" files:
161//!
162//! * dep-info files generated by `rustc`.
163//! * Fingerprint dep-info files translated from the first one.
164//! * dep-info for external build system integration.
165//! * Unstable `-Zbinary-dep-depinfo`.
166//!
167//! #### `rustc` dep-info files
168//!
169//! Cargo passes the `--emit=dep-info` flag to `rustc` so that `rustc` will
170//! generate a "dep info" file (with the `.d` extension). This is a
171//! Makefile-like syntax that includes all of the source files used to build
172//! the crate. This file is used by Cargo to know which files to check to see
173//! if the crate will need to be rebuilt. Example:
174//!
175//! ```makefile
176//! /path/to/target/debug/deps/cargo-b6219d178925203d: src/bin/main.rs src/bin/cargo/cli.rs # … etc.
177//! ```
178//!
179//! #### Fingerprint dep-info files
180//!
181//! After `rustc` exits successfully, Cargo will read the first kind of dep
182//! info file and translate it into a binary format that is stored in the
183//! fingerprint directory ([`translate_dep_info`]).
184//!
185//! These are used to quickly scan for any changed files. The mtime of the
186//! fingerprint dep-info file itself is used as the reference for comparing the
187//! source files to determine if any of the source files have been modified
188//! (see [below](#mtime-comparison) for more detail).
189//!
190//! Note that Cargo parses the special `# env-var:...` comments in dep-info
191//! files to learn about environment variables that the rustc compile depends on.
192//! Cargo then later uses this to trigger a recompile if a referenced env var
193//! changes (even if the source didn't change).
194//! This also includes env vars generated from Cargo metadata like `CARGO_PKG_DESCRIPTION`.
195//! (See [`crate::core::manifest::ManifestMetadata`]
196//!
197//! #### dep-info files for build system integration.
198//!
199//! There is also a third dep-info file. Cargo will extend the file created by
200//! rustc with some additional information and saves this into the output
201//! directory. This is intended for build system integration. See the
202//! [`output_depinfo`] function for more detail.
203//!
204//! #### -Zbinary-dep-depinfo
205//!
206//! `rustc` has an experimental flag `-Zbinary-dep-depinfo`. This causes
207//! `rustc` to include binary files (like rlibs) in the dep-info file. This is
208//! primarily to support rustc development, so that Cargo can check the
209//! implicit dependency to the standard library (which lives in the sysroot).
210//! We want Cargo to recompile whenever the standard library rlib/dylibs
211//! change, and this is a generic mechanism to make that work.
212//!
213//! ### Mtime comparison
214//!
215//! The use of modification timestamps is the most common way a unit will be
216//! determined to be dirty or fresh between builds. There are many subtle
217//! issues and edge cases with mtime comparisons. This gives a high-level
218//! overview, but you'll need to read the code for the gritty details. Mtime
219//! handling is different for different unit kinds. The different styles are
220//! driven by the [`Fingerprint::local`] field, which is set based on the unit
221//! kind.
222//!
223//! The status of whether or not the mtime is "stale" or "up-to-date" is
224//! stored in [`Fingerprint::fs_status`].
225//!
226//! All units will compare the mtime of its newest output file with the mtimes
227//! of the outputs of all its dependencies. If any output file is missing,
228//! then the unit is stale. If any dependency is newer, the unit is stale.
229//!
230//! #### Normal package mtime handling
231//!
232//! [`LocalFingerprint::CheckDepInfo`] is used for checking the mtime of
233//! packages. It compares the mtime of the input files (the source files) to
234//! the mtime of the dep-info file (which is written last after a build is
235//! finished). If the dep-info is missing, the unit is stale (it has never
236//! been built). The list of input files comes from the dep-info file. See the
237//! section above for details on dep-info files.
238//!
239//! Also note that although registry and git packages use [`CheckDepInfo`], none
240//! of their source files are included in the dep-info (see
241//! [`translate_dep_info`]), so for those kinds no mtime checking is done
242//! (unless `-Zbinary-dep-depinfo` is used). Repository and git packages are
243//! static, so there is no need to check anything.
244//!
245//! When a build is complete, the mtime of the dep-info file in the
246//! fingerprint directory is modified to rewind it to the time when the build
247//! started. This is done by creating an `invoked.timestamp` file when the
248//! build starts to capture the start time. The mtime is rewound to the start
249//! to handle the case where the user modifies a source file while a build is
250//! running. Cargo can't know whether or not the file was included in the
251//! build, so it takes a conservative approach of assuming the file was *not*
252//! included, and it should be rebuilt during the next build.
253//!
254//! #### Rustdoc mtime handling
255//!
256//! Rustdoc does not emit a dep-info file, so Cargo currently has a relatively
257//! simple system for detecting rebuilds. [`LocalFingerprint::Precalculated`] is
258//! used for rustdoc units. For registry packages, this is the package
259//! version. For git packages, it is the git hash. For path packages, it is
260//! a string of the mtime of the newest file in the package.
261//!
262//! There are some known bugs with how this works, so it should be improved at
263//! some point.
264//!
265//! #### Build script mtime handling
266//!
267//! Build script mtime handling runs in different modes. There is the "old
268//! style" where the build script does not emit any `rerun-if` directives. In
269//! this mode, Cargo will use [`LocalFingerprint::Precalculated`]. See the
270//! "rustdoc" section above how it works.
271//!
272//! In the new-style, each `rerun-if` directive is translated to the
273//! corresponding [`LocalFingerprint`] variant. The [`RerunIfChanged`] variant
274//! compares the mtime of the given filenames against the mtime of the
275//! "output" file.
276//!
277//! Similar to normal units, the build script "output" file mtime is rewound
278//! to the time just before the build script is executed to handle mid-build
279//! modifications.
280//!
281//! ## Considerations for inclusion in a fingerprint
282//!
283//! Over time we've realized a few items which historically were included in
284//! fingerprint hashings should not actually be included. Examples are:
285//!
286//! * Modification time values. We strive to never include a modification time
287//!   inside a `Fingerprint` to get hashed into an actual value. While
288//!   theoretically fine to do, in practice this causes issues with common
289//!   applications like Docker. Docker, after a layer is built, will zero out
290//!   the nanosecond part of all filesystem modification times. This means that
291//!   the actual modification time is different for all build artifacts, which
292//!   if we tracked the actual values of modification times would cause
293//!   unnecessary recompiles. To fix this we instead only track paths which are
294//!   relevant. These paths are checked dynamically to see if they're up to
295//!   date, and the modification time doesn't make its way into the fingerprint
296//!   hash.
297//!
298//! * Absolute path names. We strive to maintain a property where if you rename
299//!   a project directory Cargo will continue to preserve all build artifacts
300//!   and reuse the cache. This means that we can't ever hash an absolute path
301//!   name. Instead we always hash relative path names and the "root" is passed
302//!   in at runtime dynamically. Some of this is best effort, but the general
303//!   idea is that we assume all accesses within a crate stay within that
304//!   crate.
305//!
306//! These are pretty tricky to test for unfortunately, but we should have a good
307//! test suite nowadays and lord knows Cargo gets enough testing in the wild!
308//!
309//! ## Build scripts
310//!
311//! The *running* of a build script ([`CompileMode::RunCustomBuild`]) is treated
312//! significantly different than all other Unit kinds. It has its own function
313//! for calculating the Fingerprint ([`calculate_run_custom_build`]) and has some
314//! unique considerations. It does not track the same information as a normal
315//! Unit. The information tracked depends on the `rerun-if-changed` and
316//! `rerun-if-env-changed` statements produced by the build script. If the
317//! script does not emit either of these statements, the Fingerprint runs in
318//! "old style" mode where an mtime change of *any* file in the package will
319//! cause the build script to be re-run. Otherwise, the fingerprint *only*
320//! tracks the individual "rerun-if" items listed by the build script.
321//!
322//! The "rerun-if" statements from a *previous* build are stored in the build
323//! output directory in a file called `output`. Cargo parses this file when
324//! the Unit for that build script is prepared for the [`JobQueue`]. The
325//! Fingerprint code can then use that information to compute the Fingerprint
326//! and compare against the old fingerprint hash.
327//!
328//! Care must be taken with build script Fingerprints because the
329//! [`Fingerprint::local`] value may be changed after the build script runs
330//! (such as if the build script adds or removes "rerun-if" items).
331//!
332//! Another complication is if a build script is overridden. In that case, the
333//! fingerprint is the hash of the output of the override.
334//!
335//! ## Special considerations
336//!
337//! Registry dependencies do not track the mtime of files. This is because
338//! registry dependencies are not expected to change (if a new version is
339//! used, the Package ID will change, causing a rebuild). Cargo currently
340//! partially works with Docker caching. When a Docker image is built, it has
341//! normal mtime information. However, when a step is cached, the nanosecond
342//! portions of all files is zeroed out. Currently this works, but care must
343//! be taken for situations like these.
344//!
345//! HFS on macOS only supports 1 second timestamps. This causes a significant
346//! number of problems, particularly with Cargo's testsuite which does rapid
347//! builds in succession. Other filesystems have various degrees of
348//! resolution.
349//!
350//! Various weird filesystems (such as network filesystems) also can cause
351//! complications. Network filesystems may track the time on the server
352//! (except when the time is set manually such as with
353//! `filetime::set_file_times`). Not all filesystems support modifying the
354//! mtime.
355//!
356//! See the [`A-rebuild-detection`] label on the issue tracker for more.
357//!
358//! [`check_filesystem`]: Fingerprint::check_filesystem
359//! [`Metadata`]: crate::core::compiler::Metadata
360//! [`Metadata::unit_id`]: crate::core::compiler::Metadata::unit_id
361//! [`Metadata::c_metadata`]: crate::core::compiler::Metadata::c_metadata
362//! [`Metadata::c_extra_filename`]: crate::core::compiler::Metadata::c_extra_filename
363//! [`UnitHash`]: crate::core::compiler::UnitHash
364//! [`Profile`]: crate::core::profiles::Profile
365//! [`CompileMode`]: crate::core::compiler::CompileMode
366//! [`Lto`]: crate::core::compiler::Lto
367//! [`CompileKind`]: crate::core::compiler::CompileKind
368//! [`JobQueue`]: super::job_queue::JobQueue
369//! [`output_depinfo`]: super::output_depinfo()
370//! [`CheckDepInfo`]: LocalFingerprint::CheckDepInfo
371//! [`RerunIfChanged`]: LocalFingerprint::RerunIfChanged
372//! [`CompileMode::RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild
373//! [`A-rebuild-detection`]: https://github.com/rust-lang/cargo/issues?q=is%3Aissue+is%3Aopen+label%3AA-rebuild-detection
374
375mod dep_info;
376mod dirty_reason;
377mod rustdoc;
378
379use crate::util::data_structures::HashMap;
380use std::collections::hash_map::Entry;
381use std::env;
382use std::ffi::OsString;
383use std::fs;
384use std::fs::File;
385use std::hash::{self, Hash, Hasher};
386use std::io::{self};
387use std::path::{Path, PathBuf};
388use std::sync::{Arc, Mutex};
389use std::time::SystemTime;
390
391use anyhow::Context as _;
392use anyhow::format_err;
393use cargo_util::paths;
394use filetime::FileTime;
395use serde::de;
396use serde::ser;
397use serde::{Deserialize, Serialize};
398use tracing::{debug, info};
399
400use crate::core::Package;
401use crate::core::compiler::unit_graph::UnitDep;
402use crate::util;
403use crate::util::errors::CargoResult;
404use crate::util::interning::InternedString;
405use crate::util::log_message::LogMessage;
406use crate::util::{StableHasher, internal, path_args};
407use crate::{CARGO_ENV, GlobalContext};
408
409use super::BuildContext;
410use super::BuildRunner;
411use super::FileFlavor;
412use super::Job;
413use super::Unit;
414use super::UnitIndex;
415use super::Work;
416use super::custom_build::BuildDeps;
417
418pub use self::dep_info::Checksum;
419pub use self::dep_info::parse_dep_info;
420pub use self::dep_info::parse_rustc_dep_info;
421pub use self::dep_info::translate_dep_info;
422pub use self::dirty_reason::DirtyReason;
423pub use self::rustdoc::RustdocFingerprint;
424
425/// Result of comparing fingerprints between the current and previous builds.
426enum FingerprintComparison {
427    /// The unit does not need rebuilding.
428    Fresh,
429    /// The unit needs rebuilding.
430    Dirty {
431        /// The reason why the unit is dirty.
432        reason: DirtyReason,
433    },
434}
435
436/// Determines if a [`Unit`] is up-to-date, and if not prepares necessary work to
437/// update the persisted fingerprint.
438///
439/// This function will inspect `Unit`, calculate a fingerprint for it, and then
440/// return an appropriate [`Job`] to run. The returned `Job` will be a noop if
441/// `unit` is considered "fresh", or if it was previously built and cached.
442/// Otherwise the `Job` returned will write out the true fingerprint to the
443/// filesystem, to be executed after the unit's work has completed.
444///
445/// The `force` flag is a way to force the `Job` to be "dirty", or always
446/// update the fingerprint. **Beware using this flag** because it does not
447/// transitively propagate throughout the dependency graph, it only forces this
448/// one unit which is very unlikely to be what you want unless you're
449/// exclusively talking about top-level units.
450#[tracing::instrument(
451    skip(build_runner, unit),
452    fields(package_id = %unit.pkg.package_id(), target = unit.target.name())
453)]
454pub fn prepare_target(
455    build_runner: &mut BuildRunner<'_, '_>,
456    unit: &Unit,
457    force: bool,
458) -> CargoResult<Job> {
459    let bcx = build_runner.bcx;
460    let loc = build_runner.files().fingerprint_file_path(unit, "");
461
462    debug!("fingerprint at: {}", loc.display());
463
464    // Figure out if this unit is up to date. After calculating the fingerprint
465    // compare it to an old version, if any, and attempt to print diagnostic
466    // information about failed comparisons to aid in debugging.
467    let fingerprint = calculate(build_runner, unit)?;
468    let mtime_on_use = build_runner.bcx.gctx.cli_unstable().mtime_on_use;
469    let dirty_reason = match compare_old_fingerprint(unit, &loc, &*fingerprint, mtime_on_use, force)
470    {
471        FingerprintComparison::Fresh => None,
472        FingerprintComparison::Dirty { reason } => Some(reason),
473    };
474
475    if let Some(logger) = bcx.logger {
476        let index = bcx.unit_to_index[unit];
477        let mut cause = None;
478        let status = match dirty_reason.as_ref() {
479            Some(reason) if reason.is_fresh_build() => util::log_message::FingerprintStatus::New,
480            Some(reason) => {
481                cause = Some(reason.clone());
482                util::log_message::FingerprintStatus::Dirty
483            }
484            None => util::log_message::FingerprintStatus::Fresh,
485        };
486        logger.log(LogMessage::UnitFingerprint {
487            index,
488            status,
489            cause,
490        });
491    }
492
493    let Some(dirty_reason) = dirty_reason else {
494        return Ok(Job::new_fresh());
495    };
496
497    // We're going to rebuild, so ensure the source of the crate passes all
498    // verification checks before we build it.
499    //
500    // The `Source::verify` method is intended to allow sources to execute
501    // pre-build checks to ensure that the relevant source code is all
502    // up-to-date and as expected. This is currently used primarily for
503    // directory sources which will use this hook to perform an integrity check
504    // on all files in the source to ensure they haven't changed. If they have
505    // changed then an error is issued.
506    let source_id = unit.pkg.package_id().source_id();
507    let sources = bcx.packages.sources();
508    let source = sources
509        .get(source_id)
510        .ok_or_else(|| internal("missing package source"))?;
511    source.verify(unit.pkg.package_id())?;
512
513    // Clear out the old fingerprint file if it exists. This protects when
514    // compilation is interrupted leaving a corrupt file. For example, a
515    // project with a lib.rs and integration test (two units):
516    //
517    // 1. Build the library and integration test.
518    // 2. Make a change to lib.rs (NOT the integration test).
519    // 3. Build the integration test, hit Ctrl-C while linking. With gcc, this
520    //    will leave behind an incomplete executable (zero size, or partially
521    //    written). NOTE: The library builds successfully, it is the linking
522    //    of the integration test that we are interrupting.
523    // 4. Build the integration test again.
524    //
525    // Without the following line, then step 3 will leave a valid fingerprint
526    // on the disk. Then step 4 will think the integration test is "fresh"
527    // because:
528    //
529    // - There is a valid fingerprint hash on disk (written in step 1).
530    // - The mtime of the output file (the corrupt integration executable
531    //   written in step 3) is newer than all of its dependencies.
532    // - The mtime of the integration test fingerprint dep-info file (written
533    //   in step 1) is newer than the integration test's source files, because
534    //   we haven't modified any of its source files.
535    //
536    // But the executable is corrupt and needs to be rebuilt. Clearing the
537    // fingerprint at step 3 ensures that Cargo never mistakes a partially
538    // written output as up-to-date.
539    if loc.exists() {
540        // Truncate instead of delete so that compare_old_fingerprint will
541        // still log the reason for the fingerprint failure instead of just
542        // reporting "failed to read fingerprint" during the next build if
543        // this build fails.
544        paths::write(&loc, b"")?;
545    }
546
547    let write_fingerprint = if unit.mode.is_run_custom_build() {
548        // For build scripts the `local` field of the fingerprint may change
549        // while we're executing it. For example it could be in the legacy
550        // "consider everything a dependency mode" and then we switch to "deps
551        // are explicitly specified" mode.
552        //
553        // To handle this movement we need to regenerate the `local` field of a
554        // build script's fingerprint after it's executed. We do this by
555        // using the `build_script_local_fingerprints` function which returns a
556        // thunk we can invoke on a foreign thread to calculate this.
557        let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
558        let metadata = build_runner.get_run_build_script_metadata(unit);
559        let (gen_local, _overridden) = build_script_local_fingerprints(build_runner, unit)?;
560        let output_path = build_runner.build_explicit_deps[unit]
561            .build_script_output
562            .clone();
563        Work::new(move |_| {
564            let outputs = build_script_outputs.lock().unwrap();
565            let output = outputs
566                .get(metadata)
567                .expect("output must exist after running");
568            let deps = BuildDeps::new(&output_path, Some(output));
569
570            // FIXME: it's basically buggy that we pass `None` to `call_box`
571            // here. See documentation on `build_script_local_fingerprints`
572            // below for more information. Despite this just try to proceed and
573            // hobble along if it happens to return `Some`.
574            if let Some(new_local) = (gen_local)(&deps, None)? {
575                *fingerprint.local.lock().unwrap() = new_local;
576            }
577
578            write_fingerprint(&loc, &fingerprint)
579        })
580    } else {
581        Work::new(move |_| write_fingerprint(&loc, &fingerprint))
582    };
583
584    Ok(Job::new_dirty(write_fingerprint, dirty_reason))
585}
586
587/// Dependency edge information for fingerprints. This is generated for each
588/// dependency and is stored in a [`Fingerprint`].
589#[derive(Clone)]
590struct DepFingerprint {
591    /// The hash of the package id that this dependency points to
592    pkg_id: u64,
593    /// The crate name we're using for this dependency, which if we change we'll
594    /// need to recompile!
595    name: InternedString,
596    /// Whether or not this dependency is flagged as a public dependency or not.
597    public: bool,
598    /// Whether or not this dependency is an rmeta dependency or a "full"
599    /// dependency. In the case of an rmeta dependency our dependency edge only
600    /// actually requires the rmeta from what we depend on, so when checking
601    /// mtime information all files other than the rmeta can be ignored.
602    only_requires_rmeta: bool,
603    /// The dependency's fingerprint we recursively point to, containing all the
604    /// other hash information we'd otherwise need.
605    fingerprint: Arc<Fingerprint>,
606}
607
608/// A fingerprint can be considered to be a "short string" representing the
609/// state of a world for a package.
610///
611/// If a fingerprint ever changes, then the package itself needs to be
612/// recompiled. Inputs to the fingerprint include source code modifications,
613/// compiler flags, compiler version, etc. This structure is not simply a
614/// `String` due to the fact that some fingerprints cannot be calculated lazily.
615///
616/// Path sources, for example, use the mtime of the corresponding dep-info file
617/// as a fingerprint (all source files must be modified *before* this mtime).
618/// This dep-info file is not generated, however, until after the crate is
619/// compiled. As a result, this structure can be thought of as a fingerprint
620/// to-be. The actual value can be calculated via [`hash_u64()`], but the operation
621/// may fail as some files may not have been generated.
622///
623/// Note that dependencies are taken into account for fingerprints because rustc
624/// requires that whenever an upstream crate is recompiled that all downstream
625/// dependents are also recompiled. This is typically tracked through
626/// [`DependencyQueue`], but it also needs to be retained here because Cargo can
627/// be interrupted while executing, losing the state of the [`DependencyQueue`]
628/// graph.
629///
630/// [`hash_u64()`]: crate::core::compiler::fingerprint::Fingerprint::hash_u64
631/// [`DependencyQueue`]: crate::util::DependencyQueue
632#[derive(Serialize, Deserialize)]
633pub struct Fingerprint {
634    /// Hash of the version of `rustc` used.
635    rustc: u64,
636    /// Sorted list of cfg features enabled.
637    features: String,
638    /// Sorted list of all the declared cfg features.
639    declared_features: String,
640    /// Hash of the `Target` struct, including the target name,
641    /// package-relative source path, edition, etc.
642    target: u64,
643    /// Hash of the [`Profile`], [`CompileMode`], and any extra flags passed via
644    /// `cargo rustc` or `cargo rustdoc`.
645    ///
646    /// [`Profile`]: crate::core::profiles::Profile
647    /// [`CompileMode`]: crate::core::compiler::CompileMode
648    profile: u64,
649    /// Hash of the path to the base source file. This is relative to the
650    /// workspace root for path members, or absolute for other sources.
651    path: u64,
652    /// Fingerprints of dependencies.
653    deps: Vec<DepFingerprint>,
654    /// Information about the inputs that affect this Unit (such as source
655    /// file mtimes or build script environment variables).
656    local: Mutex<Vec<LocalFingerprint>>,
657    /// Cached hash of the [`Fingerprint`] struct. Used to improve performance
658    /// for hashing.
659    #[serde(skip)]
660    memoized_hash: Mutex<Option<u64>>,
661    /// RUSTFLAGS/RUSTDOCFLAGS environment variable value (or config value).
662    rustflags: Vec<String>,
663    /// Hash of various config settings that change how things are compiled.
664    config: u64,
665    /// The rustc target. This is only relevant for `.json` files, otherwise
666    /// the metadata hash segregates the units.
667    compile_kind: u64,
668    /// Unit index for this fingerprint, used for tracing cascading rebuilds.
669    /// Not persisted to disk as indices can change between builds.
670    #[serde(skip)]
671    index: UnitIndex,
672    /// Description of whether the filesystem status for this unit is up to date
673    /// or should be considered stale.
674    #[serde(skip)]
675    fs_status: FsStatus,
676    /// Files, relative to `target_root`, that are produced by the step that
677    /// this `Fingerprint` represents. This is used to detect when the whole
678    /// fingerprint is out of date if this is missing, or if previous
679    /// fingerprints output files are regenerated and look newer than this one.
680    #[serde(skip)]
681    outputs: Vec<PathBuf>,
682}
683
684/// Indication of the status on the filesystem for a particular unit.
685#[derive(Clone, Default, Debug, Serialize, Deserialize)]
686#[serde(tag = "fs_status", rename_all = "kebab-case")]
687pub enum FsStatus {
688    /// This unit is to be considered stale, even if hash information all
689    /// matches.
690    #[default]
691    Stale,
692
693    /// File system inputs have changed (or are missing), or there were
694    /// changes to the environment variables that affect this unit. See
695    /// the variants of [`StaleItem`] for more information.
696    StaleItem(StaleItem),
697
698    /// A dependency was stale.
699    StaleDependency {
700        unit: UnitIndex,
701        #[serde(with = "serde_file_time")]
702        dep_mtime: FileTime,
703        #[serde(with = "serde_file_time")]
704        max_mtime: FileTime,
705    },
706
707    /// A dependency's fingerprint was stale.
708    StaleDepFingerprint { unit: UnitIndex },
709
710    /// This unit is up-to-date. All outputs and their corresponding mtime are
711    /// listed in the payload here for other dependencies to compare against.
712    #[serde(skip)]
713    UpToDate { mtimes: HashMap<PathBuf, FileTime> },
714}
715
716impl FsStatus {
717    fn up_to_date(&self) -> bool {
718        match self {
719            FsStatus::UpToDate { .. } => true,
720            FsStatus::Stale
721            | FsStatus::StaleItem(_)
722            | FsStatus::StaleDependency { .. }
723            | FsStatus::StaleDepFingerprint { .. } => false,
724        }
725    }
726}
727
728mod serde_file_time {
729    use filetime::FileTime;
730    use serde::Deserialize;
731    use serde::Serialize;
732
733    /// Serialize FileTime as milliseconds with nano.
734    pub(super) fn serialize<S>(ft: &FileTime, s: S) -> Result<S::Ok, S::Error>
735    where
736        S: serde::Serializer,
737    {
738        let secs_as_millis = ft.unix_seconds() as f64 * 1000.0;
739        let nanos_as_millis = ft.nanoseconds() as f64 / 1_000_000.0;
740        (secs_as_millis + nanos_as_millis).serialize(s)
741    }
742
743    /// Deserialize FileTime from milliseconds with nano.
744    pub(super) fn deserialize<'de, D>(d: D) -> Result<FileTime, D::Error>
745    where
746        D: serde::Deserializer<'de>,
747    {
748        let millis = f64::deserialize(d)?;
749        let secs = (millis / 1000.0) as i64;
750        let nanos = ((millis % 1000.0) * 1_000_000.0) as u32;
751        Ok(FileTime::from_unix_time(secs, nanos))
752    }
753}
754
755impl Serialize for DepFingerprint {
756    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
757    where
758        S: ser::Serializer,
759    {
760        (
761            &self.pkg_id,
762            &self.name,
763            &self.public,
764            &self.fingerprint.hash_u64(),
765        )
766            .serialize(ser)
767    }
768}
769
770impl<'de> Deserialize<'de> for DepFingerprint {
771    fn deserialize<D>(d: D) -> Result<DepFingerprint, D::Error>
772    where
773        D: de::Deserializer<'de>,
774    {
775        let (pkg_id, name, public, hash) = <(u64, String, bool, u64)>::deserialize(d)?;
776        Ok(DepFingerprint {
777            pkg_id,
778            name: name.into(),
779            public,
780            fingerprint: Arc::new(Fingerprint {
781                memoized_hash: Mutex::new(Some(hash)),
782                ..Fingerprint::new()
783            }),
784            // This field is never read since it's only used in
785            // `check_filesystem` which isn't used by fingerprints loaded from
786            // disk.
787            only_requires_rmeta: false,
788        })
789    }
790}
791
792/// A `LocalFingerprint` represents something that we use to detect direct
793/// changes to a `Fingerprint`.
794///
795/// This is where we track file information, env vars, etc. This
796/// `LocalFingerprint` struct is hashed and if the hash changes will force a
797/// recompile of any fingerprint it's included into. Note that the "local"
798/// terminology comes from the fact that it only has to do with one crate, and
799/// `Fingerprint` tracks the transitive propagation of fingerprint changes.
800///
801/// Note that because this is hashed its contents are carefully managed. Like
802/// mentioned in the above module docs, we don't want to hash absolute paths or
803/// mtime information.
804///
805/// Also note that a `LocalFingerprint` is used in `check_filesystem` to detect
806/// when the filesystem contains stale information (based on mtime currently).
807/// The paths here don't change much between compilations but they're used as
808/// inputs when we probe the filesystem looking at information.
809#[derive(Debug, Serialize, Deserialize, Hash)]
810enum LocalFingerprint {
811    /// This is a precalculated fingerprint which has an opaque string we just
812    /// hash as usual. This variant is primarily used for rustdoc where we
813    /// don't have a dep-info file to compare against.
814    ///
815    /// This is also used for build scripts with no `rerun-if-*` statements, but
816    /// that's overall a mistake and causes bugs in Cargo. We shouldn't use this
817    /// for build scripts.
818    Precalculated(String),
819
820    /// This is used for crate compilations. The `dep_info` file is a relative
821    /// path anchored at `target_root(...)` to the dep-info file that Cargo
822    /// generates (which is a custom serialization after parsing rustc's own
823    /// `dep-info` output).
824    ///
825    /// The `dep_info` file, when present, also lists a number of other files
826    /// for us to look at. If any of those files are newer than this file then
827    /// we need to recompile.
828    ///
829    /// If the `checksum` bool is true then the `dep_info` file is expected to
830    /// contain file checksums instead of file mtimes.
831    CheckDepInfo { dep_info: PathBuf, checksum: bool },
832
833    /// This represents a nonempty set of `rerun-if-changed` annotations printed
834    /// out by a build script. The `output` file is a relative file anchored at
835    /// `target_root(...)` which is the actual output of the build script. That
836    /// output has already been parsed and the paths printed out via
837    /// `rerun-if-changed` are listed in `paths`. The `paths` field is relative
838    /// to `pkg.root()`
839    ///
840    /// This is considered up-to-date if all of the `paths` are older than
841    /// `output`, otherwise we need to recompile.
842    RerunIfChanged {
843        output: PathBuf,
844        paths: Vec<PathBuf>,
845    },
846
847    /// This represents a single `rerun-if-env-changed` annotation printed by a
848    /// build script. The exact env var and value are hashed here. There's no
849    /// filesystem dependence here, and if the values are changed the hash will
850    /// change forcing a recompile.
851    RerunIfEnvChanged { var: String, val: Option<String> },
852}
853
854/// See [`FsStatus::StaleItem`].
855#[derive(Clone, Debug, Serialize, Deserialize)]
856#[serde(tag = "stale_item", rename_all = "kebab-case")]
857pub enum StaleItem {
858    MissingFile {
859        path: PathBuf,
860    },
861    UnableToReadFile {
862        path: PathBuf,
863    },
864    FailedToReadMetadata {
865        path: PathBuf,
866    },
867    FileSizeChanged {
868        path: PathBuf,
869        old_size: u64,
870        new_size: u64,
871    },
872    ChangedFile {
873        reference: PathBuf,
874        #[serde(with = "serde_file_time")]
875        reference_mtime: FileTime,
876        stale: PathBuf,
877        #[serde(with = "serde_file_time")]
878        stale_mtime: FileTime,
879    },
880    ChangedChecksum {
881        source: PathBuf,
882        stored_checksum: Checksum,
883        new_checksum: Checksum,
884    },
885    MissingChecksum {
886        path: PathBuf,
887    },
888    ChangedEnv {
889        var: String,
890        previous: Option<String>,
891        current: Option<String>,
892    },
893}
894
895impl LocalFingerprint {
896    /// Read the environment variable of the given env `key`, and creates a new
897    /// [`LocalFingerprint::RerunIfEnvChanged`] for it. The `env_config` is used firstly
898    /// to check if the env var is set in the config system as some envs need to be overridden.
899    /// If not, it will fallback to `std::env::var`.
900    ///
901    // TODO: `std::env::var` is allowed at this moment. Should figure out
902    // if it makes sense if permitting to read env from the env snapshot.
903    #[allow(clippy::disallowed_methods)]
904    fn from_env<K: AsRef<str>>(
905        key: K,
906        env_config: &Arc<HashMap<String, OsString>>,
907    ) -> LocalFingerprint {
908        let key = key.as_ref();
909        let var = key.to_owned();
910        let val = if let Some(val) = env_config.get(key) {
911            val.to_str().map(ToOwned::to_owned)
912        } else {
913            env::var(key).ok()
914        };
915        LocalFingerprint::RerunIfEnvChanged { var, val }
916    }
917
918    /// Checks dynamically at runtime if this `LocalFingerprint` has a stale
919    /// item inside of it.
920    ///
921    /// The main purpose of this function is to handle two different ways
922    /// fingerprints can be invalidated:
923    ///
924    /// * One is a dependency listed in rustc's dep-info files is invalid. Note
925    ///   that these could either be env vars or files. We check both here.
926    ///
927    /// * Another is the `rerun-if-changed` directive from build scripts. This
928    ///   is where we'll find whether files have actually changed
929    fn find_stale_item(
930        &self,
931        mtime_cache: &mut HashMap<PathBuf, FileTime>,
932        checksum_cache: &mut HashMap<PathBuf, Checksum>,
933        pkg: &Package,
934        build_root: &Path,
935        cargo_exe: &Path,
936        gctx: &GlobalContext,
937    ) -> CargoResult<Option<StaleItem>> {
938        let pkg_root = pkg.root();
939        match self {
940            // We need to parse `dep_info`, learn about the crate's dependencies.
941            //
942            // For each env var we see if our current process's env var still
943            // matches, and for each file we see if any of them are newer than
944            // the `dep_info` file itself whose mtime represents the start of
945            // rustc.
946            LocalFingerprint::CheckDepInfo { dep_info, checksum } => {
947                let dep_info = build_root.join(dep_info);
948                let Some(info) = parse_dep_info(pkg_root, build_root, &dep_info)? else {
949                    return Ok(Some(StaleItem::MissingFile { path: dep_info }));
950                };
951                for (key, previous) in info.env.iter() {
952                    if let Some(value) = pkg.manifest().metadata().env_var(key.as_str()) {
953                        if Some(value.as_ref()) == previous.as_deref() {
954                            continue;
955                        }
956                    }
957
958                    let current = if key == CARGO_ENV {
959                        Some(cargo_exe.to_str().ok_or_else(|| {
960                            format_err!(
961                                "cargo exe path {} must be valid UTF-8",
962                                cargo_exe.display()
963                            )
964                        })?)
965                    } else {
966                        if let Some(value) = gctx.env_config()?.get(key) {
967                            value.to_str()
968                        } else {
969                            gctx.get_env(key).ok()
970                        }
971                    };
972                    if current == previous.as_deref() {
973                        continue;
974                    }
975                    return Ok(Some(StaleItem::ChangedEnv {
976                        var: key.clone(),
977                        previous: previous.clone(),
978                        current: current.map(Into::into),
979                    }));
980                }
981                if *checksum {
982                    Ok(find_stale_file(
983                        mtime_cache,
984                        checksum_cache,
985                        &dep_info,
986                        info.files.iter().map(|(file, checksum)| (file, *checksum)),
987                        *checksum,
988                    ))
989                } else {
990                    Ok(find_stale_file(
991                        mtime_cache,
992                        checksum_cache,
993                        &dep_info,
994                        info.files.into_keys().map(|p| (p, None)),
995                        *checksum,
996                    ))
997                }
998            }
999
1000            // We need to verify that no paths listed in `paths` are newer than
1001            // the `output` path itself, or the last time the build script ran.
1002            LocalFingerprint::RerunIfChanged { output, paths } => Ok(find_stale_file(
1003                mtime_cache,
1004                checksum_cache,
1005                &build_root.join(output),
1006                paths.iter().map(|p| (pkg_root.join(p), None)),
1007                false,
1008            )),
1009
1010            // These have no dependencies on the filesystem, and their values
1011            // are included natively in the `Fingerprint` hash so nothing
1012            // tocheck for here.
1013            LocalFingerprint::RerunIfEnvChanged { .. } => Ok(None),
1014            LocalFingerprint::Precalculated(..) => Ok(None),
1015        }
1016    }
1017
1018    fn kind(&self) -> &'static str {
1019        match self {
1020            LocalFingerprint::Precalculated(..) => "precalculated",
1021            LocalFingerprint::CheckDepInfo { .. } => "dep-info",
1022            LocalFingerprint::RerunIfChanged { .. } => "rerun-if-changed",
1023            LocalFingerprint::RerunIfEnvChanged { .. } => "rerun-if-env-changed",
1024        }
1025    }
1026}
1027
1028impl Fingerprint {
1029    fn new() -> Fingerprint {
1030        Fingerprint {
1031            rustc: 0,
1032            target: 0,
1033            profile: 0,
1034            path: 0,
1035            features: String::new(),
1036            declared_features: String::new(),
1037            deps: Vec::new(),
1038            local: Mutex::new(Vec::new()),
1039            memoized_hash: Mutex::new(None),
1040            rustflags: Vec::new(),
1041            config: 0,
1042            compile_kind: 0,
1043            index: UnitIndex::default(),
1044            fs_status: FsStatus::Stale,
1045            outputs: Vec::new(),
1046        }
1047    }
1048
1049    /// For performance reasons fingerprints will memoize their own hash, but
1050    /// there's also internal mutability with its `local` field which can
1051    /// change, for example with build scripts, during a build.
1052    ///
1053    /// This method can be used to bust all memoized hashes just before a build
1054    /// to ensure that after a build completes everything is up-to-date.
1055    pub fn clear_memoized(&self) {
1056        *self.memoized_hash.lock().unwrap() = None;
1057    }
1058
1059    fn hash_u64(&self) -> u64 {
1060        if let Some(s) = *self.memoized_hash.lock().unwrap() {
1061            return s;
1062        }
1063        let ret = util::hash_u64(self);
1064        *self.memoized_hash.lock().unwrap() = Some(ret);
1065        ret
1066    }
1067
1068    /// Compares this fingerprint with an old version which was previously
1069    /// serialized to filesystem.
1070    ///
1071    /// The purpose of this is exclusively to produce a diagnostic message
1072    /// [`DirtyReason`], indicating why we're recompiling something.
1073    fn compare(&self, old: &Fingerprint) -> DirtyReason {
1074        if self.rustc != old.rustc {
1075            return DirtyReason::RustcChanged;
1076        }
1077        if self.features != old.features {
1078            return DirtyReason::FeaturesChanged {
1079                old: old.features.clone(),
1080                new: self.features.clone(),
1081            };
1082        }
1083        if self.declared_features != old.declared_features {
1084            return DirtyReason::DeclaredFeaturesChanged {
1085                old: old.declared_features.clone(),
1086                new: self.declared_features.clone(),
1087            };
1088        }
1089        if self.target != old.target {
1090            return DirtyReason::TargetConfigurationChanged;
1091        }
1092        if self.path != old.path {
1093            return DirtyReason::PathToSourceChanged;
1094        }
1095        if self.profile != old.profile {
1096            return DirtyReason::ProfileConfigurationChanged;
1097        }
1098        if self.rustflags != old.rustflags {
1099            return DirtyReason::RustflagsChanged {
1100                old: old.rustflags.clone(),
1101                new: self.rustflags.clone(),
1102            };
1103        }
1104        if self.config != old.config {
1105            return DirtyReason::ConfigSettingsChanged;
1106        }
1107        if self.compile_kind != old.compile_kind {
1108            return DirtyReason::CompileKindChanged;
1109        }
1110        let my_local = self.local.lock().unwrap();
1111        let old_local = old.local.lock().unwrap();
1112        if my_local.len() != old_local.len() {
1113            return DirtyReason::LocalLengthsChanged;
1114        }
1115        for (new, old) in my_local.iter().zip(old_local.iter()) {
1116            match (new, old) {
1117                (LocalFingerprint::Precalculated(a), LocalFingerprint::Precalculated(b)) => {
1118                    if a != b {
1119                        return DirtyReason::PrecalculatedComponentsChanged {
1120                            old: b.to_string(),
1121                            new: a.to_string(),
1122                        };
1123                    }
1124                }
1125                (
1126                    LocalFingerprint::CheckDepInfo {
1127                        dep_info: a_dep,
1128                        checksum: checksum_a,
1129                    },
1130                    LocalFingerprint::CheckDepInfo {
1131                        dep_info: b_dep,
1132                        checksum: checksum_b,
1133                    },
1134                ) => {
1135                    if a_dep != b_dep {
1136                        return DirtyReason::DepInfoOutputChanged {
1137                            old: b_dep.clone(),
1138                            new: a_dep.clone(),
1139                        };
1140                    }
1141                    if checksum_a != checksum_b {
1142                        return DirtyReason::ChecksumUseChanged { old: *checksum_b };
1143                    }
1144                }
1145                (
1146                    LocalFingerprint::RerunIfChanged {
1147                        output: a_out,
1148                        paths: a_paths,
1149                    },
1150                    LocalFingerprint::RerunIfChanged {
1151                        output: b_out,
1152                        paths: b_paths,
1153                    },
1154                ) => {
1155                    if a_out != b_out {
1156                        return DirtyReason::RerunIfChangedOutputFileChanged {
1157                            old: b_out.clone(),
1158                            new: a_out.clone(),
1159                        };
1160                    }
1161                    if a_paths != b_paths {
1162                        return DirtyReason::RerunIfChangedOutputPathsChanged {
1163                            old: b_paths.clone(),
1164                            new: a_paths.clone(),
1165                        };
1166                    }
1167                }
1168                (
1169                    LocalFingerprint::RerunIfEnvChanged {
1170                        var: a_key,
1171                        val: a_value,
1172                    },
1173                    LocalFingerprint::RerunIfEnvChanged {
1174                        var: b_key,
1175                        val: b_value,
1176                    },
1177                ) => {
1178                    if *a_key != *b_key {
1179                        return DirtyReason::EnvVarsChanged {
1180                            old: b_key.clone(),
1181                            new: a_key.clone(),
1182                        };
1183                    }
1184                    if *a_value != *b_value {
1185                        return DirtyReason::EnvVarChanged {
1186                            name: a_key.clone(),
1187                            old_value: b_value.clone(),
1188                            new_value: a_value.clone(),
1189                        };
1190                    }
1191                }
1192                (a, b) => {
1193                    return DirtyReason::LocalFingerprintTypeChanged {
1194                        old: b.kind().to_owned(),
1195                        new: a.kind().to_owned(),
1196                    };
1197                }
1198            }
1199        }
1200
1201        if self.deps.len() != old.deps.len() {
1202            return DirtyReason::NumberOfDependenciesChanged {
1203                old: old.deps.len(),
1204                new: self.deps.len(),
1205            };
1206        }
1207        for (a, b) in self.deps.iter().zip(old.deps.iter()) {
1208            if a.name != b.name {
1209                return DirtyReason::UnitDependencyNameChanged {
1210                    old: b.name,
1211                    new: a.name,
1212                };
1213            }
1214
1215            if a.fingerprint.hash_u64() != b.fingerprint.hash_u64() {
1216                return DirtyReason::UnitDependencyInfoChanged {
1217                    unit: a.fingerprint.index,
1218                };
1219            }
1220        }
1221
1222        if !self.fs_status.up_to_date() {
1223            return DirtyReason::FsStatusOutdated(self.fs_status.clone());
1224        }
1225
1226        // This typically means some filesystem modifications happened or
1227        // something transitive was odd. In general we should strive to provide
1228        // a better error message than this, so if you see this message a lot it
1229        // likely means this method needs to be updated!
1230        DirtyReason::NothingObvious
1231    }
1232
1233    /// Dynamically inspect the local filesystem to update the `fs_status` field
1234    /// of this `Fingerprint`.
1235    ///
1236    /// This function is used just after a `Fingerprint` is constructed to check
1237    /// the local state of the filesystem and propagate any dirtiness from
1238    /// dependencies up to this unit as well. This function assumes that the
1239    /// unit starts out as [`FsStatus::Stale`] and then it will optionally switch
1240    /// it to `UpToDate` if it can.
1241    fn check_filesystem(
1242        &mut self,
1243        mtime_cache: &mut HashMap<PathBuf, FileTime>,
1244        checksum_cache: &mut HashMap<PathBuf, Checksum>,
1245        pkg: &Package,
1246        build_root: &Path,
1247        cargo_exe: &Path,
1248        gctx: &GlobalContext,
1249    ) -> CargoResult<()> {
1250        assert!(!self.fs_status.up_to_date());
1251
1252        let pkg_root = pkg.root();
1253        let mut mtimes = HashMap::default();
1254
1255        // Get the `mtime` of all outputs. Optionally update their mtime
1256        // afterwards based on the `mtime_on_use` flag. Afterwards we want the
1257        // minimum mtime as it's the one we'll be comparing to inputs and
1258        // dependencies.
1259        for output in self.outputs.iter() {
1260            let Ok(mtime) = paths::mtime(output) else {
1261                // This path failed to report its `mtime`. It probably doesn't
1262                // exists, so leave ourselves as stale and bail out.
1263                let item = StaleItem::FailedToReadMetadata {
1264                    path: output.clone(),
1265                };
1266                self.fs_status = FsStatus::StaleItem(item);
1267                return Ok(());
1268            };
1269            assert!(mtimes.insert(output.clone(), mtime).is_none());
1270        }
1271
1272        let opt_max = mtimes.iter().max_by_key(|kv| kv.1);
1273        let Some((max_path, max_mtime)) = opt_max else {
1274            // We had no output files. This means we're an overridden build
1275            // script and we're just always up to date because we aren't
1276            // watching the filesystem.
1277            self.fs_status = FsStatus::UpToDate { mtimes };
1278            return Ok(());
1279        };
1280        debug!(
1281            "max output mtime for {:?} is {:?} {}",
1282            pkg_root, max_path, max_mtime
1283        );
1284
1285        for dep in self.deps.iter() {
1286            let dep_mtimes = match &dep.fingerprint.fs_status {
1287                FsStatus::UpToDate { mtimes } => mtimes,
1288                // If our dependency is stale, so are we, so bail out.
1289                FsStatus::Stale
1290                | FsStatus::StaleItem(_)
1291                | FsStatus::StaleDependency { .. }
1292                | FsStatus::StaleDepFingerprint { .. } => {
1293                    self.fs_status = FsStatus::StaleDepFingerprint {
1294                        unit: dep.fingerprint.index,
1295                    };
1296                    return Ok(());
1297                }
1298            };
1299
1300            // If our dependency edge only requires the rmeta file to be present
1301            // then we only need to look at that one output file, otherwise we
1302            // need to consider all output files to see if we're out of date.
1303            let (dep_path, dep_mtime) = if dep.only_requires_rmeta {
1304                dep_mtimes
1305                    .iter()
1306                    .find(|(path, _mtime)| {
1307                        path.extension().and_then(|s| s.to_str()) == Some("rmeta")
1308                    })
1309                    .expect("failed to find rmeta")
1310            } else {
1311                match dep_mtimes.iter().max_by_key(|kv| kv.1) {
1312                    Some(dep_mtime) => dep_mtime,
1313                    // If our dependencies is up to date and has no filesystem
1314                    // interactions, then we can move on to the next dependency.
1315                    None => continue,
1316                }
1317            };
1318            debug!(
1319                "max dep mtime for {:?} is {:?} {}",
1320                pkg_root, dep_path, dep_mtime
1321            );
1322
1323            // If the dependency is newer than our own output then it was
1324            // recompiled previously. We transitively become stale ourselves in
1325            // that case, so bail out.
1326            //
1327            // Note that this comparison should probably be `>=`, not `>`, but
1328            // for a discussion of why it's `>` see the discussion about #5918
1329            // below in `find_stale`.
1330            if dep_mtime > max_mtime {
1331                info!(
1332                    "dependency on `{}` is newer than we are {} > {} {:?}",
1333                    dep.name, dep_mtime, max_mtime, pkg_root
1334                );
1335
1336                self.fs_status = FsStatus::StaleDependency {
1337                    unit: dep.fingerprint.index,
1338                    dep_mtime: *dep_mtime,
1339                    max_mtime: *max_mtime,
1340                };
1341
1342                return Ok(());
1343            }
1344        }
1345
1346        // If we reached this far then all dependencies are up to date. Check
1347        // all our `LocalFingerprint` information to see if we have any stale
1348        // files for this package itself. If we do find something log a helpful
1349        // message and bail out so we stay stale.
1350        for local in self.local.get_mut().unwrap().iter() {
1351            if let Some(item) = local.find_stale_item(
1352                mtime_cache,
1353                checksum_cache,
1354                pkg,
1355                build_root,
1356                cargo_exe,
1357                gctx,
1358            )? {
1359                item.log();
1360                self.fs_status = FsStatus::StaleItem(item);
1361                return Ok(());
1362            }
1363        }
1364
1365        // Everything was up to date! Record such.
1366        self.fs_status = FsStatus::UpToDate { mtimes };
1367        debug!("filesystem up-to-date {:?}", pkg_root);
1368
1369        Ok(())
1370    }
1371}
1372
1373impl hash::Hash for Fingerprint {
1374    fn hash<H: Hasher>(&self, h: &mut H) {
1375        let Fingerprint {
1376            rustc,
1377            ref features,
1378            ref declared_features,
1379            target,
1380            path,
1381            profile,
1382            ref deps,
1383            ref local,
1384            config,
1385            compile_kind,
1386            ref rustflags,
1387            ..
1388        } = *self;
1389        let local = local.lock().unwrap();
1390        (
1391            rustc,
1392            features,
1393            declared_features,
1394            target,
1395            path,
1396            profile,
1397            &*local,
1398            config,
1399            compile_kind,
1400            rustflags,
1401        )
1402            .hash(h);
1403
1404        h.write_usize(deps.len());
1405        for DepFingerprint {
1406            pkg_id,
1407            name,
1408            public,
1409            fingerprint,
1410            only_requires_rmeta: _, // static property, no need to hash
1411        } in deps
1412        {
1413            pkg_id.hash(h);
1414            name.hash(h);
1415            public.hash(h);
1416            // use memoized dep hashes to avoid exponential blowup
1417            h.write_u64(fingerprint.hash_u64());
1418        }
1419    }
1420}
1421
1422impl DepFingerprint {
1423    fn new(
1424        build_runner: &mut BuildRunner<'_, '_>,
1425        parent: &Unit,
1426        dep: &UnitDep,
1427    ) -> CargoResult<DepFingerprint> {
1428        let fingerprint = calculate(build_runner, &dep.unit)?;
1429        // We need to be careful about what we hash here. We have a goal of
1430        // supporting renaming a project directory and not rebuilding
1431        // everything. To do that, however, we need to make sure that the cwd
1432        // doesn't make its way into any hashes, and one source of that is the
1433        // `SourceId` for `path` packages.
1434        //
1435        // We already have a requirement that `path` packages all have unique
1436        // names (sort of for this same reason), so if the package source is a
1437        // `path` then we just hash the name, but otherwise we hash the full
1438        // id as it won't change when the directory is renamed.
1439        let pkg_id = if dep.unit.pkg.package_id().source_id().is_path() {
1440            util::hash_u64(dep.unit.pkg.package_id().name())
1441        } else {
1442            util::hash_u64(dep.unit.pkg.package_id())
1443        };
1444
1445        Ok(DepFingerprint {
1446            pkg_id,
1447            name: dep.extern_crate_name,
1448            public: dep.public,
1449            fingerprint,
1450            only_requires_rmeta: build_runner.only_requires_rmeta(parent, &dep.unit),
1451        })
1452    }
1453}
1454
1455impl StaleItem {
1456    /// Use the `log` crate to log a hopefully helpful message in diagnosing
1457    /// what file is considered stale and why. This is intended to be used in
1458    /// conjunction with `CARGO_LOG` to determine why Cargo is recompiling
1459    /// something. Currently there's no user-facing usage of this other than
1460    /// that.
1461    fn log(&self) {
1462        match self {
1463            StaleItem::MissingFile { path } => {
1464                info!("stale: missing {:?}", path);
1465            }
1466            StaleItem::UnableToReadFile { path } => {
1467                info!("stale: unable to read {:?}", path);
1468            }
1469            StaleItem::FailedToReadMetadata { path } => {
1470                info!("stale: couldn't read metadata {:?}", path);
1471            }
1472            StaleItem::ChangedFile {
1473                reference,
1474                reference_mtime,
1475                stale,
1476                stale_mtime,
1477            } => {
1478                info!("stale: changed {:?}", stale);
1479                info!("          (vs) {:?}", reference);
1480                info!("               {:?} < {:?}", reference_mtime, stale_mtime);
1481            }
1482            StaleItem::FileSizeChanged {
1483                path,
1484                new_size,
1485                old_size,
1486            } => {
1487                info!("stale: changed {:?}", path);
1488                info!("prior file size {old_size}");
1489                info!("  new file size {new_size}");
1490            }
1491            StaleItem::ChangedChecksum {
1492                source,
1493                stored_checksum,
1494                new_checksum,
1495            } => {
1496                info!("stale: changed {:?}", source);
1497                info!("prior checksum {stored_checksum}");
1498                info!("  new checksum {new_checksum}");
1499            }
1500            StaleItem::MissingChecksum { path } => {
1501                info!("stale: no prior checksum {:?}", path);
1502            }
1503            StaleItem::ChangedEnv {
1504                var,
1505                previous,
1506                current,
1507            } => {
1508                info!("stale: changed env {:?}", var);
1509                info!("       {:?} != {:?}", previous, current);
1510            }
1511        }
1512    }
1513}
1514
1515/// Calculates the fingerprint for a [`Unit`].
1516///
1517/// This fingerprint is used by Cargo to learn about when information such as:
1518///
1519/// * A non-path package changes (changes version, changes revision, etc).
1520/// * Any dependency changes
1521/// * The compiler changes
1522/// * The set of features a package is built with changes
1523/// * The profile a target is compiled with changes (e.g., opt-level changes)
1524/// * Any other compiler flags change that will affect the result
1525///
1526/// Information like file modification time is only calculated for path
1527/// dependencies.
1528fn calculate(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Arc<Fingerprint>> {
1529    // This function is slammed quite a lot, so the result is memoized.
1530    if let Some(s) = build_runner.fingerprints.get(unit) {
1531        return Ok(Arc::clone(s));
1532    }
1533    let mut fingerprint = if unit.mode.is_run_custom_build() {
1534        calculate_run_custom_build(build_runner, unit)?
1535    } else if unit.mode.is_doc_test() {
1536        panic!("doc tests do not fingerprint");
1537    } else {
1538        calculate_normal(build_runner, unit)?
1539    };
1540
1541    // After we built the initial `Fingerprint` be sure to update the
1542    // `fs_status` field of it.
1543    let build_root = build_root(build_runner);
1544    let cargo_exe = build_runner.bcx.gctx.cargo_exe()?;
1545    fingerprint.check_filesystem(
1546        &mut build_runner.mtime_cache,
1547        &mut build_runner.checksum_cache,
1548        &unit.pkg,
1549        &build_root,
1550        cargo_exe,
1551        build_runner.bcx.gctx,
1552    )?;
1553
1554    let fingerprint = Arc::new(fingerprint);
1555    build_runner
1556        .fingerprints
1557        .insert(unit.clone(), Arc::clone(&fingerprint));
1558    Ok(fingerprint)
1559}
1560
1561/// Calculate a fingerprint for a "normal" unit, or anything that's not a build
1562/// script. This is an internal helper of [`calculate`], don't call directly.
1563fn calculate_normal(
1564    build_runner: &mut BuildRunner<'_, '_>,
1565    unit: &Unit,
1566) -> CargoResult<Fingerprint> {
1567    let deps = {
1568        // Recursively calculate the fingerprint for all of our dependencies.
1569        //
1570        // Skip fingerprints of binaries because they don't actually induce a
1571        // recompile, they're just dependencies in the sense that they need to be
1572        // built. The only exception here are artifact dependencies,
1573        // which is an actual dependency that needs a recompile.
1574        //
1575        // Create Vec since mutable build_runner is needed in closure.
1576        let deps = Vec::from(build_runner.unit_deps(unit));
1577        let mut deps = deps
1578            .into_iter()
1579            .filter(|dep| !dep.unit.target.is_bin() || dep.unit.artifact.is_true())
1580            .map(|dep| DepFingerprint::new(build_runner, unit, &dep))
1581            .collect::<CargoResult<Vec<_>>>()?;
1582        deps.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id));
1583        deps
1584    };
1585
1586    // Afterwards calculate our own fingerprint information.
1587    let build_root = build_root(build_runner);
1588    let is_any_doc_gen = unit.mode.is_doc() || unit.mode.is_doc_scrape();
1589    let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo;
1590    let local = if is_any_doc_gen && !rustdoc_depinfo_enabled {
1591        // rustdoc does not have dep-info files.
1592        let fingerprint = pkg_fingerprint(build_runner.bcx, &unit.pkg).with_context(|| {
1593            format!(
1594                "failed to determine package fingerprint for documenting {}",
1595                unit.pkg
1596            )
1597        })?;
1598        vec![LocalFingerprint::Precalculated(fingerprint)]
1599    } else {
1600        let dep_info = dep_info_loc(build_runner, unit);
1601        let dep_info = dep_info.strip_prefix(&build_root).unwrap().to_path_buf();
1602        vec![LocalFingerprint::CheckDepInfo {
1603            dep_info,
1604            checksum: build_runner.bcx.gctx.cli_unstable().checksum_freshness,
1605        }]
1606    };
1607
1608    // Figure out what the outputs of our unit is, and we'll be storing them
1609    // into the fingerprint as well.
1610    let outputs = build_runner
1611        .outputs(unit)?
1612        .iter()
1613        .filter(|output| {
1614            !matches!(
1615                output.flavor,
1616                FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom
1617            )
1618        })
1619        .map(|output| output.path.clone())
1620        .collect();
1621
1622    // Fill out a bunch more information that we'll be tracking typically
1623    // hashed to take up less space on disk as we just need to know when things
1624    // change.
1625    let extra_flags = if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
1626        &unit.rustdocflags
1627    } else {
1628        &unit.rustflags
1629    }
1630    .to_vec();
1631
1632    let profile_hash = util::hash_u64((
1633        &unit.profile,
1634        unit.mode,
1635        build_runner.bcx.extra_args_for(unit),
1636        build_runner.lto[unit],
1637        unit.pkg.manifest().lint_rustflags(),
1638    ));
1639    let mut config = StableHasher::new();
1640    let linker = if unit.target.for_host() && !build_runner.bcx.gctx.target_applies_to_host()? {
1641        build_runner.compilation.host_linker()
1642    } else {
1643        build_runner.compilation.target_linker(unit.kind)
1644    };
1645    if let Some(linker) = linker {
1646        linker.hash(&mut config);
1647    }
1648    if unit.mode.is_doc() && build_runner.bcx.gctx.cli_unstable().rustdoc_map {
1649        if let Ok(map) = build_runner.bcx.gctx.doc_extern_map() {
1650            map.hash(&mut config);
1651        }
1652    }
1653    if let Some(allow_features) = &build_runner.bcx.gctx.cli_unstable().allow_features {
1654        allow_features.hash(&mut config);
1655    }
1656    // -Zpublic-dependency changes how library units pass dependency privacy
1657    // to rustc via `--extern`.
1658    (unit.target.is_lib()
1659        && build_runner.unit_deps(unit).iter().any(|dep| !dep.public)
1660        && super::is_public_dependency_enabled(build_runner, unit))
1661    .hash(&mut config);
1662    // -Zno-embed-metadata changes how all units are compiled, and it also changes how we tell
1663    // rustc to link to deps using `--extern`. If it changes, we should rebuild everything.
1664    build_runner
1665        .bcx
1666        .gctx
1667        .cli_unstable()
1668        .no_embed_metadata
1669        .hash(&mut config);
1670
1671    let compile_kind = unit.kind.fingerprint_hash();
1672    let mut declared_features = unit.pkg.summary().features().keys().collect::<Vec<_>>();
1673    declared_features.sort(); // to avoid useless rebuild if the user orders it's features
1674    // differently
1675    Ok(Fingerprint {
1676        rustc: util::hash_u64(&build_runner.bcx.rustc().verbose_version),
1677        target: util::hash_u64(&unit.target),
1678        profile: profile_hash,
1679        // Note that .0 is hashed here, not .1 which is the cwd. That doesn't
1680        // actually affect the output artifact so there's no need to hash it.
1681        path: util::hash_u64(path_args(build_runner.bcx.ws, unit).0),
1682        features: format!("{:?}", unit.features),
1683        declared_features: format!("{declared_features:?}"),
1684        deps,
1685        local: Mutex::new(local),
1686        memoized_hash: Mutex::new(None),
1687        config: Hasher::finish(&config),
1688        compile_kind,
1689        index: build_runner.bcx.unit_to_index[unit],
1690        rustflags: extra_flags,
1691        fs_status: FsStatus::Stale,
1692        outputs,
1693    })
1694}
1695
1696/// Calculate a fingerprint for an "execute a build script" unit.  This is an
1697/// internal helper of [`calculate`], don't call directly.
1698fn calculate_run_custom_build(
1699    build_runner: &mut BuildRunner<'_, '_>,
1700    unit: &Unit,
1701) -> CargoResult<Fingerprint> {
1702    assert!(unit.mode.is_run_custom_build());
1703    // Using the `BuildDeps` information we'll have previously parsed and
1704    // inserted into `build_explicit_deps` built an initial snapshot of the
1705    // `LocalFingerprint` list for this build script. If we previously executed
1706    // the build script this means we'll be watching files and env vars.
1707    // Otherwise if we haven't previously executed it we'll just start watching
1708    // the whole crate.
1709    let (gen_local, overridden) = build_script_local_fingerprints(build_runner, unit)?;
1710    let deps = &build_runner.build_explicit_deps[unit];
1711    let local = (gen_local)(
1712        deps,
1713        Some(&|| {
1714            const IO_ERR_MESSAGE: &str = "\
1715An I/O error happened. Please make sure you can access the file.
1716
1717By default, if your project contains a build script, cargo scans all files in
1718it to determine whether a rebuild is needed. If you don't expect to access the
1719file, specify `rerun-if-changed` in your build script.
1720See https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed for more information.";
1721            pkg_fingerprint(build_runner.bcx, &unit.pkg).map_err(|err| {
1722                let mut message = format!("failed to determine package fingerprint for build script for {}", unit.pkg);
1723                if err.root_cause().is::<io::Error>() {
1724                    message = format!("{}\n{}", message, IO_ERR_MESSAGE)
1725                }
1726                err.context(message)
1727            })
1728        }),
1729    )?
1730    .unwrap();
1731    let output = deps.build_script_output.clone();
1732
1733    // Include any dependencies of our execution, which is typically just the
1734    // compilation of the build script itself. (if the build script changes we
1735    // should be rerun!). Note though that if we're an overridden build script
1736    // we have no dependencies so no need to recurse in that case.
1737    let deps = if overridden {
1738        // Overridden build scripts don't need to track deps.
1739        vec![]
1740    } else {
1741        // Create Vec since mutable build_runner is needed in closure.
1742        let deps = Vec::from(build_runner.unit_deps(unit));
1743        deps.into_iter()
1744            .map(|dep| DepFingerprint::new(build_runner, unit, &dep))
1745            .collect::<CargoResult<Vec<_>>>()?
1746    };
1747
1748    let rustflags = unit.rustflags.to_vec();
1749
1750    Ok(Fingerprint {
1751        local: Mutex::new(local),
1752        rustc: util::hash_u64(&build_runner.bcx.rustc().verbose_version),
1753        deps,
1754        outputs: if overridden { Vec::new() } else { vec![output] },
1755        rustflags,
1756        index: build_runner.bcx.unit_to_index[unit],
1757
1758        // Most of the other info is blank here as we don't really include it
1759        // in the execution of the build script, but... this may be a latent
1760        // bug in Cargo.
1761        ..Fingerprint::new()
1762    })
1763}
1764
1765/// Get ready to compute the [`LocalFingerprint`] values
1766/// for a [`RunCustomBuild`] unit.
1767///
1768/// This function has, what's on the surface, a seriously wonky interface.
1769/// You'll call this function and it'll return a closure and a boolean. The
1770/// boolean is pretty simple in that it indicates whether the `unit` has been
1771/// overridden via `.cargo/config.toml`. The closure is much more complicated.
1772///
1773/// This closure is intended to capture any local state necessary to compute
1774/// the `LocalFingerprint` values for this unit. It is `Send` and `'static` to
1775/// be sent to other threads as well (such as when we're executing build
1776/// scripts). That deduplication is the rationale for the closure at least.
1777///
1778/// The arguments to the closure are a bit weirder, though, and I'll apologize
1779/// in advance for the weirdness too. The first argument to the closure is a
1780/// `&BuildDeps`. This is the parsed version of a build script, and when Cargo
1781/// starts up this is cached from previous runs of a build script.  After a
1782/// build script executes the output file is reparsed and passed in here.
1783///
1784/// The second argument is the weirdest, it's *optionally* a closure to
1785/// call [`pkg_fingerprint`]. The `pkg_fingerprint` requires access to
1786/// "source map" located in `Context`. That's very non-`'static` and
1787/// non-`Send`, so it can't be used on other threads, such as when we invoke
1788/// this after a build script has finished. The `Option` allows us to for sure
1789/// calculate it on the main thread at the beginning, and then swallow the bug
1790/// for now where a worker thread after a build script has finished doesn't
1791/// have access. Ideally there would be no second argument or it would be more
1792/// "first class" and not an `Option` but something that can be sent between
1793/// threads. In any case, it's a bug for now.
1794///
1795/// This isn't the greatest of interfaces, and if there's suggestions to
1796/// improve please do so!
1797///
1798/// FIXME(#6779) - see all the words above
1799///
1800/// [`RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild
1801fn build_script_local_fingerprints(
1802    build_runner: &mut BuildRunner<'_, '_>,
1803    unit: &Unit,
1804) -> CargoResult<(
1805    Box<
1806        dyn FnOnce(
1807                &BuildDeps,
1808                Option<&dyn Fn() -> CargoResult<String>>,
1809            ) -> CargoResult<Option<Vec<LocalFingerprint>>>
1810            + Send,
1811    >,
1812    bool,
1813)> {
1814    assert!(unit.mode.is_run_custom_build());
1815    // First up, if this build script is entirely overridden, then we just
1816    // return the hash of what we overrode it with. This is the easy case!
1817    if let Some(fingerprint) = build_script_override_fingerprint(build_runner, unit) {
1818        debug!("override local fingerprints deps {}", unit.pkg);
1819        return Ok((
1820            Box::new(
1821                move |_: &BuildDeps, _: Option<&dyn Fn() -> CargoResult<String>>| {
1822                    Ok(Some(vec![fingerprint]))
1823                },
1824            ),
1825            true, // this is an overridden build script
1826        ));
1827    }
1828
1829    // ... Otherwise this is a "real" build script and we need to return a real
1830    // closure. Our returned closure classifies the build script based on
1831    // whether it prints `rerun-if-*`. If it *doesn't* print this it's where the
1832    // magical second argument comes into play, which fingerprints a whole
1833    // package. Remember that the fact that this is an `Option` is a bug, but a
1834    // longstanding bug, in Cargo. Recent refactorings just made it painfully
1835    // obvious.
1836    let pkg_root = unit.pkg.root().to_path_buf();
1837    let build_dir = build_root(build_runner);
1838    let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?);
1839    let calculate =
1840        move |deps: &BuildDeps, pkg_fingerprint: Option<&dyn Fn() -> CargoResult<String>>| {
1841            if deps.rerun_if_changed.is_empty() && deps.rerun_if_env_changed.is_empty() {
1842                match pkg_fingerprint {
1843                    // FIXME: this is somewhat buggy with respect to docker and
1844                    // weird filesystems. The `Precalculated` variant
1845                    // constructed below will, for `path` dependencies, contain
1846                    // a stringified version of the mtime for the local crate.
1847                    // This violates one of the things we describe in this
1848                    // module's doc comment, never hashing mtimes. We should
1849                    // figure out a better scheme where a package fingerprint
1850                    // may be a string (like for a registry) or a list of files
1851                    // (like for a path dependency). Those list of files would
1852                    // be stored here rather than the mtime of them.
1853                    Some(f) => {
1854                        let s = f()?;
1855                        debug!(
1856                            "old local fingerprints deps {:?} precalculated={:?}",
1857                            pkg_root, s
1858                        );
1859                        return Ok(Some(vec![LocalFingerprint::Precalculated(s)]));
1860                    }
1861                    None => return Ok(None),
1862                }
1863            }
1864
1865            // Ok so now we're in "new mode" where we can have files listed as
1866            // dependencies as well as env vars listed as dependencies. Process
1867            // them all here.
1868            Ok(Some(local_fingerprints_deps(
1869                deps,
1870                &build_dir,
1871                &pkg_root,
1872                &env_config,
1873            )))
1874        };
1875
1876    // Note that `false` == "not overridden"
1877    Ok((Box::new(calculate), false))
1878}
1879
1880/// Create a [`LocalFingerprint`] for an overridden build script.
1881/// Returns None if it is not overridden.
1882fn build_script_override_fingerprint(
1883    build_runner: &mut BuildRunner<'_, '_>,
1884    unit: &Unit,
1885) -> Option<LocalFingerprint> {
1886    // Build script output is only populated at this stage when it is
1887    // overridden.
1888    let build_script_outputs = build_runner.build_script_outputs.lock().unwrap();
1889    let metadata = build_runner.get_run_build_script_metadata(unit);
1890    // Returns None if it is not overridden.
1891    let output = build_script_outputs.get(metadata)?;
1892    let s = format!(
1893        "overridden build state with hash: {}",
1894        util::hash_u64(output)
1895    );
1896    Some(LocalFingerprint::Precalculated(s))
1897}
1898
1899/// Compute the [`LocalFingerprint`] values for a [`RunCustomBuild`] unit for
1900/// non-overridden new-style build scripts only. This is only used when `deps`
1901/// is already known to have a nonempty `rerun-if-*` somewhere.
1902///
1903/// [`RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild
1904fn local_fingerprints_deps(
1905    deps: &BuildDeps,
1906    build_root: &Path,
1907    pkg_root: &Path,
1908    env_config: &Arc<HashMap<String, OsString>>,
1909) -> Vec<LocalFingerprint> {
1910    debug!("new local fingerprints deps {:?}", pkg_root);
1911    let mut local = Vec::new();
1912
1913    if !deps.rerun_if_changed.is_empty() {
1914        // Note that like the module comment above says we are careful to never
1915        // store an absolute path in `LocalFingerprint`, so ensure that we strip
1916        // absolute prefixes from them.
1917        let output = deps
1918            .build_script_output
1919            .strip_prefix(build_root)
1920            .unwrap()
1921            .to_path_buf();
1922        let paths = deps
1923            .rerun_if_changed
1924            .iter()
1925            .map(|p| p.strip_prefix(pkg_root).unwrap_or(p).to_path_buf())
1926            .collect();
1927        local.push(LocalFingerprint::RerunIfChanged { output, paths });
1928    }
1929
1930    local.extend(
1931        deps.rerun_if_env_changed
1932            .iter()
1933            .map(|s| LocalFingerprint::from_env(s, env_config)),
1934    );
1935
1936    local
1937}
1938
1939/// Writes the short fingerprint hash value to `<loc>`
1940/// and logs detailed JSON information to `<loc>.json`.
1941fn write_fingerprint(loc: &Path, fingerprint: &Fingerprint) -> CargoResult<()> {
1942    debug_assert_ne!(fingerprint.rustc, 0);
1943    // fingerprint::new().rustc == 0, make sure it doesn't make it to the file system.
1944    // This is mostly so outside tools can reliably find out what rust version this file is for,
1945    // as we can use the full hash.
1946    let hash = fingerprint.hash_u64();
1947    debug!("write fingerprint ({:x}) : {}", hash, loc.display());
1948    paths::write(loc, util::to_hex(hash).as_bytes())?;
1949
1950    let json = serde_json::to_string(fingerprint).unwrap();
1951    if cfg!(debug_assertions) {
1952        let f: Fingerprint = serde_json::from_str(&json).unwrap();
1953        assert_eq!(f.hash_u64(), hash);
1954    }
1955    paths::write(&loc.with_extension("json"), json.as_bytes())?;
1956    Ok(())
1957}
1958
1959/// Prepare for work when a package starts to build
1960pub fn prepare_init(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<()> {
1961    let new1 = build_runner.files().fingerprint_dir(unit);
1962
1963    // Doc tests have no output, thus no fingerprint.
1964    if !new1.exists() && !unit.mode.is_doc_test() {
1965        paths::create_dir_all(&new1)?;
1966    }
1967
1968    Ok(())
1969}
1970
1971/// Returns the location that the dep-info file will show up at
1972/// for the [`Unit`] specified.
1973pub fn dep_info_loc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> PathBuf {
1974    build_runner.files().fingerprint_file_path(unit, "dep-")
1975}
1976
1977/// Returns an absolute path that build directory.
1978/// All paths are rewritten to be relative to this.
1979fn build_root(build_runner: &BuildRunner<'_, '_>) -> PathBuf {
1980    build_runner.bcx.ws.build_dir().into_path_unlocked()
1981}
1982
1983/// Reads the value from the old fingerprint hash file and compare.
1984///
1985/// If dirty, it then restores the detailed information
1986/// from the fingerprint JSON file, and provides an rich dirty reason.
1987fn compare_old_fingerprint(
1988    unit: &Unit,
1989    old_hash_path: &Path,
1990    new_fingerprint: &Fingerprint,
1991    mtime_on_use: bool,
1992    forced: bool,
1993) -> FingerprintComparison {
1994    if mtime_on_use {
1995        // update the mtime so other cleaners know we used it
1996        let t = FileTime::from_system_time(SystemTime::now());
1997        debug!("mtime-on-use forcing {:?} to {}", old_hash_path, t);
1998        paths::set_file_time_no_err(old_hash_path, t);
1999    }
2000
2001    let compare = _compare_old_fingerprint(old_hash_path, new_fingerprint);
2002
2003    match compare.as_ref() {
2004        Ok(FingerprintComparison::Fresh) => {}
2005        Ok(FingerprintComparison::Dirty { reason }) => {
2006            info!(
2007                "fingerprint dirty for {}/{:?}/{:?}",
2008                unit.pkg, unit.mode, unit.target,
2009            );
2010            info!("    dirty: {reason:?}");
2011        }
2012        Err(e) => {
2013            info!(
2014                "fingerprint error for {}/{:?}/{:?}",
2015                unit.pkg, unit.mode, unit.target,
2016            );
2017            info!("    err: {e:?}");
2018        }
2019    }
2020
2021    match compare {
2022        Ok(FingerprintComparison::Fresh) if forced => FingerprintComparison::Dirty {
2023            reason: DirtyReason::Forced,
2024        },
2025        Ok(cmp) => cmp,
2026        Err(_) => FingerprintComparison::Dirty {
2027            reason: DirtyReason::FreshBuild,
2028        },
2029    }
2030}
2031
2032fn _compare_old_fingerprint(
2033    old_hash_path: &Path,
2034    new_fingerprint: &Fingerprint,
2035) -> CargoResult<FingerprintComparison> {
2036    let old_fingerprint_short = paths::read(old_hash_path)?;
2037
2038    let new_hash = new_fingerprint.hash_u64();
2039
2040    if util::to_hex(new_hash) == old_fingerprint_short && new_fingerprint.fs_status.up_to_date() {
2041        return Ok(FingerprintComparison::Fresh);
2042    }
2043
2044    let old_fingerprint_json = paths::read(&old_hash_path.with_extension("json"))?;
2045    let old_fingerprint: Fingerprint = serde_json::from_str(&old_fingerprint_json)
2046        .with_context(|| internal("failed to deserialize json"))?;
2047    // Fingerprint can be empty after a failed rebuild (see comment in prepare_target).
2048    if !old_fingerprint_short.is_empty() {
2049        debug_assert_eq!(
2050            util::to_hex(old_fingerprint.hash_u64()),
2051            old_fingerprint_short
2052        );
2053    }
2054
2055    let reason = new_fingerprint.compare(&old_fingerprint);
2056    Ok(FingerprintComparison::Dirty { reason })
2057}
2058
2059/// Calculates the fingerprint of a unit thats contains no dep-info files.
2060fn pkg_fingerprint(bcx: &BuildContext<'_, '_>, pkg: &Package) -> CargoResult<String> {
2061    let source_id = pkg.package_id().source_id();
2062    let sources = bcx.packages.sources();
2063
2064    let source = sources
2065        .get(source_id)
2066        .ok_or_else(|| internal("missing package source"))?;
2067    source.fingerprint(pkg)
2068}
2069
2070/// The `reference` file is considered as "stale" if any file from `paths` has a newer mtime.
2071fn find_stale_file<I, P>(
2072    mtime_cache: &mut HashMap<PathBuf, FileTime>,
2073    checksum_cache: &mut HashMap<PathBuf, Checksum>,
2074    reference: &Path,
2075    paths: I,
2076    use_checksums: bool,
2077) -> Option<StaleItem>
2078where
2079    I: IntoIterator<Item = (P, Option<(u64, Checksum)>)>,
2080    P: AsRef<Path>,
2081{
2082    let reference_mtime = match paths::mtime(reference) {
2083        Ok(mtime) => mtime,
2084        Err(..) => {
2085            return Some(StaleItem::MissingFile {
2086                path: reference.to_path_buf(),
2087            });
2088        }
2089    };
2090
2091    let skippable_dirs = if let Ok(cargo_home) = home::cargo_home() {
2092        let skippable_dirs: Vec<_> = ["git", "registry"]
2093            .into_iter()
2094            .map(|subfolder| cargo_home.join(subfolder))
2095            .collect();
2096        Some(skippable_dirs)
2097    } else {
2098        None
2099    };
2100    for (path, prior_checksum) in paths {
2101        let path = path.as_ref();
2102
2103        // Assuming anything in cargo_home/{git, registry} is immutable
2104        // (see also #9455 about marking the src directory readonly) which avoids rebuilds when CI
2105        // caches $CARGO_HOME/registry/{index, cache} and $CARGO_HOME/git/db across runs, keeping
2106        // the content the same but changing the mtime.
2107        if let Some(ref skippable_dirs) = skippable_dirs {
2108            if skippable_dirs.iter().any(|dir| path.starts_with(dir)) {
2109                continue;
2110            }
2111        }
2112        if use_checksums {
2113            let Some((file_len, prior_checksum)) = prior_checksum else {
2114                return Some(StaleItem::MissingChecksum {
2115                    path: path.to_path_buf(),
2116                });
2117            };
2118            let path_buf = path.to_path_buf();
2119
2120            let path_checksum = match checksum_cache.entry(path_buf) {
2121                Entry::Occupied(o) => *o.get(),
2122                Entry::Vacant(v) => {
2123                    let Ok(current_file_len) = fs::metadata(&path).map(|m| m.len()) else {
2124                        return Some(StaleItem::FailedToReadMetadata {
2125                            path: path.to_path_buf(),
2126                        });
2127                    };
2128                    if current_file_len != file_len {
2129                        return Some(StaleItem::FileSizeChanged {
2130                            path: path.to_path_buf(),
2131                            new_size: current_file_len,
2132                            old_size: file_len,
2133                        });
2134                    }
2135                    let Ok(file) = File::open(path) else {
2136                        return Some(StaleItem::MissingFile {
2137                            path: path.to_path_buf(),
2138                        });
2139                    };
2140                    let Ok(checksum) = Checksum::compute(prior_checksum.algo(), file) else {
2141                        return Some(StaleItem::UnableToReadFile {
2142                            path: path.to_path_buf(),
2143                        });
2144                    };
2145                    *v.insert(checksum)
2146                }
2147            };
2148            if path_checksum == prior_checksum {
2149                continue;
2150            }
2151            return Some(StaleItem::ChangedChecksum {
2152                source: path.to_path_buf(),
2153                stored_checksum: prior_checksum,
2154                new_checksum: path_checksum,
2155            });
2156        } else {
2157            let path_mtime = match mtime_cache.entry(path.to_path_buf()) {
2158                Entry::Occupied(o) => *o.get(),
2159                Entry::Vacant(v) => {
2160                    let Ok(mtime) = paths::mtime_recursive(path) else {
2161                        return Some(StaleItem::MissingFile {
2162                            path: path.to_path_buf(),
2163                        });
2164                    };
2165                    *v.insert(mtime)
2166                }
2167            };
2168
2169            // TODO: fix #5918.
2170            // Note that equal mtimes should be considered "stale". For filesystems with
2171            // not much timestamp precision like 1s this is would be a conservative approximation
2172            // to handle the case where a file is modified within the same second after
2173            // a build starts. We want to make sure that incremental rebuilds pick that up!
2174            //
2175            // For filesystems with nanosecond precision it's been seen in the wild that
2176            // its "nanosecond precision" isn't really nanosecond-accurate. It turns out that
2177            // kernels may cache the current time so files created at different times actually
2178            // list the same nanosecond precision. Some digging on #5919 picked up that the
2179            // kernel caches the current time between timer ticks, which could mean that if
2180            // a file is updated at most 10ms after a build starts then Cargo may not
2181            // pick up the build changes.
2182            //
2183            // All in all, an equality check here would be a conservative assumption that,
2184            // if equal, files were changed just after a previous build finished.
2185            // Unfortunately this became problematic when (in #6484) cargo switch to more accurately
2186            // measuring the start time of builds.
2187            if path_mtime <= reference_mtime {
2188                continue;
2189            }
2190
2191            return Some(StaleItem::ChangedFile {
2192                reference: reference.to_path_buf(),
2193                reference_mtime,
2194                stale: path.to_path_buf(),
2195                stale_mtime: path_mtime,
2196            });
2197        }
2198    }
2199
2200    debug!(
2201        "all paths up-to-date relative to {:?} mtime={}",
2202        reference, reference_mtime
2203    );
2204    None
2205}