cargo/core/compiler/build_runner/
compilation_files.rs

1//! See [`CompilationFiles`].
2
3use std::collections::HashMap;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9use lazycell::LazyCell;
10use tracing::debug;
11
12use super::{BuildContext, BuildRunner, CompileKind, FileFlavor, Layout};
13use crate::core::compiler::{CompileMode, CompileTarget, CrateType, FileType, Unit};
14use crate::core::{Target, TargetKind, Workspace};
15use crate::util::{self, CargoResult, StableHasher};
16
17/// This is a generic version number that can be changed to make
18/// backwards-incompatible changes to any file structures in the output
19/// directory. For example, the fingerprint files or the build-script
20/// output files.
21///
22/// Normally cargo updates ship with rustc updates which will
23/// cause a new hash due to the rustc version changing, but this allows
24/// cargo to be extra careful to deal with different versions of cargo that
25/// use the same rustc version.
26const METADATA_VERSION: u8 = 2;
27
28/// Uniquely identify a [`Unit`] under specific circumstances, see [`Metadata`] for more.
29#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
30pub struct UnitHash(u64);
31
32impl fmt::Display for UnitHash {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(f, "{:016x}", self.0)
35    }
36}
37
38impl fmt::Debug for UnitHash {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "UnitHash({:016x})", self.0)
41    }
42}
43
44/// [`Metadata`] tracks several [`UnitHash`]s, including
45/// [`Metadata::unit_id`], [`Metadata::c_metadata`], and [`Metadata::c_extra_filename`].
46///
47/// We use a hash because it is an easy way to guarantee
48/// that all the inputs can be converted to a valid path.
49///
50/// [`Metadata::unit_id`] is used to uniquely identify a unit in the build graph.
51/// This serves as a similar role as [`Metadata::c_extra_filename`] in that it uniquely identifies output
52/// on the filesystem except that its always present.
53///
54/// [`Metadata::c_extra_filename`] is needed for cases like:
55/// - A project may depend on crate `A` and crate `B`, so the package name must be in the file name.
56/// - Similarly a project may depend on two versions of `A`, so the version must be in the file name.
57///
58/// This also acts as the main layer of caching provided by Cargo
59/// so this must include all things that need to be distinguished in different parts of
60/// the same build. This is absolutely required or we override things before
61/// we get chance to use them.
62///
63/// For example, we want to cache `cargo build` and `cargo doc` separately, so that running one
64/// does not invalidate the artifacts for the other. We do this by including [`CompileMode`] in the
65/// hash, thus the artifacts go in different folders and do not override each other.
66/// If we don't add something that we should have, for this reason, we get the
67/// correct output but rebuild more than is needed.
68///
69/// Some things that need to be tracked to ensure the correct output should definitely *not*
70/// go in the `Metadata`. For example, the modification time of a file, should be tracked to make a
71/// rebuild when the file changes. However, it would be wasteful to include in the `Metadata`. The
72/// old artifacts are never going to be needed again. We can save space by just overwriting them.
73/// If we add something that we should not have, for this reason, we get the correct output but take
74/// more space than needed. This makes not including something in `Metadata`
75/// a form of cache invalidation.
76///
77/// Note that the `Fingerprint` is in charge of tracking everything needed to determine if a
78/// rebuild is needed.
79///
80/// [`Metadata::c_metadata`] is used for symbol mangling, because if you have two versions of
81/// the same crate linked together, their symbols need to be differentiated.
82///
83/// You should avoid anything that would interfere with reproducible
84/// builds. For example, *any* absolute path should be avoided. This is one
85/// reason that `RUSTFLAGS` is not in [`Metadata::c_metadata`], because it often has
86/// absolute paths (like `--remap-path-prefix` which is fundamentally used for
87/// reproducible builds and has absolute paths in it). Also, in some cases the
88/// mangled symbols need to be stable between different builds with different
89/// settings. For example, profile-guided optimizations need to swap
90/// `RUSTFLAGS` between runs, but needs to keep the same symbol names.
91#[derive(Copy, Clone, Debug)]
92pub struct Metadata {
93    unit_id: UnitHash,
94    c_metadata: UnitHash,
95    c_extra_filename: Option<UnitHash>,
96}
97
98impl Metadata {
99    /// A hash to identify a given [`Unit`] in the build graph
100    pub fn unit_id(&self) -> UnitHash {
101        self.unit_id
102    }
103
104    /// A hash to add to symbol naming through `-C metadata`
105    pub fn c_metadata(&self) -> UnitHash {
106        self.c_metadata
107    }
108
109    /// A hash to add to file names through `-C extra-filename`
110    pub fn c_extra_filename(&self) -> Option<UnitHash> {
111        self.c_extra_filename
112    }
113}
114
115/// Collection of information about the files emitted by the compiler, and the
116/// output directory structure.
117pub struct CompilationFiles<'a, 'gctx> {
118    /// The target directory layout for the host (and target if it is the same as host).
119    pub(super) host: Layout,
120    /// The target directory layout for the target (if different from then host).
121    pub(super) target: HashMap<CompileTarget, Layout>,
122    /// Additional directory to include a copy of the outputs.
123    export_dir: Option<PathBuf>,
124    /// The root targets requested by the user on the command line (does not
125    /// include dependencies).
126    roots: Vec<Unit>,
127    ws: &'a Workspace<'gctx>,
128    /// Metadata hash to use for each unit.
129    metas: HashMap<Unit, Metadata>,
130    /// For each Unit, a list all files produced.
131    outputs: HashMap<Unit, LazyCell<Arc<Vec<OutputFile>>>>,
132}
133
134/// Info about a single file emitted by the compiler.
135#[derive(Debug)]
136pub struct OutputFile {
137    /// Absolute path to the file that will be produced by the build process.
138    pub path: PathBuf,
139    /// If it should be linked into `target`, and what it should be called
140    /// (e.g., without metadata).
141    pub hardlink: Option<PathBuf>,
142    /// If `--artifact-dir` is specified, the absolute path to the exported file.
143    pub export_path: Option<PathBuf>,
144    /// Type of the file (library / debug symbol / else).
145    pub flavor: FileFlavor,
146}
147
148impl OutputFile {
149    /// Gets the hard link if present; otherwise, returns the path.
150    pub fn bin_dst(&self) -> &PathBuf {
151        match self.hardlink {
152            Some(ref link_dst) => link_dst,
153            None => &self.path,
154        }
155    }
156}
157
158impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> {
159    pub(super) fn new(
160        build_runner: &BuildRunner<'a, 'gctx>,
161        host: Layout,
162        target: HashMap<CompileTarget, Layout>,
163    ) -> CompilationFiles<'a, 'gctx> {
164        let mut metas = HashMap::new();
165        for unit in &build_runner.bcx.roots {
166            metadata_of(unit, build_runner, &mut metas);
167        }
168        let outputs = metas
169            .keys()
170            .cloned()
171            .map(|unit| (unit, LazyCell::new()))
172            .collect();
173        CompilationFiles {
174            ws: build_runner.bcx.ws,
175            host,
176            target,
177            export_dir: build_runner.bcx.build_config.export_dir.clone(),
178            roots: build_runner.bcx.roots.clone(),
179            metas,
180            outputs,
181        }
182    }
183
184    /// Returns the appropriate directory layout for either a plugin or not.
185    pub fn layout(&self, kind: CompileKind) -> &Layout {
186        match kind {
187            CompileKind::Host => &self.host,
188            CompileKind::Target(target) => &self.target[&target],
189        }
190    }
191
192    /// Gets the metadata for the given unit.
193    ///
194    /// See [`Metadata`] and [`fingerprint`] module for more.
195    ///
196    /// [`fingerprint`]: super::super::fingerprint#fingerprints-and-metadata
197    pub fn metadata(&self, unit: &Unit) -> Metadata {
198        self.metas[unit]
199    }
200
201    /// Gets the short hash based only on the `PackageId`.
202    /// Used for the metadata when `metadata` returns `None`.
203    pub fn target_short_hash(&self, unit: &Unit) -> String {
204        let hashable = unit.pkg.package_id().stable_hash(self.ws.root());
205        util::short_hash(&(METADATA_VERSION, hashable))
206    }
207
208    /// Returns the directory where the artifacts for the given unit are
209    /// initially created.
210    pub fn out_dir(&self, unit: &Unit) -> PathBuf {
211        // Docscrape units need to have doc/ set as the out_dir so sources for reverse-dependencies
212        // will be put into doc/ and not into deps/ where the *.examples files are stored.
213        if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
214            self.layout(unit.kind).doc().to_path_buf()
215        } else if unit.mode.is_doc_test() {
216            panic!("doc tests do not have an out dir");
217        } else if unit.target.is_custom_build() {
218            self.build_script_dir(unit)
219        } else if unit.target.is_example() {
220            self.layout(unit.kind).build_examples().to_path_buf()
221        } else if unit.artifact.is_true() {
222            self.artifact_dir(unit)
223        } else {
224            self.deps_dir(unit).to_path_buf()
225        }
226    }
227
228    /// Additional export directory from `--artifact-dir`.
229    pub fn export_dir(&self) -> Option<PathBuf> {
230        self.export_dir.clone()
231    }
232
233    /// Directory name to use for a package in the form `NAME-HASH`.
234    ///
235    /// Note that some units may share the same directory, so care should be
236    /// taken in those cases!
237    fn pkg_dir(&self, unit: &Unit) -> String {
238        let name = unit.pkg.package_id().name();
239        let meta = self.metas[unit];
240        if let Some(c_extra_filename) = meta.c_extra_filename() {
241            format!("{}-{}", name, c_extra_filename)
242        } else {
243            format!("{}-{}", name, self.target_short_hash(unit))
244        }
245    }
246
247    /// Returns the final artifact path for the host (`/…/target/debug`)
248    pub fn host_dest(&self) -> &Path {
249        self.host.dest()
250    }
251
252    /// Returns the root of the build output tree for the host (`/…/target`)
253    pub fn host_root(&self) -> &Path {
254        self.host.root()
255    }
256
257    /// Returns the host `deps` directory path.
258    pub fn host_deps(&self) -> &Path {
259        self.host.deps()
260    }
261
262    /// Returns the directories where Rust crate dependencies are found for the
263    /// specified unit.
264    pub fn deps_dir(&self, unit: &Unit) -> &Path {
265        self.layout(unit.kind).deps()
266    }
267
268    /// Directory where the fingerprint for the given unit should go.
269    pub fn fingerprint_dir(&self, unit: &Unit) -> PathBuf {
270        let dir = self.pkg_dir(unit);
271        self.layout(unit.kind).fingerprint().join(dir)
272    }
273
274    /// Returns the path for a file in the fingerprint directory.
275    ///
276    /// The "prefix" should be something to distinguish the file from other
277    /// files in the fingerprint directory.
278    pub fn fingerprint_file_path(&self, unit: &Unit, prefix: &str) -> PathBuf {
279        // Different targets need to be distinguished in the
280        let kind = unit.target.kind().description();
281        let flavor = if unit.mode.is_any_test() {
282            "test-"
283        } else if unit.mode.is_doc() {
284            "doc-"
285        } else if unit.mode.is_run_custom_build() {
286            "run-"
287        } else {
288            ""
289        };
290        let name = format!("{}{}{}-{}", prefix, flavor, kind, unit.target.name());
291        self.fingerprint_dir(unit).join(name)
292    }
293
294    /// Path where compiler output is cached.
295    pub fn message_cache_path(&self, unit: &Unit) -> PathBuf {
296        self.fingerprint_file_path(unit, "output-")
297    }
298
299    /// Returns the directory where a compiled build script is stored.
300    /// `/path/to/target/{debug,release}/build/PKG-HASH`
301    pub fn build_script_dir(&self, unit: &Unit) -> PathBuf {
302        assert!(unit.target.is_custom_build());
303        assert!(!unit.mode.is_run_custom_build());
304        assert!(self.metas.contains_key(unit));
305        let dir = self.pkg_dir(unit);
306        self.layout(CompileKind::Host).build().join(dir)
307    }
308
309    /// Returns the directory for compiled artifacts files.
310    /// `/path/to/target/{debug,release}/deps/artifact/KIND/PKG-HASH`
311    fn artifact_dir(&self, unit: &Unit) -> PathBuf {
312        assert!(self.metas.contains_key(unit));
313        assert!(unit.artifact.is_true());
314        let dir = self.pkg_dir(unit);
315        let kind = match unit.target.kind() {
316            TargetKind::Bin => "bin",
317            TargetKind::Lib(lib_kinds) => match lib_kinds.as_slice() {
318                &[CrateType::Cdylib] => "cdylib",
319                &[CrateType::Staticlib] => "staticlib",
320                invalid => unreachable!(
321                    "BUG: unexpected artifact library type(s): {:?} - these should have been split",
322                    invalid
323                ),
324            },
325            invalid => unreachable!(
326                "BUG: {:?} are not supposed to be used as artifacts",
327                invalid
328            ),
329        };
330        self.layout(unit.kind).artifact().join(dir).join(kind)
331    }
332
333    /// Returns the directory where information about running a build script
334    /// is stored.
335    /// `/path/to/target/{debug,release}/build/PKG-HASH`
336    pub fn build_script_run_dir(&self, unit: &Unit) -> PathBuf {
337        assert!(unit.target.is_custom_build());
338        assert!(unit.mode.is_run_custom_build());
339        let dir = self.pkg_dir(unit);
340        self.layout(unit.kind).build().join(dir)
341    }
342
343    /// Returns the "`OUT_DIR`" directory for running a build script.
344    /// `/path/to/target/{debug,release}/build/PKG-HASH/out`
345    pub fn build_script_out_dir(&self, unit: &Unit) -> PathBuf {
346        self.build_script_run_dir(unit).join("out")
347    }
348
349    /// Returns the path to the executable binary for the given bin target.
350    ///
351    /// This should only to be used when a `Unit` is not available.
352    pub fn bin_link_for_target(
353        &self,
354        target: &Target,
355        kind: CompileKind,
356        bcx: &BuildContext<'_, '_>,
357    ) -> CargoResult<PathBuf> {
358        assert!(target.is_bin());
359        let dest = self.layout(kind).dest();
360        let info = bcx.target_data.info(kind);
361        let (file_types, _) = info
362            .rustc_outputs(
363                CompileMode::Build,
364                &TargetKind::Bin,
365                bcx.target_data.short_name(&kind),
366            )
367            .expect("target must support `bin`");
368
369        let file_type = file_types
370            .iter()
371            .find(|file_type| file_type.flavor == FileFlavor::Normal)
372            .expect("target must support `bin`");
373
374        Ok(dest.join(file_type.uplift_filename(target)))
375    }
376
377    /// Returns the filenames that the given unit will generate.
378    ///
379    /// Note: It is not guaranteed that all of the files will be generated.
380    pub(super) fn outputs(
381        &self,
382        unit: &Unit,
383        bcx: &BuildContext<'a, 'gctx>,
384    ) -> CargoResult<Arc<Vec<OutputFile>>> {
385        self.outputs[unit]
386            .try_borrow_with(|| self.calc_outputs(unit, bcx))
387            .map(Arc::clone)
388    }
389
390    /// Returns the path where the output for the given unit and `FileType`
391    /// should be uplifted to.
392    ///
393    /// Returns `None` if the unit shouldn't be uplifted (for example, a
394    /// dependent rlib).
395    fn uplift_to(&self, unit: &Unit, file_type: &FileType, from_path: &Path) -> Option<PathBuf> {
396        // Tests, check, doc, etc. should not be uplifted.
397        if unit.mode != CompileMode::Build || file_type.flavor == FileFlavor::Rmeta {
398            return None;
399        }
400
401        // Artifact dependencies are never uplifted.
402        if unit.artifact.is_true() {
403            return None;
404        }
405
406        // - Binaries: The user always wants to see these, even if they are
407        //   implicitly built (for example for integration tests).
408        // - dylibs: This ensures that the dynamic linker pulls in all the
409        //   latest copies (even if the dylib was built from a previous cargo
410        //   build). There are complex reasons for this, see #8139, #6167, #6162.
411        // - Things directly requested from the command-line (the "roots").
412        //   This one is a little questionable for rlibs (see #6131), but is
413        //   historically how Cargo has operated. This is primarily useful to
414        //   give the user access to staticlibs and cdylibs.
415        if !unit.target.is_bin()
416            && !unit.target.is_custom_build()
417            && file_type.crate_type != Some(CrateType::Dylib)
418            && !self.roots.contains(unit)
419        {
420            return None;
421        }
422
423        let filename = file_type.uplift_filename(&unit.target);
424        let uplift_path = if unit.target.is_example() {
425            // Examples live in their own little world.
426            self.layout(unit.kind).examples().join(filename)
427        } else if unit.target.is_custom_build() {
428            self.build_script_dir(unit).join(filename)
429        } else {
430            self.layout(unit.kind).dest().join(filename)
431        };
432        if from_path == uplift_path {
433            // This can happen with things like examples that reside in the
434            // same directory, do not have a metadata hash (like on Windows),
435            // and do not have hyphens.
436            return None;
437        }
438        Some(uplift_path)
439    }
440
441    /// Calculates the filenames that the given unit will generate.
442    /// Should use [`CompilationFiles::outputs`] instead
443    /// as it caches the result of this function.
444    fn calc_outputs(
445        &self,
446        unit: &Unit,
447        bcx: &BuildContext<'a, 'gctx>,
448    ) -> CargoResult<Arc<Vec<OutputFile>>> {
449        let ret = match unit.mode {
450            CompileMode::Doc { json, .. } => {
451                let path = if json {
452                    self.out_dir(unit)
453                        .join(format!("{}.json", unit.target.crate_name()))
454                } else {
455                    self.out_dir(unit)
456                        .join(unit.target.crate_name())
457                        .join("index.html")
458                };
459
460                vec![OutputFile {
461                    path,
462                    hardlink: None,
463                    export_path: None,
464                    flavor: FileFlavor::Normal,
465                }]
466            }
467            CompileMode::RunCustomBuild => {
468                // At this time, this code path does not handle build script
469                // outputs.
470                vec![]
471            }
472            CompileMode::Doctest => {
473                // Doctests are built in a temporary directory and then
474                // deleted. There is the `--persist-doctests` unstable flag,
475                // but Cargo does not know about that.
476                vec![]
477            }
478            CompileMode::Docscrape => {
479                // The file name needs to be stable across Cargo sessions.
480                // This originally used unit.buildkey(), but that isn't stable,
481                // so we use metadata instead (prefixed with name for debugging).
482                let file_name = format!(
483                    "{}-{}.examples",
484                    unit.pkg.name(),
485                    self.metadata(unit).unit_id()
486                );
487                let path = self.deps_dir(unit).join(file_name);
488                vec![OutputFile {
489                    path,
490                    hardlink: None,
491                    export_path: None,
492                    flavor: FileFlavor::Normal,
493                }]
494            }
495            CompileMode::Test
496            | CompileMode::Build
497            | CompileMode::Bench
498            | CompileMode::Check { .. } => {
499                let mut outputs = self.calc_outputs_rustc(unit, bcx)?;
500                if bcx.build_config.sbom && bcx.gctx.cli_unstable().sbom {
501                    let sbom_files: Vec<_> = outputs
502                        .iter()
503                        .filter(|o| matches!(o.flavor, FileFlavor::Normal | FileFlavor::Linkable))
504                        .map(|output| OutputFile {
505                            path: Self::append_sbom_suffix(&output.path),
506                            hardlink: output.hardlink.as_ref().map(Self::append_sbom_suffix),
507                            export_path: output.export_path.as_ref().map(Self::append_sbom_suffix),
508                            flavor: FileFlavor::Sbom,
509                        })
510                        .collect();
511                    outputs.extend(sbom_files.into_iter());
512                }
513                outputs
514            }
515        };
516        debug!("Target filenames: {:?}", ret);
517
518        Ok(Arc::new(ret))
519    }
520
521    /// Append the SBOM suffix to the file name.
522    fn append_sbom_suffix(link: &PathBuf) -> PathBuf {
523        const SBOM_FILE_EXTENSION: &str = ".cargo-sbom.json";
524        let mut link_buf = link.clone().into_os_string();
525        link_buf.push(SBOM_FILE_EXTENSION);
526        PathBuf::from(link_buf)
527    }
528
529    /// Computes the actual, full pathnames for all the files generated by rustc.
530    ///
531    /// The `OutputFile` also contains the paths where those files should be
532    /// "uplifted" to.
533    fn calc_outputs_rustc(
534        &self,
535        unit: &Unit,
536        bcx: &BuildContext<'a, 'gctx>,
537    ) -> CargoResult<Vec<OutputFile>> {
538        let out_dir = self.out_dir(unit);
539
540        let info = bcx.target_data.info(unit.kind);
541        let triple = bcx.target_data.short_name(&unit.kind);
542        let (file_types, unsupported) =
543            info.rustc_outputs(unit.mode, unit.target.kind(), triple)?;
544        if file_types.is_empty() {
545            if !unsupported.is_empty() {
546                let unsupported_strs: Vec<_> = unsupported.iter().map(|ct| ct.as_str()).collect();
547                anyhow::bail!(
548                    "cannot produce {} for `{}` as the target `{}` \
549                     does not support these crate types",
550                    unsupported_strs.join(", "),
551                    unit.pkg,
552                    triple,
553                )
554            }
555            anyhow::bail!(
556                "cannot compile `{}` as the target `{}` does not \
557                 support any of the output crate types",
558                unit.pkg,
559                triple,
560            );
561        }
562
563        // Convert FileType to OutputFile.
564        let mut outputs = Vec::new();
565        for file_type in file_types {
566            let meta = self.metas[unit];
567            let meta_opt = meta.c_extra_filename().map(|h| h.to_string());
568            let path = out_dir.join(file_type.output_filename(&unit.target, meta_opt.as_deref()));
569
570            // If, the `different_binary_name` feature is enabled, the name of the hardlink will
571            // be the name of the binary provided by the user in `Cargo.toml`.
572            let hardlink = self.uplift_to(unit, &file_type, &path);
573            let export_path = if unit.target.is_custom_build() {
574                None
575            } else {
576                self.export_dir.as_ref().and_then(|export_dir| {
577                    hardlink
578                        .as_ref()
579                        .map(|hardlink| export_dir.join(hardlink.file_name().unwrap()))
580                })
581            };
582            outputs.push(OutputFile {
583                path,
584                hardlink,
585                export_path,
586                flavor: file_type.flavor,
587            });
588        }
589        Ok(outputs)
590    }
591}
592
593/// Gets the metadata hash for the given [`Unit`].
594///
595/// When a metadata hash doesn't exist for the given unit,
596/// this calls itself recursively to compute metadata hashes of all its dependencies.
597/// See [`compute_metadata`] for how a single metadata hash is computed.
598fn metadata_of<'a>(
599    unit: &Unit,
600    build_runner: &BuildRunner<'_, '_>,
601    metas: &'a mut HashMap<Unit, Metadata>,
602) -> &'a Metadata {
603    if !metas.contains_key(unit) {
604        let meta = compute_metadata(unit, build_runner, metas);
605        metas.insert(unit.clone(), meta);
606        for dep in build_runner.unit_deps(unit) {
607            metadata_of(&dep.unit, build_runner, metas);
608        }
609    }
610    &metas[unit]
611}
612
613/// Computes the metadata hash for the given [`Unit`].
614fn compute_metadata(
615    unit: &Unit,
616    build_runner: &BuildRunner<'_, '_>,
617    metas: &mut HashMap<Unit, Metadata>,
618) -> Metadata {
619    let bcx = &build_runner.bcx;
620    let deps_metadata = build_runner
621        .unit_deps(unit)
622        .iter()
623        .map(|dep| *metadata_of(&dep.unit, build_runner, metas))
624        .collect::<Vec<_>>();
625    let use_extra_filename = use_extra_filename(bcx, unit);
626
627    let mut shared_hasher = StableHasher::new();
628
629    METADATA_VERSION.hash(&mut shared_hasher);
630
631    let ws_root = if unit.is_std {
632        // SourceId for stdlib crates is an absolute path inside the sysroot.
633        // Pass the sysroot as workspace root so that we hash a relative path.
634        // This avoids the metadata hash changing depending on where the user installed rustc.
635        &bcx.target_data.get_info(unit.kind).unwrap().sysroot
636    } else {
637        bcx.ws.root()
638    };
639
640    // Unique metadata per (name, source, version) triple. This'll allow us
641    // to pull crates from anywhere without worrying about conflicts.
642    unit.pkg
643        .package_id()
644        .stable_hash(ws_root)
645        .hash(&mut shared_hasher);
646
647    // Also mix in enabled features to our metadata. This'll ensure that
648    // when changing feature sets each lib is separately cached.
649    unit.features.hash(&mut shared_hasher);
650
651    // Throw in the profile we're compiling with. This helps caching
652    // `panic=abort` and `panic=unwind` artifacts, additionally with various
653    // settings like debuginfo and whatnot.
654    unit.profile.hash(&mut shared_hasher);
655    unit.mode.hash(&mut shared_hasher);
656    build_runner.lto[unit].hash(&mut shared_hasher);
657
658    // Artifacts compiled for the host should have a different
659    // metadata piece than those compiled for the target, so make sure
660    // we throw in the unit's `kind` as well.  Use `fingerprint_hash`
661    // so that the StableHash doesn't change based on the pathnames
662    // of the custom target JSON spec files.
663    unit.kind.fingerprint_hash().hash(&mut shared_hasher);
664
665    // Finally throw in the target name/kind. This ensures that concurrent
666    // compiles of targets in the same crate don't collide.
667    unit.target.name().hash(&mut shared_hasher);
668    unit.target.kind().hash(&mut shared_hasher);
669
670    hash_rustc_version(bcx, &mut shared_hasher, unit);
671
672    if build_runner.bcx.ws.is_member(&unit.pkg) {
673        // This is primarily here for clippy. This ensures that the clippy
674        // artifacts are separate from the `check` ones.
675        if let Some(path) = &build_runner.bcx.rustc().workspace_wrapper {
676            path.hash(&mut shared_hasher);
677        }
678    }
679
680    // Seed the contents of `__CARGO_DEFAULT_LIB_METADATA` to the hasher if present.
681    // This should be the release channel, to get a different hash for each channel.
682    if let Ok(ref channel) = build_runner
683        .bcx
684        .gctx
685        .get_env("__CARGO_DEFAULT_LIB_METADATA")
686    {
687        channel.hash(&mut shared_hasher);
688    }
689
690    // std units need to be kept separate from user dependencies. std crates
691    // are differentiated in the Unit with `is_std` (for things like
692    // `-Zforce-unstable-if-unmarked`), so they are always built separately.
693    // This isn't strictly necessary for build dependencies which probably
694    // don't need unstable support. A future experiment might be to set
695    // `is_std` to false for build dependencies so that they can be shared
696    // with user dependencies.
697    unit.is_std.hash(&mut shared_hasher);
698
699    // While we don't hash RUSTFLAGS because it may contain absolute paths that
700    // hurts reproducibility, we track whether a unit's RUSTFLAGS is from host
701    // config, so that we can generate a different metadata hash for runtime
702    // and compile-time units.
703    //
704    // HACK: This is a temporary hack for fixing rust-lang/cargo#14253
705    // Need to find a long-term solution to replace this fragile workaround.
706    // See https://github.com/rust-lang/cargo/pull/14432#discussion_r1725065350
707    if unit.kind.is_host() && !bcx.gctx.target_applies_to_host().unwrap_or_default() {
708        let host_info = bcx.target_data.info(CompileKind::Host);
709        let target_configs_are_different = unit.rustflags != host_info.rustflags
710            || unit.rustdocflags != host_info.rustdocflags
711            || bcx
712                .target_data
713                .target_config(CompileKind::Host)
714                .links_overrides
715                != unit.links_overrides;
716        target_configs_are_different.hash(&mut shared_hasher);
717    }
718
719    let mut c_metadata_hasher = shared_hasher.clone();
720    // Mix in the target-metadata of all the dependencies of this target.
721    let mut dep_c_metadata_hashes = deps_metadata
722        .iter()
723        .map(|m| m.c_metadata)
724        .collect::<Vec<_>>();
725    dep_c_metadata_hashes.sort();
726    dep_c_metadata_hashes.hash(&mut c_metadata_hasher);
727
728    let mut c_extra_filename_hasher = shared_hasher.clone();
729    // Mix in the target-metadata of all the dependencies of this target.
730    let mut dep_c_extra_filename_hashes = deps_metadata
731        .iter()
732        .map(|m| m.c_extra_filename)
733        .collect::<Vec<_>>();
734    dep_c_extra_filename_hashes.sort();
735    dep_c_extra_filename_hashes.hash(&mut c_extra_filename_hasher);
736    // Avoid trashing the caches on RUSTFLAGS changing via `c_extra_filename`
737    //
738    // Limited to `c_extra_filename` to help with reproducible build / PGO issues.
739    let default = Vec::new();
740    let extra_args = build_runner.bcx.extra_args_for(unit).unwrap_or(&default);
741    if !has_remap_path_prefix(&extra_args) {
742        extra_args.hash(&mut c_extra_filename_hasher);
743    }
744    if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
745        if !has_remap_path_prefix(&unit.rustdocflags) {
746            unit.rustdocflags.hash(&mut c_extra_filename_hasher);
747        }
748    } else {
749        if !has_remap_path_prefix(&unit.rustflags) {
750            unit.rustflags.hash(&mut c_extra_filename_hasher);
751        }
752    }
753
754    let c_metadata = UnitHash(Hasher::finish(&c_metadata_hasher));
755    let c_extra_filename = UnitHash(Hasher::finish(&c_extra_filename_hasher));
756    let unit_id = c_extra_filename;
757
758    let c_extra_filename = use_extra_filename.then_some(c_extra_filename);
759
760    Metadata {
761        unit_id,
762        c_metadata,
763        c_extra_filename,
764    }
765}
766
767/// HACK: Detect the *potential* presence of `--remap-path-prefix`
768///
769/// As CLI parsing is contextual and dependent on the CLI definition to understand the context, we
770/// can't say for sure whether `--remap-path-prefix` is present, so we guess if anything looks like
771/// it.
772/// If we could, we'd strip it out for hashing.
773/// Instead, we use this to avoid hashing rustflags if it might be present to avoid the risk of taking
774/// a flag that is trying to make things reproducible and making things less reproducible by the
775/// `-Cextra-filename` showing up in the rlib, even with `split-debuginfo`.
776fn has_remap_path_prefix(args: &[String]) -> bool {
777    args.iter()
778        .any(|s| s.starts_with("--remap-path-prefix=") || s == "--remap-path-prefix")
779}
780
781/// Hash the version of rustc being used during the build process.
782fn hash_rustc_version(bcx: &BuildContext<'_, '_>, hasher: &mut StableHasher, unit: &Unit) {
783    let vers = &bcx.rustc().version;
784    if vers.pre.is_empty() || bcx.gctx.cli_unstable().separate_nightlies {
785        // For stable, keep the artifacts separate. This helps if someone is
786        // testing multiple versions, to avoid recompiles. Note though that for
787        // cross-compiled builds the `host:` line of `verbose_version` is
788        // omitted since rustc should produce the same output for each target
789        // regardless of the host.
790        for line in bcx.rustc().verbose_version.lines() {
791            if unit.kind.is_host() || !line.starts_with("host: ") {
792                line.hash(hasher);
793            }
794        }
795        return;
796    }
797    // On "nightly"/"beta"/"dev"/etc, keep each "channel" separate. Don't hash
798    // the date/git information, so that whenever someone updates "nightly",
799    // they won't have a bunch of stale artifacts in the target directory.
800    //
801    // This assumes that the first segment is the important bit ("nightly",
802    // "beta", "dev", etc.). Skip other parts like the `.3` in `-beta.3`.
803    vers.pre.split('.').next().hash(hasher);
804    // Keep "host" since some people switch hosts to implicitly change
805    // targets, (like gnu vs musl or gnu vs msvc). In the future, we may want
806    // to consider hashing `unit.kind.short_name()` instead.
807    if unit.kind.is_host() {
808        bcx.rustc().host.hash(hasher);
809    }
810    // None of the other lines are important. Currently they are:
811    // binary: rustc  <-- or "rustdoc"
812    // commit-hash: 38114ff16e7856f98b2b4be7ab4cd29b38bed59a
813    // commit-date: 2020-03-21
814    // host: x86_64-apple-darwin
815    // release: 1.44.0-nightly
816    // LLVM version: 9.0
817    //
818    // The backend version ("LLVM version") might become more relevant in
819    // the future when cranelift sees more use, and people want to switch
820    // between different backends without recompiling.
821}
822
823/// Returns whether or not this unit should use a hash in the filename to make it unique.
824fn use_extra_filename(bcx: &BuildContext<'_, '_>, unit: &Unit) -> bool {
825    if unit.mode.is_doc_test() || unit.mode.is_doc() {
826        // Doc tests do not have metadata.
827        return false;
828    }
829    if unit.mode.is_any_test() || unit.mode.is_check() {
830        // These always use metadata.
831        return true;
832    }
833    // No metadata in these cases:
834    //
835    // - dylibs:
836    //   - if any dylib names are encoded in executables, so they can't be renamed.
837    //   - TODO: Maybe use `-install-name` on macOS or `-soname` on other UNIX systems
838    //     to specify the dylib name to be used by the linker instead of the filename.
839    // - Windows MSVC executables: The path to the PDB is embedded in the
840    //   executable, and we don't want the PDB path to include the hash in it.
841    // - wasm32-unknown-emscripten executables: When using emscripten, the path to the
842    //   .wasm file is embedded in the .js file, so we don't want the hash in there.
843    //
844    // This is only done for local packages, as we don't expect to export
845    // dependencies.
846    //
847    // The __CARGO_DEFAULT_LIB_METADATA env var is used to override this to
848    // force metadata in the hash. This is only used for building libstd. For
849    // example, if libstd is placed in a common location, we don't want a file
850    // named /usr/lib/libstd.so which could conflict with other rustc
851    // installs. In addition it prevents accidentally loading a libstd of a
852    // different compiler at runtime.
853    // See https://github.com/rust-lang/cargo/issues/3005
854    let short_name = bcx.target_data.short_name(&unit.kind);
855    if (unit.target.is_dylib()
856        || unit.target.is_cdylib()
857        || (unit.target.is_executable() && short_name == "wasm32-unknown-emscripten")
858        || (unit.target.is_executable() && short_name.contains("msvc")))
859        && unit.pkg.package_id().source_id().is_path()
860        && bcx.gctx.get_env("__CARGO_DEFAULT_LIB_METADATA").is_err()
861    {
862        return false;
863    }
864    true
865}