Skip to main content

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