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