cargo/compiler/layout.rs
1//! Management of the directory layout of a build
2//!
3//! The directory layout is a little tricky at times, hence a separate file to
4//! house this logic. Cargo stores build artifacts in two directories: `artifact-dir` and
5//! `build-dir`
6//!
7//! ## `artifact-dir` layout
8//!
9//! `artifact-dir` is where final artifacts like binaries are stored.
10//! The `artifact-dir` layout is consider part of the public API and
11//! cannot be easily changed.
12//!
13//! ```text
14//! <artifact-dir>/
15//!
16//! # Compilation files are grouped by build target and profile.
17//! # The target is omitted if not explicitly specified.
18//! [<target>]/<profile>/ # e.g. `debug` / `release`
19//!
20//! # File used to lock the directory to prevent multiple cargo processes
21//! # from using it at the same time.
22//! .cargo-lock
23//!
24//! # Root directory for all compiled examples.
25//! examples/
26//!
27//! # Output from rustdoc
28//! doc/
29//!
30//! # Output from `cargo package` to build a `.crate` file.
31//! package/
32//! ```
33//!
34//! ## `build-dir` layout
35//!
36//! `build-dir` is where intermediate build artifacts are stored.
37//! The `build-dir` layout is considered an internal implementation detail of Cargo
38//! meaning that we can change this if needed. However, in reality many tools rely on
39//! implementation details of Cargo so breaking changes need to be done carefully.
40//!
41//! ```text
42//! <build-dir>/
43//!
44//! # Cache of `rustc -Vv` output for performance.
45//! .rustc-info.json
46//!
47//! # Compilation files are grouped by build target and profile.
48//! # The target is omitted if not explicitly specified.
49//! [<target>]/<profile>/ # e.g. `debug` / `release`
50//!
51//! # File used to lock the directory to prevent multiple cargo processes
52//! # from using it at the same time.
53//! .cargo-lock
54//!
55//! # Hidden directory that holds all of the fingerprint files for all
56//! # packages
57//! .fingerprint/
58//! # Each package is in a separate directory.
59//! # Note that different target kinds have different filename prefixes.
60//! $pkgname-$META/
61//! # Set of source filenames for this package.
62//! dep-lib-$targetname
63//! # Timestamp when this package was last built.
64//! invoked.timestamp
65//! # The fingerprint hash.
66//! lib-$targetname
67//! # Detailed information used for logging the reason why
68//! # something is being recompiled.
69//! lib-$targetname.json
70//! # The console output from the compiler. This is cached
71//! # so that warnings can be redisplayed for "fresh" units.
72//! output-lib-$targetname
73//!
74//! # This is the root directory for all rustc artifacts except build
75//! # scripts, examples, and test and bench executables. Almost every
76//! # artifact should have a metadata hash added to its filename to
77//! # prevent collisions. One notable exception is dynamic libraries.
78//! deps/
79//!
80//! # Each artifact dependency gets in its own directory.
81//! /artifact/$pkgname-$META/$kind
82//!
83//! # Root directory for all compiled examples.
84//! examples/
85//!
86//! # Directory used to store incremental data for the compiler (when
87//! # incremental is enabled.
88//! incremental/
89//!
90//! # This is the location at which the output of all custom build
91//! # commands are rooted.
92//! build/
93//!
94//! # Each package gets its own directory where its build script and
95//! # script output are placed
96//! $pkgname-$META/ # For the build script itself.
97//! # The build script executable (name may be changed by user).
98//! build-script-build-$META
99//! # Hard link to build-script-build-$META.
100//! build-script-build
101//! # Dependency information generated by rustc.
102//! build-script-build-$META.d
103//! # Debug information, depending on platform and profile
104//! # settings.
105//! <debug symbols>
106//!
107//! # The package shows up twice with two different metadata hashes.
108//! $pkgname-$META/ # For the output of the build script.
109//! # Timestamp when the build script was last executed.
110//! invoked.timestamp
111//! # Directory where script can output files ($OUT_DIR).
112//! out/
113//! # Output from the build script.
114//! output
115//! # Path to `out`, used to help when the target directory is
116//! # moved.
117//! root-output
118//! # Stderr output from the build script.
119//! stderr
120//!
121//! # Used by `cargo package` and `cargo publish` to build a `.crate` file.
122//! package/
123//!
124//! # Experimental feature for generated build scripts.
125//! .metabuild/
126//! ```
127//!
128//! ### New `build-dir` layout
129//!
130//! `build-dir` supports a new "build unit" based layout that is unstable.
131//! It can be enabled via `-Zbuild-dir-new-layout`.
132//! For more info about the layout transition see: [#15010](https://github.com/rust-lang/cargo/issues/15010)
133//!
134//! ```text
135//! <build-dir>/
136//!
137//! # Cache of `rustc -Vv` output for performance.
138//! .rustc-info.json
139//!
140//! # Compilation files are grouped by build target and profile.
141//! # The target is omitted if not explicitly specified.
142//! [<target>]/<profile>/ # e.g. `debug` / `release`
143//!
144//! # File used to lock the directory to prevent multiple cargo processes
145//! # from using it at the same time.
146//! .cargo-lock
147//!
148//! # Directory used to store incremental data for the compiler (when
149//! # incremental is enabled.
150//! incremental/
151//!
152//! # Main directory for storing build unit related files.
153//! # Files are organized by Cargo build unit (`$pkgname/$META`) so that
154//! # related files are stored in a single directory.
155//! build/
156//!
157//! # This is the location at which the output of all files related to
158//! # a given build unit. These files are organized together so that we can
159//! # treat this directly like a single unit for locking and caching.
160//! $pkgname/
161//! $META/
162//! # The general purpose output directory for build units.
163//! # For compilation units, the rustc artifact will be located here.
164//! # For build script run units, this is the $OUT_DIR
165//! out/
166//!
167//! # For artifact dependency units, the output is nested by the kind
168//! artifact/$kind
169//!
170//! # Directory that holds all of the fingerprint files for the build unit.
171//! fingerprint/
172//! # Set of source filenames for this package.
173//! dep-lib-$targetname
174//! # Timestamp when this package was last built.
175//! invoked.timestamp
176//! # The fingerprint hash.
177//! lib-$targetname
178//! # Detailed information used for logging the reason why
179//! # something is being recompiled.
180//! lib-$targetname.json
181//! # The console output from the compiler. This is cached
182//! # so that warnings can be redisplayed for "fresh" units.
183//! output-lib-$targetname
184//!
185//! # Directory for "execution" units that spawn a process (excluding compilation with
186//! # rustc). Contains the process execution details.
187//! # Currently the only execution unit Cargo supports is running build script
188//! # binaries.
189//! run/
190//! # Timestamp of last execution.
191//! invoked.timestamp
192//! # Stdout output from the process.
193//! stdout
194//! # Stderr output from the process.
195//! stderr
196//! # Path to `out`, used to help when the target directory is
197//! # moved. (build scripts)
198//! root-output
199//!
200//! # Used by `cargo package` and `cargo publish` to build a `.crate` file.
201//! package/
202//!
203//! # Experimental feature for generated build scripts.
204//! .metabuild/
205//! ```
206//!
207//! When cross-compiling, the layout is the same, except it appears in
208//! `target/$TUPLE`.
209
210use crate::compiler::CompileTarget;
211use crate::util::flock::is_on_nfs_mount;
212use crate::util::{CargoResult, FileLock};
213use crate::workspace::Workspace;
214use cargo_util::paths;
215use std::path::{Path, PathBuf};
216
217/// Contains the paths of all target output locations.
218///
219/// See module docs for more information.
220pub struct Layout {
221 artifact_dir: Option<ArtifactDirLayout>,
222 build_dir: BuildDirLayout,
223 _lock: Option<FileLock>,
224}
225
226impl Layout {
227 /// Calculate the paths for build output, lock the build directory, and return as a Layout.
228 ///
229 /// This function will block if the directory is already locked.
230 ///
231 /// `dest` should be the final artifact directory name. Currently either
232 /// "debug" or "release".
233 pub fn new(
234 ws: &Workspace<'_>,
235 target: Option<CompileTarget>,
236 dest: &str,
237 must_take_artifact_dir_lock: bool,
238 must_take_build_dir_lock_exclusively: bool,
239 ) -> CargoResult<Layout> {
240 let is_new_layout = ws.gctx().cli_unstable().build_dir_new_layout;
241 let mut root = ws.target_dir();
242 let mut build_root = ws.build_dir();
243 if let Some(target) = target {
244 root.push(target.short_name());
245 build_root.push(target.short_name());
246 }
247 let build_dest = build_root.join(dest);
248 let dest = root.join(dest);
249 // If the root directory doesn't already exist go ahead and create it
250 // here. Use this opportunity to exclude it from backups as well if the
251 // system supports it since this is a freshly created folder.
252 //
253 // Only the created directory is excluded, not the parents created
254 // along the way, so when one root contains the other, as with a
255 // `--target-dir` inside `build.build-dir`, the outer one is created
256 // first.
257 for dir in outer_first(root.as_path_unlocked(), build_root.as_path_unlocked()) {
258 paths::create_dir_all_excluded_from_backups_atomic(dir)?;
259 }
260
261 // Now that the excluded from backups target root is created we can create the
262 // actual destination (sub)subdirectory.
263 paths::create_dir_all(dest.as_path_unlocked())?;
264
265 let build_dir_lock = if is_on_nfs_mount(build_root.as_path_unlocked()) {
266 None
267 } else {
268 if ws.gctx().cli_unstable().fine_grain_locking && !must_take_build_dir_lock_exclusively
269 {
270 Some(build_dest.open_ro_shared_create(
271 ".cargo-build-lock",
272 ws.gctx(),
273 "build directory",
274 )?)
275 } else {
276 Some(build_dest.open_rw_exclusive_create(
277 ".cargo-build-lock",
278 ws.gctx(),
279 "build directory",
280 )?)
281 }
282 };
283 let build_root = build_root.into_path_unlocked();
284 let build_dest = build_dest.as_path_unlocked();
285 let deps = build_dest.join("deps");
286 let artifact = deps.join("artifact");
287
288 // We take a shared lock on `.cargo-lock` to make sure we don't run currently with
289 // older versions of Cargo (including tools that use Cargo as a library) that don't support
290 // `.cargo-build-lock`.
291 let lock = if is_on_nfs_mount(root.as_path_unlocked()) {
292 None
293 } else {
294 Some(dest.open_ro_shared_create(".cargo-lock", ws.gctx(), "artifact directory")?)
295 };
296
297 let artifact_dir = if must_take_artifact_dir_lock {
298 // For now we don't do any more finer-grained locking on the artifact
299 // directory, so just lock the entire thing for the duration of this
300 // compile.
301 let artifact_dir_lock = if is_on_nfs_mount(root.as_path_unlocked()) {
302 None
303 } else {
304 Some(dest.open_rw_exclusive_create(
305 ".cargo-artifact-lock",
306 ws.gctx(),
307 "artifact directory",
308 )?)
309 };
310 let root = root.into_path_unlocked();
311 let dest = dest.into_path_unlocked();
312 Some(ArtifactDirLayout {
313 dest: dest.clone(),
314 examples: dest.join("examples"),
315 doc: root.join("doc"),
316 timings: root.join("cargo-timings"),
317 _lock: artifact_dir_lock,
318 })
319 } else {
320 None
321 };
322 Ok(Layout {
323 artifact_dir,
324 build_dir: BuildDirLayout {
325 root: build_root.clone(),
326 deps,
327 build: build_dest.join("build"),
328 artifact,
329 incremental: build_dest.join("incremental"),
330 fingerprint: build_dest.join(".fingerprint"),
331 examples: build_dest.join("examples"),
332 tmp: build_root.join("tmp"),
333 _lock: build_dir_lock,
334 is_new_layout,
335 },
336 _lock: lock,
337 })
338 }
339
340 /// Makes sure all directories stored in the Layout exist on the filesystem.
341 pub fn prepare(&mut self) -> CargoResult<()> {
342 if let Some(ref mut artifact_dir) = self.artifact_dir {
343 artifact_dir.prepare()?;
344 }
345 self.build_dir.prepare()?;
346
347 Ok(())
348 }
349
350 pub fn artifact_dir(&self) -> Option<&ArtifactDirLayout> {
351 self.artifact_dir.as_ref()
352 }
353
354 pub fn build_dir(&self) -> &BuildDirLayout {
355 &self.build_dir
356 }
357}
358
359/// Orders two directories so that one that contains the other comes first.
360pub fn outer_first<'a>(a: &'a Path, b: &'a Path) -> [&'a Path; 2] {
361 if a.starts_with(b) { [b, a] } else { [a, b] }
362}
363
364pub struct ArtifactDirLayout {
365 /// The final artifact destination: `<artifact-dir>/debug` (or `release`).
366 dest: PathBuf,
367 /// The directory for examples
368 examples: PathBuf,
369 /// The directory for rustdoc output
370 doc: PathBuf,
371 /// The directory for --timings output
372 timings: PathBuf,
373 /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
374 /// struct is `drop`ped.
375 _lock: Option<FileLock>,
376}
377
378impl ArtifactDirLayout {
379 /// Makes sure all directories stored in the Layout exist on the filesystem.
380 pub fn prepare(&mut self) -> CargoResult<()> {
381 paths::create_dir_all(&self.examples)?;
382
383 Ok(())
384 }
385 /// Fetch the destination path for final artifacts (`/…/target/debug`).
386 pub fn dest(&self) -> &Path {
387 &self.dest
388 }
389 /// Fetch the examples path.
390 pub fn examples(&self) -> &Path {
391 &self.examples
392 }
393 /// Fetch the doc path.
394 pub fn doc(&self) -> &Path {
395 &self.doc
396 }
397 /// Fetch the cargo-timings path.
398 pub fn timings(&self) -> &Path {
399 &self.timings
400 }
401}
402
403pub struct BuildDirLayout {
404 /// The root directory: `/path/to/build-dir`.
405 /// If cross compiling: `/path/to/build-dir/$TUPLE`.
406 root: PathBuf,
407 /// The directory with rustc artifacts
408 deps: PathBuf,
409 /// The primary directory for build files
410 build: PathBuf,
411 /// The directory for artifacts, i.e. binaries, cdylibs, staticlibs
412 artifact: PathBuf,
413 /// The directory for incremental files
414 incremental: PathBuf,
415 /// The directory for fingerprints
416 fingerprint: PathBuf,
417 /// The directory for pre-uplifted examples: `build-dir/debug/examples`
418 examples: PathBuf,
419 /// The directory for temporary data of integration tests and benches
420 tmp: PathBuf,
421 /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
422 /// struct is `drop`ped.
423 ///
424 /// Will be `None` when the build-dir and target-dir are the same path as we cannot
425 /// lock the same path twice.
426 _lock: Option<FileLock>,
427 is_new_layout: bool,
428}
429
430impl BuildDirLayout {
431 /// Makes sure all directories stored in the Layout exist on the filesystem.
432 pub fn prepare(&mut self) -> CargoResult<()> {
433 if !self.is_new_layout {
434 paths::create_dir_all(&self.deps)?;
435 paths::create_dir_all(&self.fingerprint)?;
436 paths::create_dir_all(&self.examples)?;
437 }
438 paths::create_dir_all(&self.incremental)?;
439 paths::create_dir_all(&self.build)?;
440
441 Ok(())
442 }
443 /// Fetch the deps path.
444 pub fn deps(&self, pkg_dir: &str) -> PathBuf {
445 if self.is_new_layout {
446 self.out_force_new_layout(pkg_dir)
447 } else {
448 self.legacy_deps().to_path_buf()
449 }
450 }
451 /// Fetch the output path for build units. (new layout only)
452 ///
453 /// New features should consider using this so we can avoid their migrations.
454 pub fn out_force_new_layout(&self, pkg_dir: &str) -> PathBuf {
455 let mut build_dir = self.build_unit(pkg_dir);
456 // This function can be somewhat hot for packages with many dependencies, so reuse the
457 // PathBuf allocation here.
458 build_dir.push("out");
459 build_dir
460 }
461 /// Fetch the deps path. (old layout)
462 pub fn legacy_deps(&self) -> &Path {
463 &self.deps
464 }
465 pub fn root(&self) -> &Path {
466 &self.root
467 }
468 /// Fetch the build examples path.
469 pub fn examples(&self) -> &Path {
470 &self.examples
471 }
472 /// Fetch the incremental path.
473 pub fn incremental(&self) -> &Path {
474 &self.incremental
475 }
476 /// Fetch the fingerprint path.
477 pub fn fingerprint(&self, pkg_dir: &str) -> PathBuf {
478 if self.is_new_layout {
479 self.build_unit(pkg_dir).join("fingerprint")
480 } else {
481 self.legacy_fingerprint().join(pkg_dir)
482 }
483 }
484 /// Fetch the fingerprint path. (old layout)
485 pub fn legacy_fingerprint(&self) -> &Path {
486 &self.fingerprint
487 }
488 /// Fetch the build path.
489 pub fn build(&self) -> &Path {
490 &self.build
491 }
492 /// Fetch the build script path.
493 pub fn build_script(&self, pkg_dir: &str) -> PathBuf {
494 if self.is_new_layout {
495 self.deps(pkg_dir)
496 } else {
497 self.build().join(pkg_dir)
498 }
499 }
500 /// Fetch the build script execution path.
501 pub fn build_script_execution(&self, pkg_dir: &str) -> PathBuf {
502 if self.is_new_layout {
503 self.build_unit(pkg_dir).join("run")
504 } else {
505 self.build().join(pkg_dir)
506 }
507 }
508 /// Fetch the artifact path.
509 pub fn artifact(&self, pkg_dir: &str, kind: &str) -> PathBuf {
510 if self.is_new_layout {
511 self.build_unit(pkg_dir).join("artifact").join(kind)
512 } else {
513 self.artifact.join(pkg_dir).join(kind)
514 }
515 }
516 /// Fetch the build unit path
517 pub fn build_unit(&self, pkg_dir: &str) -> PathBuf {
518 self.build().join(pkg_dir)
519 }
520 /// Create and return the tmp path.
521 pub fn prepare_tmp(&self) -> CargoResult<&Path> {
522 paths::create_dir_all(&self.tmp)?;
523 Ok(&self.tmp)
524 }
525}