Skip to main content

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