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