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        // 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}