cargo/core/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/$TRIPLE`.
209
210use crate::core::Workspace;
211use crate::core::compiler::CompileTarget;
212use crate::util::flock::is_on_nfs_mount;
213use crate::util::{CargoResult, FileLock};
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 paths::create_dir_all_excluded_from_backups_atomic(root.as_path_unlocked())?;
254 if root != build_root {
255 paths::create_dir_all_excluded_from_backups_atomic(build_root.as_path_unlocked())?;
256 }
257
258 // Now that the excluded from backups target root is created we can create the
259 // actual destination (sub)subdirectory.
260 paths::create_dir_all(dest.as_path_unlocked())?;
261
262 let build_dir_lock = if is_on_nfs_mount(build_root.as_path_unlocked()) {
263 None
264 } else {
265 if ws.gctx().cli_unstable().fine_grain_locking && !must_take_build_dir_lock_exclusively
266 {
267 Some(build_dest.open_ro_shared_create(
268 ".cargo-build-lock",
269 ws.gctx(),
270 "build directory",
271 )?)
272 } else {
273 Some(build_dest.open_rw_exclusive_create(
274 ".cargo-build-lock",
275 ws.gctx(),
276 "build directory",
277 )?)
278 }
279 };
280 let build_root = build_root.into_path_unlocked();
281 let build_dest = build_dest.as_path_unlocked();
282 let deps = build_dest.join("deps");
283 let artifact = deps.join("artifact");
284
285 // We take a shared lock on `.cargo-lock` to make sure we don't run currently with
286 // older versions of Cargo (including tools that use Cargo as a library) that don't support
287 // `.cargo-build-lock`.
288 let lock = if is_on_nfs_mount(root.as_path_unlocked()) {
289 None
290 } else {
291 Some(dest.open_ro_shared_create(".cargo-lock", ws.gctx(), "artifact directory")?)
292 };
293
294 let artifact_dir = if must_take_artifact_dir_lock {
295 // For now we don't do any more finer-grained locking on the artifact
296 // directory, so just lock the entire thing for the duration of this
297 // compile.
298 let artifact_dir_lock = if is_on_nfs_mount(root.as_path_unlocked()) {
299 None
300 } else {
301 Some(dest.open_rw_exclusive_create(
302 ".cargo-artifact-lock",
303 ws.gctx(),
304 "artifact directory",
305 )?)
306 };
307 let root = root.into_path_unlocked();
308 let dest = dest.into_path_unlocked();
309 Some(ArtifactDirLayout {
310 dest: dest.clone(),
311 examples: dest.join("examples"),
312 doc: root.join("doc"),
313 timings: root.join("cargo-timings"),
314 _lock: artifact_dir_lock,
315 })
316 } else {
317 None
318 };
319 Ok(Layout {
320 artifact_dir,
321 build_dir: BuildDirLayout {
322 root: build_root.clone(),
323 deps,
324 build: build_dest.join("build"),
325 artifact,
326 incremental: build_dest.join("incremental"),
327 fingerprint: build_dest.join(".fingerprint"),
328 examples: build_dest.join("examples"),
329 tmp: build_root.join("tmp"),
330 _lock: build_dir_lock,
331 is_new_layout,
332 },
333 _lock: lock,
334 })
335 }
336
337 /// Makes sure all directories stored in the Layout exist on the filesystem.
338 pub fn prepare(&mut self) -> CargoResult<()> {
339 if let Some(ref mut artifact_dir) = self.artifact_dir {
340 artifact_dir.prepare()?;
341 }
342 self.build_dir.prepare()?;
343
344 Ok(())
345 }
346
347 pub fn artifact_dir(&self) -> Option<&ArtifactDirLayout> {
348 self.artifact_dir.as_ref()
349 }
350
351 pub fn build_dir(&self) -> &BuildDirLayout {
352 &self.build_dir
353 }
354}
355
356pub struct ArtifactDirLayout {
357 /// The final artifact destination: `<artifact-dir>/debug` (or `release`).
358 dest: PathBuf,
359 /// The directory for examples
360 examples: PathBuf,
361 /// The directory for rustdoc output
362 doc: PathBuf,
363 /// The directory for --timings output
364 timings: PathBuf,
365 /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
366 /// struct is `drop`ped.
367 _lock: Option<FileLock>,
368}
369
370impl ArtifactDirLayout {
371 /// Makes sure all directories stored in the Layout exist on the filesystem.
372 pub fn prepare(&mut self) -> CargoResult<()> {
373 paths::create_dir_all(&self.examples)?;
374
375 Ok(())
376 }
377 /// Fetch the destination path for final artifacts (`/…/target/debug`).
378 pub fn dest(&self) -> &Path {
379 &self.dest
380 }
381 /// Fetch the examples path.
382 pub fn examples(&self) -> &Path {
383 &self.examples
384 }
385 /// Fetch the doc path.
386 pub fn doc(&self) -> &Path {
387 &self.doc
388 }
389 /// Fetch the cargo-timings path.
390 pub fn timings(&self) -> &Path {
391 &self.timings
392 }
393}
394
395pub struct BuildDirLayout {
396 /// The root directory: `/path/to/build-dir`.
397 /// If cross compiling: `/path/to/build-dir/$TRIPLE`.
398 root: PathBuf,
399 /// The directory with rustc artifacts
400 deps: PathBuf,
401 /// The primary directory for build files
402 build: PathBuf,
403 /// The directory for artifacts, i.e. binaries, cdylibs, staticlibs
404 artifact: PathBuf,
405 /// The directory for incremental files
406 incremental: PathBuf,
407 /// The directory for fingerprints
408 fingerprint: PathBuf,
409 /// The directory for pre-uplifted examples: `build-dir/debug/examples`
410 examples: PathBuf,
411 /// The directory for temporary data of integration tests and benches
412 tmp: PathBuf,
413 /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
414 /// struct is `drop`ped.
415 ///
416 /// Will be `None` when the build-dir and target-dir are the same path as we cannot
417 /// lock the same path twice.
418 _lock: Option<FileLock>,
419 is_new_layout: bool,
420}
421
422impl BuildDirLayout {
423 /// Makes sure all directories stored in the Layout exist on the filesystem.
424 pub fn prepare(&mut self) -> CargoResult<()> {
425 if !self.is_new_layout {
426 paths::create_dir_all(&self.deps)?;
427 paths::create_dir_all(&self.fingerprint)?;
428 paths::create_dir_all(&self.examples)?;
429 }
430 paths::create_dir_all(&self.incremental)?;
431 paths::create_dir_all(&self.build)?;
432
433 Ok(())
434 }
435 /// Fetch the deps path.
436 pub fn deps(&self, pkg_dir: &str) -> PathBuf {
437 if self.is_new_layout {
438 self.out_force_new_layout(pkg_dir)
439 } else {
440 self.legacy_deps().to_path_buf()
441 }
442 }
443 /// Fetch the output path for build units. (new layout only)
444 ///
445 /// New features should consider using this so we can avoid their migrations.
446 pub fn out_force_new_layout(&self, pkg_dir: &str) -> PathBuf {
447 self.build_unit(pkg_dir).join("out")
448 }
449 /// Fetch the deps path. (old layout)
450 pub fn legacy_deps(&self) -> &Path {
451 &self.deps
452 }
453 pub fn root(&self) -> &Path {
454 &self.root
455 }
456 /// Fetch the build examples path.
457 pub fn examples(&self) -> &Path {
458 &self.examples
459 }
460 /// Fetch the incremental path.
461 pub fn incremental(&self) -> &Path {
462 &self.incremental
463 }
464 /// Fetch the fingerprint path.
465 pub fn fingerprint(&self, pkg_dir: &str) -> PathBuf {
466 if self.is_new_layout {
467 self.build_unit(pkg_dir).join("fingerprint")
468 } else {
469 self.legacy_fingerprint().to_path_buf().join(pkg_dir)
470 }
471 }
472 /// Fetch the fingerprint path. (old layout)
473 pub fn legacy_fingerprint(&self) -> &Path {
474 &self.fingerprint
475 }
476 /// Fetch the build path.
477 pub fn build(&self) -> &Path {
478 &self.build
479 }
480 /// Fetch the build script path.
481 pub fn build_script(&self, pkg_dir: &str) -> PathBuf {
482 if self.is_new_layout {
483 self.deps(pkg_dir)
484 } else {
485 self.build().join(pkg_dir)
486 }
487 }
488 /// Fetch the build script execution path.
489 pub fn build_script_execution(&self, pkg_dir: &str) -> PathBuf {
490 if self.is_new_layout {
491 self.build_unit(pkg_dir).join("run")
492 } else {
493 self.build().join(pkg_dir)
494 }
495 }
496 /// Fetch the artifact path.
497 pub fn artifact(&self, pkg_dir: &str, kind: &str) -> PathBuf {
498 if self.is_new_layout {
499 self.build_unit(pkg_dir).join("artifact").join(kind)
500 } else {
501 self.artifact.join(pkg_dir).join(kind)
502 }
503 }
504 /// Fetch the build unit path
505 pub fn build_unit(&self, pkg_dir: &str) -> PathBuf {
506 self.build().join(pkg_dir)
507 }
508 /// Create and return the tmp path.
509 pub fn prepare_tmp(&self) -> CargoResult<&Path> {
510 paths::create_dir_all(&self.tmp)?;
511 Ok(&self.tmp)
512 }
513}