Skip to main content

cargo/core/compiler/build_runner/
compilation_files.rs

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