Skip to main content

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}