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