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