cargo/core/compiler/context/compilation_files.rs
1use std::collections::HashMap;
2use std::env;
3use std::fmt;
4use std::hash::{Hash, Hasher, SipHasher};
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8use lazycell::LazyCell;
9use log::info;
10
11use super::{BuildContext, CompileKind, Context, FileFlavor, Layout};
12use crate::core::compiler::{CompileMode, CompileTarget, Unit};
13use crate::core::{Target, TargetKind, Workspace};
14use crate::util::{self, CargoResult};
15
16/// The `Metadata` is a hash used to make unique file names for each unit in a build.
17/// For example:
18/// - A project may depend on crate `A` and crate `B`, so the package name must be in the file name.
19/// - Similarly a project may depend on two versions of `A`, so the version must be in the file name.
20/// In general this must include all things that need to be distinguished in different parts of
21/// the same build. This is absolutely required or we override things before
22/// we get chance to use them.
23///
24/// We use a hash because it is an easy way to guarantee
25/// that all the inputs can be converted to a valid path.
26///
27/// This also acts as the main layer of caching provided by Cargo.
28/// For example, we want to cache `cargo build` and `cargo doc` separately, so that running one
29/// does not invalidate the artifacts for the other. We do this by including `CompileMode` in the
30/// hash, thus the artifacts go in different folders and do not override each other.
31/// If we don't add something that we should have, for this reason, we get the
32/// correct output but rebuild more than is needed.
33///
34/// Some things that need to be tracked to ensure the correct output should definitely *not*
35/// go in the `Metadata`. For example, the modification time of a file, should be tracked to make a
36/// rebuild when the file changes. However, it would be wasteful to include in the `Metadata`. The
37/// old artifacts are never going to be needed again. We can save space by just overwriting them.
38/// If we add something that we should not have, for this reason, we get the correct output but take
39/// more space than needed. This makes not including something in `Metadata`
40/// a form of cache invalidation.
41///
42/// Note that the `Fingerprint` is in charge of tracking everything needed to determine if a
43/// rebuild is needed.
44#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
45pub struct Metadata(u64);
46
47impl fmt::Display for Metadata {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 write!(f, "{:016x}", self.0)
50 }
51}
52
53impl fmt::Debug for Metadata {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(f, "Metadata({:016x})", self.0)
56 }
57}
58
59/// Collection of information about the files emitted by the compiler, and the
60/// output directory structure.
61pub struct CompilationFiles<'a, 'cfg> {
62 /// The target directory layout for the host (and target if it is the same as host).
63 pub(super) host: Layout,
64 /// The target directory layout for the target (if different from then host).
65 pub(super) target: HashMap<CompileTarget, Layout>,
66 /// Additional directory to include a copy of the outputs.
67 export_dir: Option<PathBuf>,
68 /// The root targets requested by the user on the command line (does not
69 /// include dependencies).
70 roots: Vec<Unit<'a>>,
71 ws: &'a Workspace<'cfg>,
72 /// Metadata hash to use for each unit.
73 ///
74 /// `None` if the unit should not use a metadata data hash (like rustdoc,
75 /// or some dylibs).
76 metas: HashMap<Unit<'a>, Option<Metadata>>,
77 /// For each Unit, a list all files produced.
78 outputs: HashMap<Unit<'a>, LazyCell<Arc<Vec<OutputFile>>>>,
79}
80
81/// Info about a single file emitted by the compiler.
82#[derive(Debug)]
83pub struct OutputFile {
84 /// Absolute path to the file that will be produced by the build process.
85 pub path: PathBuf,
86 /// If it should be linked into `target`, and what it should be called
87 /// (e.g., without metadata).
88 pub hardlink: Option<PathBuf>,
89 /// If `--out-dir` is specified, the absolute path to the exported file.
90 pub export_path: Option<PathBuf>,
91 /// Type of the file (library / debug symbol / else).
92 pub flavor: FileFlavor,
93}
94
95impl OutputFile {
96 /// Gets the hard link if present; otherwise, returns the path.
97 pub fn bin_dst(&self) -> &PathBuf {
98 match self.hardlink {
99 Some(ref link_dst) => link_dst,
100 None => &self.path,
101 }
102 }
103}
104
105impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
106 pub(super) fn new(
107 roots: &[Unit<'a>],
108 host: Layout,
109 target: HashMap<CompileTarget, Layout>,
110 export_dir: Option<PathBuf>,
111 ws: &'a Workspace<'cfg>,
112 cx: &Context<'a, 'cfg>,
113 ) -> CompilationFiles<'a, 'cfg> {
114 let mut metas = HashMap::new();
115 for unit in roots {
116 metadata_of(unit, cx, &mut metas);
117 }
118 let outputs = metas
119 .keys()
120 .cloned()
121 .map(|unit| (unit, LazyCell::new()))
122 .collect();
123 CompilationFiles {
124 ws,
125 host,
126 target,
127 export_dir,
128 roots: roots.to_vec(),
129 metas,
130 outputs,
131 }
132 }
133
134 /// Returns the appropriate directory layout for either a plugin or not.
135 pub fn layout(&self, kind: CompileKind) -> &Layout {
136 match kind {
137 CompileKind::Host => &self.host,
138 CompileKind::Target(target) => &self.target[&target],
139 }
140 }
141
142 /// Gets the metadata for a target in a specific profile.
143 /// We build to the path `"{filename}-{target_metadata}"`.
144 /// We use a linking step to link/copy to a predictable filename
145 /// like `target/debug/libfoo.{a,so,rlib}` and such.
146 ///
147 /// Returns `None` if the unit should not use a metadata data hash (like
148 /// rustdoc, or some dylibs).
149 pub fn metadata(&self, unit: &Unit<'a>) -> Option<Metadata> {
150 self.metas[unit]
151 }
152
153 /// Gets the short hash based only on the `PackageId`.
154 /// Used for the metadata when `metadata` returns `None`.
155 pub fn target_short_hash(&self, unit: &Unit<'_>) -> String {
156 let hashable = unit.pkg.package_id().stable_hash(self.ws.root());
157 util::short_hash(&hashable)
158 }
159
160 /// Returns the appropriate output directory for the specified package and
161 /// target.
162 pub fn out_dir(&self, unit: &Unit<'a>) -> PathBuf {
163 if unit.mode.is_doc() {
164 self.layout(unit.kind).doc().to_path_buf()
165 } else if unit.mode.is_doc_test() {
166 panic!("doc tests do not have an out dir");
167 } else if unit.target.is_custom_build() {
168 self.build_script_dir(unit)
169 } else if unit.target.is_example() {
170 self.layout(unit.kind).examples().to_path_buf()
171 } else {
172 self.deps_dir(unit).to_path_buf()
173 }
174 }
175
176 /// Additional export directory from `--out-dir`.
177 pub fn export_dir(&self) -> Option<PathBuf> {
178 self.export_dir.clone()
179 }
180
181 /// Directory name to use for a package in the form `NAME-HASH`.
182 pub fn pkg_dir(&self, unit: &Unit<'a>) -> String {
183 let name = unit.pkg.package_id().name();
184 match self.metas[unit] {
185 Some(ref meta) => format!("{}-{}", name, meta),
186 None => format!("{}-{}", name, self.target_short_hash(unit)),
187 }
188 }
189
190 /// Returns the root of the build output tree for the host
191 pub fn host_root(&self) -> &Path {
192 self.host.dest()
193 }
194
195 /// Returns the host `deps` directory path.
196 pub fn host_deps(&self) -> &Path {
197 self.host.deps()
198 }
199
200 /// Returns the directories where Rust crate dependencies are found for the
201 /// specified unit.
202 pub fn deps_dir(&self, unit: &Unit<'_>) -> &Path {
203 self.layout(unit.kind).deps()
204 }
205
206 /// Directory where the fingerprint for the given unit should go.
207 pub fn fingerprint_dir(&self, unit: &Unit<'a>) -> PathBuf {
208 let dir = self.pkg_dir(unit);
209 self.layout(unit.kind).fingerprint().join(dir)
210 }
211
212 /// Path where compiler output is cached.
213 pub fn message_cache_path(&self, unit: &Unit<'a>) -> PathBuf {
214 self.fingerprint_dir(unit).join("output")
215 }
216
217 /// Returns the directory where a compiled build script is stored.
218 /// `/path/to/target/{debug,release}/build/PKG-HASH`
219 pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf {
220 assert!(unit.target.is_custom_build());
221 assert!(!unit.mode.is_run_custom_build());
222 assert!(self.metas.contains_key(unit));
223 let dir = self.pkg_dir(unit);
224 self.layout(CompileKind::Host).build().join(dir)
225 }
226
227 /// Returns the directory where information about running a build script
228 /// is stored.
229 /// `/path/to/target/{debug,release}/build/PKG-HASH`
230 pub fn build_script_run_dir(&self, unit: &Unit<'a>) -> PathBuf {
231 assert!(unit.target.is_custom_build());
232 assert!(unit.mode.is_run_custom_build());
233 let dir = self.pkg_dir(unit);
234 self.layout(unit.kind).build().join(dir)
235 }
236
237 /// Returns the "OUT_DIR" directory for running a build script.
238 /// `/path/to/target/{debug,release}/build/PKG-HASH/out`
239 pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf {
240 self.build_script_run_dir(unit).join("out")
241 }
242
243 /// Returns the file stem for a given target/profile combo (with metadata).
244 pub fn file_stem(&self, unit: &Unit<'a>) -> String {
245 match self.metas[unit] {
246 Some(ref metadata) => format!("{}-{}", unit.target.crate_name(), metadata),
247 None => self.bin_stem(unit),
248 }
249 }
250
251 /// Returns the path to the executable binary for the given bin target.
252 ///
253 /// This should only to be used when a `Unit` is not available.
254 pub fn bin_link_for_target(
255 &self,
256 target: &Target,
257 kind: CompileKind,
258 bcx: &BuildContext<'_, '_>,
259 ) -> CargoResult<PathBuf> {
260 assert!(target.is_bin());
261 let dest = self.layout(kind).dest();
262 let info = bcx.target_data.info(kind);
263 let file_types = info
264 .file_types(
265 "bin",
266 FileFlavor::Normal,
267 &TargetKind::Bin,
268 bcx.target_data.short_name(&kind),
269 )?
270 .expect("target must support `bin`");
271
272 let file_type = file_types
273 .iter()
274 .find(|file_type| file_type.flavor == FileFlavor::Normal)
275 .expect("target must support `bin`");
276
277 Ok(dest.join(file_type.filename(target.name())))
278 }
279
280 /// Returns the filenames that the given unit will generate.
281 pub(super) fn outputs(
282 &self,
283 unit: &Unit<'a>,
284 bcx: &BuildContext<'a, 'cfg>,
285 ) -> CargoResult<Arc<Vec<OutputFile>>> {
286 self.outputs[unit]
287 .try_borrow_with(|| self.calc_outputs(unit, bcx))
288 .map(Arc::clone)
289 }
290
291 /// Returns the bin filename for a given target, without extension and metadata.
292 fn bin_stem(&self, unit: &Unit<'_>) -> String {
293 if unit.target.allows_underscores() {
294 unit.target.name().to_string()
295 } else {
296 unit.target.crate_name()
297 }
298 }
299
300 /// Returns a tuple `(hard_link_dir, filename_stem)` for the primary
301 /// output file for the given unit.
302 ///
303 /// `hard_link_dir` is the directory where the file should be hard-linked
304 /// ("uplifted") to. For example, `/path/to/project/target`.
305 ///
306 /// `filename_stem` is the base filename without an extension.
307 ///
308 /// This function returns it in two parts so the caller can add
309 /// prefix/suffix to filename separately.
310 ///
311 /// Returns an `Option` because in some cases we don't want to link
312 /// (eg a dependent lib).
313 fn link_stem(&self, unit: &Unit<'a>) -> Option<(PathBuf, String)> {
314 let out_dir = self.out_dir(unit);
315 let bin_stem = self.bin_stem(unit); // Stem without metadata.
316 let file_stem = self.file_stem(unit); // Stem with metadata.
317
318 // We currently only lift files up from the `deps` directory. If
319 // it was compiled into something like `example/` or `doc/` then
320 // we don't want to link it up.
321 if out_dir.ends_with("deps") {
322 // Don't lift up library dependencies.
323 if unit.target.is_bin() || self.roots.contains(unit) {
324 Some((
325 out_dir.parent().unwrap().to_owned(),
326 if unit.mode.is_any_test() {
327 file_stem
328 } else {
329 bin_stem
330 },
331 ))
332 } else {
333 None
334 }
335 } else if bin_stem == file_stem {
336 None
337 } else if out_dir.ends_with("examples") || out_dir.parent().unwrap().ends_with("build") {
338 Some((out_dir, bin_stem))
339 } else {
340 None
341 }
342 }
343
344 fn calc_outputs(
345 &self,
346 unit: &Unit<'a>,
347 bcx: &BuildContext<'a, 'cfg>,
348 ) -> CargoResult<Arc<Vec<OutputFile>>> {
349 let ret = match unit.mode {
350 CompileMode::Check { .. } => {
351 // This may be confusing. rustc outputs a file named `lib*.rmeta`
352 // for both libraries and binaries.
353 let file_stem = self.file_stem(unit);
354 let path = self.out_dir(unit).join(format!("lib{}.rmeta", file_stem));
355 vec![OutputFile {
356 path,
357 hardlink: None,
358 export_path: None,
359 flavor: FileFlavor::Linkable { rmeta: false },
360 }]
361 }
362 CompileMode::Doc { .. } => {
363 let path = self
364 .out_dir(unit)
365 .join(unit.target.crate_name())
366 .join("index.html");
367 vec![OutputFile {
368 path,
369 hardlink: None,
370 export_path: None,
371 flavor: FileFlavor::Normal,
372 }]
373 }
374 CompileMode::RunCustomBuild => {
375 // At this time, this code path does not handle build script
376 // outputs.
377 vec![]
378 }
379 CompileMode::Doctest => {
380 // Doctests are built in a temporary directory and then
381 // deleted. There is the `--persist-doctests` unstable flag,
382 // but Cargo does not know about that.
383 vec![]
384 }
385 CompileMode::Test | CompileMode::Build | CompileMode::Bench => {
386 self.calc_outputs_rustc(unit, bcx)?
387 }
388 };
389 info!("Target filenames: {:?}", ret);
390
391 Ok(Arc::new(ret))
392 }
393
394 fn calc_outputs_rustc(
395 &self,
396 unit: &Unit<'a>,
397 bcx: &BuildContext<'a, 'cfg>,
398 ) -> CargoResult<Vec<OutputFile>> {
399 let mut ret = Vec::new();
400 let mut unsupported = Vec::new();
401
402 let out_dir = self.out_dir(unit);
403 let link_stem = self.link_stem(unit);
404 let info = bcx.target_data.info(unit.kind);
405 let file_stem = self.file_stem(unit);
406
407 let mut add = |crate_type: &str, flavor: FileFlavor| -> CargoResult<()> {
408 let crate_type = if crate_type == "lib" {
409 "rlib"
410 } else {
411 crate_type
412 };
413 let file_types = info.file_types(
414 crate_type,
415 flavor,
416 unit.target.kind(),
417 bcx.target_data.short_name(&unit.kind),
418 )?;
419
420 match file_types {
421 Some(types) => {
422 for file_type in types {
423 let path = out_dir.join(file_type.filename(&file_stem));
424 // Don't create hardlink for tests
425 let hardlink = if unit.mode.is_any_test() {
426 None
427 } else {
428 link_stem
429 .as_ref()
430 .map(|&(ref ld, ref ls)| ld.join(file_type.filename(ls)))
431 };
432 let export_path = if unit.target.is_custom_build() {
433 None
434 } else {
435 self.export_dir.as_ref().and_then(|export_dir| {
436 hardlink
437 .as_ref()
438 .map(|hardlink| export_dir.join(hardlink.file_name().unwrap()))
439 })
440 };
441 ret.push(OutputFile {
442 path,
443 hardlink,
444 export_path,
445 flavor: file_type.flavor,
446 });
447 }
448 }
449 // Not supported; don't worry about it.
450 None => {
451 unsupported.push(crate_type.to_string());
452 }
453 }
454 Ok(())
455 };
456 match *unit.target.kind() {
457 TargetKind::Bin
458 | TargetKind::CustomBuild
459 | TargetKind::ExampleBin
460 | TargetKind::Bench
461 | TargetKind::Test => {
462 add("bin", FileFlavor::Normal)?;
463 }
464 TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.mode.is_any_test() => {
465 add("bin", FileFlavor::Normal)?;
466 }
467 TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => {
468 for kind in kinds {
469 add(
470 kind.crate_type(),
471 if kind.linkable() {
472 FileFlavor::Linkable { rmeta: false }
473 } else {
474 FileFlavor::Normal
475 },
476 )?;
477 }
478 let path = out_dir.join(format!("lib{}.rmeta", file_stem));
479 if !unit.requires_upstream_objects() {
480 ret.push(OutputFile {
481 path,
482 hardlink: None,
483 export_path: None,
484 flavor: FileFlavor::Linkable { rmeta: true },
485 });
486 }
487 }
488 }
489 if ret.is_empty() {
490 if !unsupported.is_empty() {
491 anyhow::bail!(
492 "cannot produce {} for `{}` as the target `{}` \
493 does not support these crate types",
494 unsupported.join(", "),
495 unit.pkg,
496 bcx.target_data.short_name(&unit.kind),
497 )
498 }
499 anyhow::bail!(
500 "cannot compile `{}` as the target `{}` does not \
501 support any of the output crate types",
502 unit.pkg,
503 bcx.target_data.short_name(&unit.kind),
504 );
505 }
506 Ok(ret)
507 }
508}
509
510fn metadata_of<'a, 'cfg>(
511 unit: &Unit<'a>,
512 cx: &Context<'a, 'cfg>,
513 metas: &mut HashMap<Unit<'a>, Option<Metadata>>,
514) -> Option<Metadata> {
515 if !metas.contains_key(unit) {
516 let meta = compute_metadata(unit, cx, metas);
517 metas.insert(*unit, meta);
518 for dep in cx.unit_deps(unit) {
519 metadata_of(&dep.unit, cx, metas);
520 }
521 }
522 metas[unit]
523}
524
525fn compute_metadata<'a, 'cfg>(
526 unit: &Unit<'a>,
527 cx: &Context<'a, 'cfg>,
528 metas: &mut HashMap<Unit<'a>, Option<Metadata>>,
529) -> Option<Metadata> {
530 if unit.mode.is_doc_test() {
531 // Doc tests do not have metadata.
532 return None;
533 }
534 // No metadata for dylibs because of a couple issues:
535 // - macOS encodes the dylib name in the executable,
536 // - Windows rustc multiple files of which we can't easily link all of them.
537 //
538 // No metadata for bin because of an issue:
539 // - wasm32 rustc/emcc encodes the `.wasm` name in the `.js` (rust-lang/cargo#4535).
540 // - msvc: The path to the PDB is embedded in the executable, and we don't
541 // want the PDB path to include the hash in it.
542 //
543 // Two exceptions:
544 // 1) Upstream dependencies (we aren't exporting + need to resolve name conflict),
545 // 2) `__CARGO_DEFAULT_LIB_METADATA` env var.
546 //
547 // Note, however, that the compiler's build system at least wants
548 // path dependencies (eg libstd) to have hashes in filenames. To account for
549 // that we have an extra hack here which reads the
550 // `__CARGO_DEFAULT_LIB_METADATA` environment variable and creates a
551 // hash in the filename if that's present.
552 //
553 // This environment variable should not be relied on! It's
554 // just here for rustbuild. We need a more principled method
555 // doing this eventually.
556 let bcx = &cx.bcx;
557 let __cargo_default_lib_metadata = env::var("__CARGO_DEFAULT_LIB_METADATA");
558 let short_name = bcx.target_data.short_name(&unit.kind);
559 if !(unit.mode.is_any_test() || unit.mode.is_check())
560 && (unit.target.is_dylib()
561 || unit.target.is_cdylib()
562 || (unit.target.is_executable() && short_name.starts_with("wasm32-"))
563 || (unit.target.is_executable() && short_name.contains("msvc")))
564 && unit.pkg.package_id().source_id().is_path()
565 && __cargo_default_lib_metadata.is_err()
566 {
567 return None;
568 }
569
570 let mut hasher = SipHasher::new();
571
572 // This is a generic version number that can be changed to make
573 // backwards-incompatible changes to any file structures in the output
574 // directory. For example, the fingerprint files or the build-script
575 // output files. Normally cargo updates ship with rustc updates which will
576 // cause a new hash due to the rustc version changing, but this allows
577 // cargo to be extra careful to deal with different versions of cargo that
578 // use the same rustc version.
579 1.hash(&mut hasher);
580
581 // Unique metadata per (name, source, version) triple. This'll allow us
582 // to pull crates from anywhere without worrying about conflicts.
583 unit.pkg
584 .package_id()
585 .stable_hash(bcx.ws.root())
586 .hash(&mut hasher);
587
588 // Also mix in enabled features to our metadata. This'll ensure that
589 // when changing feature sets each lib is separately cached.
590 unit.features.hash(&mut hasher);
591
592 // Mix in the target-metadata of all the dependencies of this target.
593 let mut deps_metadata = cx
594 .unit_deps(unit)
595 .iter()
596 .map(|dep| metadata_of(&dep.unit, cx, metas))
597 .collect::<Vec<_>>();
598 deps_metadata.sort();
599 deps_metadata.hash(&mut hasher);
600
601 // Throw in the profile we're compiling with. This helps caching
602 // `panic=abort` and `panic=unwind` artifacts, additionally with various
603 // settings like debuginfo and whatnot.
604 unit.profile.hash(&mut hasher);
605 unit.mode.hash(&mut hasher);
606
607 // Artifacts compiled for the host should have a different metadata
608 // piece than those compiled for the target, so make sure we throw in
609 // the unit's `kind` as well
610 unit.kind.hash(&mut hasher);
611
612 // Finally throw in the target name/kind. This ensures that concurrent
613 // compiles of targets in the same crate don't collide.
614 unit.target.name().hash(&mut hasher);
615 unit.target.kind().hash(&mut hasher);
616
617 bcx.rustc().verbose_version.hash(&mut hasher);
618
619 if cx.bcx.ws.is_member(unit.pkg) {
620 // This is primarily here for clippy. This ensures that the clippy
621 // artifacts are separate from the `check` ones.
622 if let Some(path) = &cx.bcx.rustc().workspace_wrapper {
623 path.hash(&mut hasher);
624 }
625 }
626
627 // Seed the contents of `__CARGO_DEFAULT_LIB_METADATA` to the hasher if present.
628 // This should be the release channel, to get a different hash for each channel.
629 if let Ok(ref channel) = __cargo_default_lib_metadata {
630 channel.hash(&mut hasher);
631 }
632
633 // std units need to be kept separate from user dependencies. std crates
634 // are differentiated in the Unit with `is_std` (for things like
635 // `-Zforce-unstable-if-unmarked`), so they are always built separately.
636 // This isn't strictly necessary for build dependencies which probably
637 // don't need unstable support. A future experiment might be to set
638 // `is_std` to false for build dependencies so that they can be shared
639 // with user dependencies.
640 unit.is_std.hash(&mut hasher);
641
642 Some(Metadata(hasher.finish()))
643}