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