Skip to main content

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}