1 use std
::collections
::HashMap
;
4 use std
::hash
::{Hash, Hasher, SipHasher}
;
5 use std
::path
::{Path, PathBuf}
;
8 use lazycell
::LazyCell
;
11 use super::{BuildContext, Context, FileFlavor, Kind, Layout}
;
12 use crate::core
::compiler
::{CompileMode, Unit}
;
13 use crate::core
::{InternedString, TargetKind, Workspace}
;
14 use crate::util
::{self, CargoResult}
;
16 /// The `Metadata` is a hash used to make unique file names for each unit in a build.
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.
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.
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.
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.
42 /// Note that the `Fingerprint` is in charge of tracking everything needed to determine if a
43 /// rebuild is needed.
44 #[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
45 pub struct Metadata(u64);
47 impl fmt
::Display
for Metadata
{
48 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
49 write
!(f
, "{:016x}", self.0)
53 pub struct CompilationFiles
<'a
, 'cfg
> {
54 /// The target directory layout for the host (and target if it is the same as host).
55 pub(super) host
: Layout
,
56 /// The target directory layout for the target (if different from then host).
57 pub(super) target
: HashMap
<InternedString
, Layout
>,
58 /// Additional directory to include a copy of the outputs.
59 export_dir
: Option
<PathBuf
>,
60 /// The root targets requested by the user on the command line (does not
61 /// include dependencies).
63 ws
: &'a Workspace
<'cfg
>,
64 metas
: HashMap
<Unit
<'a
>, Option
<Metadata
>>,
65 /// For each Unit, a list all files produced.
66 outputs
: HashMap
<Unit
<'a
>, LazyCell
<Arc
<Vec
<OutputFile
>>>>,
70 pub struct OutputFile
{
71 /// Absolute path to the file that will be produced by the build process.
73 /// If it should be linked into `target`, and what it should be called
74 /// (e.g., without metadata).
75 pub hardlink
: Option
<PathBuf
>,
76 /// If `--out-dir` is specified, the absolute path to the exported file.
77 pub export_path
: Option
<PathBuf
>,
78 /// Type of the file (library / debug symbol / else).
79 pub flavor
: FileFlavor
,
83 /// Gets the hard link if present; otherwise, returns the path.
84 pub fn bin_dst(&self) -> &PathBuf
{
86 Some(ref link_dst
) => link_dst
,
92 impl<'a
, 'cfg
: 'a
> CompilationFiles
<'a
, 'cfg
> {
96 target
: HashMap
<InternedString
, Layout
>,
97 export_dir
: Option
<PathBuf
>,
98 ws
: &'a Workspace
<'cfg
>,
99 cx
: &Context
<'a
, 'cfg
>,
100 ) -> CompilationFiles
<'a
, 'cfg
> {
101 let mut metas
= HashMap
::new();
103 metadata_of(unit
, cx
, &mut metas
);
108 .map(|unit
| (unit
, LazyCell
::new()))
115 roots
: roots
.to_vec(),
121 /// Returns the appropriate directory layout for either a plugin or not.
122 pub fn layout(&self, kind
: Kind
) -> &Layout
{
124 Kind
::Host
=> &self.host
,
125 Kind
::Target(name
) => self.target
.get(&name
).unwrap_or(&self.host
),
129 /// Gets the metadata for a target in a specific profile.
130 /// We build to the path `"{filename}-{target_metadata}"`.
131 /// We use a linking step to link/copy to a predictable filename
132 /// like `target/debug/libfoo.{a,so,rlib}` and such.
133 pub fn metadata(&self, unit
: &Unit
<'a
>) -> Option
<Metadata
> {
134 self.metas
[unit
].clone()
137 /// Gets the short hash based only on the `PackageId`.
138 /// Used for the metadata when `target_metadata` returns `None`.
139 pub fn target_short_hash(&self, unit
: &Unit
<'_
>) -> String
{
140 let hashable
= unit
.pkg
.package_id().stable_hash(self.ws
.root());
141 util
::short_hash(&hashable
)
144 /// Returns the appropriate output directory for the specified package and
146 pub fn out_dir(&self, unit
: &Unit
<'a
>) -> PathBuf
{
147 if unit
.mode
.is_doc() {
148 self.layout(unit
.kind
).doc().to_path_buf()
149 } else if unit
.mode
.is_doc_test() {
150 panic
!("doc tests do not have an out dir");
151 } else if unit
.target
.is_custom_build() {
152 self.build_script_dir(unit
)
153 } else if unit
.target
.is_example() {
154 self.layout(unit
.kind
).examples().to_path_buf()
156 self.deps_dir(unit
).to_path_buf()
160 pub fn export_dir(&self) -> Option
<PathBuf
> {
161 self.export_dir
.clone()
164 pub fn pkg_dir(&self, unit
: &Unit
<'a
>) -> String
{
165 let name
= unit
.pkg
.package_id().name();
166 match self.metas
[unit
] {
167 Some(ref meta
) => format
!("{}-{}", name
, meta
),
168 None
=> format
!("{}-{}", name
, self.target_short_hash(unit
)),
172 /// Returns the root of the build output tree for the host
173 pub fn host_root(&self) -> &Path
{
177 pub fn host_deps(&self) -> &Path
{
181 /// Returns the directories where Rust crate dependencies are found for the
183 pub fn deps_dir(&self, unit
: &Unit
<'_
>) -> &Path
{
184 self.layout(unit
.kind
).deps()
187 pub fn fingerprint_dir(&self, unit
: &Unit
<'a
>) -> PathBuf
{
188 let dir
= self.pkg_dir(unit
);
189 self.layout(unit
.kind
).fingerprint().join(dir
)
192 /// Path where compiler output is cached.
193 pub fn message_cache_path(&self, unit
: &Unit
<'a
>) -> PathBuf
{
194 self.fingerprint_dir(unit
).join("output")
197 /// Returns the directory where a compiled build script is stored.
198 /// `/path/to/target/{debug,release}/build/PKG-HASH`
199 pub fn build_script_dir(&self, unit
: &Unit
<'a
>) -> PathBuf
{
200 assert
!(unit
.target
.is_custom_build());
201 assert
!(!unit
.mode
.is_run_custom_build());
202 let dir
= self.pkg_dir(unit
);
203 self.layout(Kind
::Host
).build().join(dir
)
206 /// Returns the directory where information about running a build script
208 /// `/path/to/target/{debug,release}/build/PKG-HASH`
209 pub fn build_script_run_dir(&self, unit
: &Unit
<'a
>) -> PathBuf
{
210 assert
!(unit
.target
.is_custom_build());
211 assert
!(unit
.mode
.is_run_custom_build());
212 let dir
= self.pkg_dir(unit
);
213 self.layout(unit
.kind
).build().join(dir
)
216 /// Returns the "OUT_DIR" directory for running a build script.
217 /// `/path/to/target/{debug,release}/build/PKG-HASH/out`
218 pub fn build_script_out_dir(&self, unit
: &Unit
<'a
>) -> PathBuf
{
219 self.build_script_run_dir(unit
).join("out")
222 /// Returns the file stem for a given target/profile combo (with metadata).
223 pub fn file_stem(&self, unit
: &Unit
<'a
>) -> String
{
224 match self.metas
[unit
] {
225 Some(ref metadata
) => format
!("{}-{}", unit
.target
.crate_name(), metadata
),
226 None
=> self.bin_stem(unit
),
230 pub(super) fn outputs(
233 bcx
: &BuildContext
<'a
, 'cfg
>,
234 ) -> CargoResult
<Arc
<Vec
<OutputFile
>>> {
236 .try_borrow_with(|| self.calc_outputs(unit
, bcx
))
240 /// Returns the bin stem for a given target (without metadata).
241 fn bin_stem(&self, unit
: &Unit
<'_
>) -> String
{
242 if unit
.target
.allows_underscores() {
243 unit
.target
.name().to_string()
245 unit
.target
.crate_name()
249 /// Returns a tuple with the directory and name of the hard link we expect
250 /// our target to be copied to. Eg, file_stem may be out_dir/deps/foo-abcdef
251 /// and link_stem would be out_dir/foo
252 /// This function returns it in two parts so the caller can add prefix/suffix
253 /// to filename separately.
255 /// Returns an `Option` because in some cases we don't want to link
256 /// (eg a dependent lib).
257 fn link_stem(&self, unit
: &Unit
<'a
>) -> Option
<(PathBuf
, String
)> {
258 let out_dir
= self.out_dir(unit
);
259 let bin_stem
= self.bin_stem(unit
); // Stem without metadata.
260 let file_stem
= self.file_stem(unit
); // Stem with metadata.
262 // We currently only lift files up from the `deps` directory. If
263 // it was compiled into something like `example/` or `doc/` then
264 // we don't want to link it up.
265 if out_dir
.ends_with("deps") {
266 // Don't lift up library dependencies.
267 if unit
.target
.is_bin() || self.roots
.contains(unit
) {
269 out_dir
.parent().unwrap().to_owned(),
270 if unit
.mode
.is_any_test() {
279 } else if bin_stem
== file_stem
{
281 } else if out_dir
.ends_with("examples") || out_dir
.parent().unwrap().ends_with("build") {
282 Some((out_dir
, bin_stem
))
291 bcx
: &BuildContext
<'a
, 'cfg
>,
292 ) -> CargoResult
<Arc
<Vec
<OutputFile
>>> {
293 let ret
= match unit
.mode
{
294 CompileMode
::Check { .. }
=> {
295 // This may be confusing. rustc outputs a file named `lib*.rmeta`
296 // for both libraries and binaries.
297 let file_stem
= self.file_stem(unit
);
298 let path
= self.out_dir(unit
).join(format
!("lib{}.rmeta", file_stem
));
303 flavor
: FileFlavor
::Linkable { rmeta: false }
,
306 CompileMode
::Doc { .. }
=> {
309 .join(unit
.target
.crate_name())
315 flavor
: FileFlavor
::Normal
,
318 CompileMode
::RunCustomBuild
=> {
319 // At this time, this code path does not handle build script
323 CompileMode
::Doctest
=> {
324 // Doctests are built in a temporary directory and then
325 // deleted. There is the `--persist-doctests` unstable flag,
326 // but Cargo does not know about that.
329 CompileMode
::Test
| CompileMode
::Build
| CompileMode
::Bench
=> {
330 self.calc_outputs_rustc(unit
, bcx
)?
333 info
!("Target filenames: {:?}", ret
);
338 fn calc_outputs_rustc(
341 bcx
: &BuildContext
<'a
, 'cfg
>,
342 ) -> CargoResult
<Vec
<OutputFile
>> {
343 let mut ret
= Vec
::new();
344 let mut unsupported
= Vec
::new();
346 let out_dir
= self.out_dir(unit
);
347 let link_stem
= self.link_stem(unit
);
348 let info
= bcx
.info(unit
.kind
);
349 let file_stem
= self.file_stem(unit
);
351 let mut add
= |crate_type
: &str, flavor
: FileFlavor
| -> CargoResult
<()> {
352 let crate_type
= if crate_type
== "lib" {
357 let file_types
= info
.file_types(
361 &bcx
.target_triple(unit
.kind
),
366 for file_type
in types
{
367 let path
= out_dir
.join(file_type
.filename(&file_stem
));
368 let hardlink
= link_stem
370 .map(|&(ref ld
, ref ls
)| ld
.join(file_type
.filename(ls
)));
371 let export_path
= if unit
.target
.is_custom_build() {
374 self.export_dir
.as_ref().and_then(|export_dir
| {
375 hardlink
.as_ref().and_then(|hardlink
| {
376 Some(export_dir
.join(hardlink
.file_name().unwrap()))
380 ret
.push(OutputFile
{
384 flavor
: file_type
.flavor
,
388 // Not supported; don't worry about it.
390 unsupported
.push(crate_type
.to_string());
395 match *unit
.target
.kind() {
397 | TargetKind
::CustomBuild
398 | TargetKind
::ExampleBin
400 | TargetKind
::Test
=> {
401 add("bin", FileFlavor
::Normal
)?
;
403 TargetKind
::Lib(..) | TargetKind
::ExampleLib(..) if unit
.mode
.is_any_test() => {
404 add("bin", FileFlavor
::Normal
)?
;
406 TargetKind
::ExampleLib(ref kinds
) | TargetKind
::Lib(ref kinds
) => {
411 FileFlavor
::Linkable { rmeta: false }
417 let path
= out_dir
.join(format
!("lib{}.rmeta", file_stem
));
418 if !unit
.requires_upstream_objects() {
419 ret
.push(OutputFile
{
423 flavor
: FileFlavor
::Linkable { rmeta: true }
,
429 if !unsupported
.is_empty() {
431 "cannot produce {} for `{}` as the target `{}` \
432 does not support these crate types",
433 unsupported
.join(", "),
435 bcx
.target_triple(unit
.kind
),
439 "cannot compile `{}` as the target `{}` does not \
440 support any of the output crate types",
442 bcx
.target_triple(unit
.kind
),
449 fn metadata_of
<'a
, 'cfg
>(
451 cx
: &Context
<'a
, 'cfg
>,
452 metas
: &mut HashMap
<Unit
<'a
>, Option
<Metadata
>>,
453 ) -> Option
<Metadata
> {
454 if !metas
.contains_key(unit
) {
455 let meta
= compute_metadata(unit
, cx
, metas
);
456 metas
.insert(*unit
, meta
);
457 for unit
in cx
.dep_targets(unit
) {
458 metadata_of(&unit
, cx
, metas
);
464 fn compute_metadata
<'a
, 'cfg
>(
466 cx
: &Context
<'a
, 'cfg
>,
467 metas
: &mut HashMap
<Unit
<'a
>, Option
<Metadata
>>,
468 ) -> Option
<Metadata
> {
469 if unit
.mode
.is_doc_test() {
470 // Doc tests do not have metadata.
473 // No metadata for dylibs because of a couple issues:
474 // - macOS encodes the dylib name in the executable,
475 // - Windows rustc multiple files of which we can't easily link all of them.
477 // No metadata for bin because of an issue:
478 // - wasm32 rustc/emcc encodes the `.wasm` name in the `.js` (rust-lang/cargo#4535).
481 // 1) Upstream dependencies (we aren't exporting + need to resolve name conflict),
482 // 2) `__CARGO_DEFAULT_LIB_METADATA` env var.
484 // Note, however, that the compiler's build system at least wants
485 // path dependencies (eg libstd) to have hashes in filenames. To account for
486 // that we have an extra hack here which reads the
487 // `__CARGO_DEFAULT_LIB_METADATA` environment variable and creates a
488 // hash in the filename if that's present.
490 // This environment variable should not be relied on! It's
491 // just here for rustbuild. We need a more principled method
492 // doing this eventually.
494 let __cargo_default_lib_metadata
= env
::var("__CARGO_DEFAULT_LIB_METADATA");
495 if !(unit
.mode
.is_any_test() || unit
.mode
.is_check())
496 && (unit
.target
.is_dylib()
497 || unit
.target
.is_cdylib()
498 || (unit
.target
.is_executable() && bcx
.target_triple(unit
.kind
).starts_with("wasm32-")))
499 && unit
.pkg
.package_id().source_id().is_path()
500 && __cargo_default_lib_metadata
.is_err()
505 let mut hasher
= SipHasher
::new_with_keys(0, 0);
507 // This is a generic version number that can be changed to make
508 // backwards-incompatible changes to any file structures in the output
509 // directory. For example, the fingerprint files or the build-script
510 // output files. Normally cargo updates ship with rustc updates which will
511 // cause a new hash due to the rustc version changing, but this allows
512 // cargo to be extra careful to deal with different versions of cargo that
513 // use the same rustc version.
516 // Unique metadata per (name, source, version) triple. This'll allow us
517 // to pull crates from anywhere without worrying about conflicts.
520 .stable_hash(bcx
.ws
.root())
523 // Also mix in enabled features to our metadata. This'll ensure that
524 // when changing feature sets each lib is separately cached.
525 unit
.features
.hash(&mut hasher
);
527 // Mix in the target-metadata of all the dependencies of this target.
529 let mut deps_metadata
= cx
532 .map(|dep
| metadata_of(dep
, cx
, metas
))
533 .collect
::<Vec
<_
>>();
534 deps_metadata
.sort();
535 deps_metadata
.hash(&mut hasher
);
538 // Throw in the profile we're compiling with. This helps caching
539 // `panic=abort` and `panic=unwind` artifacts, additionally with various
540 // settings like debuginfo and whatnot.
541 unit
.profile
.hash(&mut hasher
);
542 unit
.mode
.hash(&mut hasher
);
544 // Throw in the rustflags we're compiling with.
545 // This helps when the target directory is a shared cache for projects with different cargo configs,
546 // or if the user is experimenting with different rustflags manually.
547 let mut hash_flags
= |flags
: &[String
]| {
548 // Ignore some flags. These may affect reproducible builds if they affect
549 // the path. The fingerprint will handle recompilation if these change.
550 let mut iter
= flags
.iter();
551 while let Some(flag
) = iter
.next() {
552 if flag
.starts_with("--remap-path-prefix=") {
555 if flag
== "--remap-path-prefix" {
559 flag
.hash(&mut hasher
);
562 if let Some(args
) = bcx
.extra_args_for(unit
) {
563 // Arguments passed to `cargo rustc`.
566 // Arguments passed in via RUSTFLAGS env var.
567 let flags
= if unit
.mode
.is_doc() {
568 bcx
.rustdocflags_args(unit
)
570 bcx
.rustflags_args(unit
)
574 // Artifacts compiled for the host should have a different metadata
575 // piece than those compiled for the target, so make sure we throw in
576 // the unit's `kind` as well
577 unit
.kind
.hash(&mut hasher
);
579 // Finally throw in the target name/kind. This ensures that concurrent
580 // compiles of targets in the same crate don't collide.
581 unit
.target
.name().hash(&mut hasher
);
582 unit
.target
.kind().hash(&mut hasher
);
584 bcx
.rustc
.verbose_version
.hash(&mut hasher
);
586 // Seed the contents of `__CARGO_DEFAULT_LIB_METADATA` to the hasher if present.
587 // This should be the release channel, to get a different hash for each channel.
588 if let Ok(ref channel
) = __cargo_default_lib_metadata
{
589 channel
.hash(&mut hasher
);
591 Some(Metadata(hasher
.finish()))