Skip to main content

cargo/compiler/build_runner/
compilation_files.rs

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