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. The current layout looks like this:
5//!
6//! ```text
7//! # This is the root directory for all output, the top-level package
8//! # places all of its output here.
9//! target/
10//!
11//!     # Cache of `rustc -Vv` output for performance.
12//!     .rustc-info.json
13//!
14//!     # All final artifacts are linked into this directory from `deps`.
15//!     # Note that named profiles will soon be included as separate directories
16//!     # here. They have a restricted format, similar to Rust identifiers, so
17//!     # Cargo-specific directories added in the future should use some prefix
18//!     # like `.` to avoid name collisions.
19//!     debug/  # or release/
20//!
21//!         # File used to lock the directory to prevent multiple cargo processes
22//!         # from using it at the same time.
23//!         .cargo-lock
24//!
25//!         # Hidden directory that holds all of the fingerprint files for all
26//!         # packages
27//!         .fingerprint/
28//!             # Each package is in a separate directory.
29//!             # Note that different target kinds have different filename prefixes.
30//!             $pkgname-$META/
31//!                 # Set of source filenames for this package.
32//!                 dep-lib-$targetname
33//!                 # Timestamp when this package was last built.
34//!                 invoked.timestamp
35//!                 # The fingerprint hash.
36//!                 lib-$targetname
37//!                 # Detailed information used for logging the reason why
38//!                 # something is being recompiled.
39//!                 lib-$targetname.json
40//!                 # The console output from the compiler. This is cached
41//!                 # so that warnings can be redisplayed for "fresh" units.
42//!                 output-lib-$targetname
43//!
44//!         # This is the root directory for all rustc artifacts except build
45//!         # scripts, examples, and test and bench executables. Almost every
46//!         # artifact should have a metadata hash added to its filename to
47//!         # prevent collisions. One notable exception is dynamic libraries.
48//!         deps/
49//!
50//!             # Each artifact dependency gets in its own directory.
51//!             /artifact/$pkgname-$META/$kind
52//!
53//!         # Root directory for all compiled examples.
54//!         examples/
55//!
56//!         # Directory used to store incremental data for the compiler (when
57//!         # incremental is enabled.
58//!         incremental/
59//!
60//!         # This is the location at which the output of all custom build
61//!         # commands are rooted.
62//!         build/
63//!
64//!             # Each package gets its own directory where its build script and
65//!             # script output are placed
66//!             $pkgname-$META/    # For the build script itself.
67//!                 # The build script executable (name may be changed by user).
68//!                 build-script-build-$META
69//!                 # Hard link to build-script-build-$META.
70//!                 build-script-build
71//!                 # Dependency information generated by rustc.
72//!                 build-script-build-$META.d
73//!                 # Debug information, depending on platform and profile
74//!                 # settings.
75//!                 <debug symbols>
76//!
77//!             # The package shows up twice with two different metadata hashes.
78//!             $pkgname-$META/  # For the output of the build script.
79//!                 # Timestamp when the build script was last executed.
80//!                 invoked.timestamp
81//!                 # Directory where script can output files ($OUT_DIR).
82//!                 out/
83//!                 # Output from the build script.
84//!                 output
85//!                 # Path to `out`, used to help when the target directory is
86//!                 # moved.
87//!                 root-output
88//!                 # Stderr output from the build script.
89//!                 stderr
90//!
91//!     # Output from rustdoc
92//!     doc/
93//!
94//!     # Used by `cargo package` and `cargo publish` to build a `.crate` file.
95//!     package/
96//!
97//!     # Experimental feature for generated build scripts.
98//!     .metabuild/
99//! ```
100//!
101//! When cross-compiling, the layout is the same, except it appears in
102//! `target/$TRIPLE`.
103
104use crate::core::Workspace;
105use crate::core::compiler::CompileTarget;
106use crate::util::flock::is_on_nfs_mount;
107use crate::util::{CargoResult, FileLock};
108use cargo_util::paths;
109use std::path::{Path, PathBuf};
110
111/// Contains the paths of all target output locations.
112///
113/// See module docs for more information.
114pub struct Layout {
115    artifact_dir: Option<ArtifactDirLayout>,
116    build_dir: BuildDirLayout,
117}
118
119impl Layout {
120    /// Calculate the paths for build output, lock the build directory, and return as a Layout.
121    ///
122    /// This function will block if the directory is already locked.
123    ///
124    /// `dest` should be the final artifact directory name. Currently either
125    /// "debug" or "release".
126    pub fn new(
127        ws: &Workspace<'_>,
128        target: Option<CompileTarget>,
129        dest: &str,
130        must_take_artifact_dir_lock: bool,
131        must_take_build_dir_lock_exclusively: bool,
132    ) -> CargoResult<Layout> {
133        let is_new_layout = ws.gctx().cli_unstable().build_dir_new_layout;
134        let mut root = ws.target_dir();
135        let mut build_root = ws.build_dir();
136        if let Some(target) = target {
137            root.push(target.short_name());
138            build_root.push(target.short_name());
139        }
140        let build_dest = build_root.join(dest);
141        let dest = root.join(dest);
142        // If the root directory doesn't already exist go ahead and create it
143        // here. Use this opportunity to exclude it from backups as well if the
144        // system supports it since this is a freshly created folder.
145        //
146        paths::create_dir_all_excluded_from_backups_atomic(root.as_path_unlocked())?;
147        if root != build_root {
148            paths::create_dir_all_excluded_from_backups_atomic(build_root.as_path_unlocked())?;
149        }
150
151        // Now that the excluded from backups target root is created we can create the
152        // actual destination (sub)subdirectory.
153        paths::create_dir_all(dest.as_path_unlocked())?;
154
155        // We always need to take the build-dir lock but if the build-dir == artifact-dir then we
156        // only take the artifact-dir. (locking both as they are the same dir)
157        // However we need to take into account that for some builds like `cargo check` we avoid
158        // locking the artifact-dir. We still need to lock the build-dir to avoid file corruption.
159        let build_dir_lock = if (must_take_artifact_dir_lock && root == build_root)
160            || is_on_nfs_mount(build_root.as_path_unlocked())
161        {
162            None
163        } else {
164            if ws.gctx().cli_unstable().fine_grain_locking && !must_take_build_dir_lock_exclusively
165            {
166                Some(build_dest.open_ro_shared_create(
167                    ".cargo-lock",
168                    ws.gctx(),
169                    "build directory",
170                )?)
171            } else {
172                Some(build_dest.open_rw_exclusive_create(
173                    ".cargo-lock",
174                    ws.gctx(),
175                    "build directory",
176                )?)
177            }
178        };
179        let build_root = build_root.into_path_unlocked();
180        let build_dest = build_dest.as_path_unlocked();
181        let deps = build_dest.join("deps");
182        let artifact = deps.join("artifact");
183
184        let artifact_dir = if must_take_artifact_dir_lock {
185            // For now we don't do any more finer-grained locking on the artifact
186            // directory, so just lock the entire thing for the duration of this
187            // compile.
188            let artifact_dir_lock = if is_on_nfs_mount(root.as_path_unlocked()) {
189                None
190            } else {
191                Some(dest.open_rw_exclusive_create(
192                    ".cargo-lock",
193                    ws.gctx(),
194                    "artifact directory",
195                )?)
196            };
197            let root = root.into_path_unlocked();
198            let dest = dest.into_path_unlocked();
199            Some(ArtifactDirLayout {
200                dest: dest.clone(),
201                examples: dest.join("examples"),
202                doc: root.join("doc"),
203                timings: root.join("cargo-timings"),
204                _lock: artifact_dir_lock,
205            })
206        } else {
207            None
208        };
209        Ok(Layout {
210            artifact_dir,
211            build_dir: BuildDirLayout {
212                root: build_root.clone(),
213                deps,
214                build: build_dest.join("build"),
215                artifact,
216                incremental: build_dest.join("incremental"),
217                fingerprint: build_dest.join(".fingerprint"),
218                examples: build_dest.join("examples"),
219                tmp: build_root.join("tmp"),
220                _lock: build_dir_lock,
221                is_new_layout,
222            },
223        })
224    }
225
226    /// Makes sure all directories stored in the Layout exist on the filesystem.
227    pub fn prepare(&mut self) -> CargoResult<()> {
228        if let Some(ref mut artifact_dir) = self.artifact_dir {
229            artifact_dir.prepare()?;
230        }
231        self.build_dir.prepare()?;
232
233        Ok(())
234    }
235
236    pub fn artifact_dir(&self) -> Option<&ArtifactDirLayout> {
237        self.artifact_dir.as_ref()
238    }
239
240    pub fn build_dir(&self) -> &BuildDirLayout {
241        &self.build_dir
242    }
243}
244
245pub struct ArtifactDirLayout {
246    /// The final artifact destination: `<artifact-dir>/debug` (or `release`).
247    dest: PathBuf,
248    /// The directory for examples
249    examples: PathBuf,
250    /// The directory for rustdoc output
251    doc: PathBuf,
252    /// The directory for --timings output
253    timings: PathBuf,
254    /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
255    /// struct is `drop`ped.
256    _lock: Option<FileLock>,
257}
258
259impl ArtifactDirLayout {
260    /// Makes sure all directories stored in the Layout exist on the filesystem.
261    pub fn prepare(&mut self) -> CargoResult<()> {
262        paths::create_dir_all(&self.examples)?;
263
264        Ok(())
265    }
266    /// Fetch the destination path for final artifacts  (`/…/target/debug`).
267    pub fn dest(&self) -> &Path {
268        &self.dest
269    }
270    /// Fetch the examples path.
271    pub fn examples(&self) -> &Path {
272        &self.examples
273    }
274    /// Fetch the doc path.
275    pub fn doc(&self) -> &Path {
276        &self.doc
277    }
278    /// Fetch the cargo-timings path.
279    pub fn timings(&self) -> &Path {
280        &self.timings
281    }
282}
283
284pub struct BuildDirLayout {
285    /// The root directory: `/path/to/build-dir`.
286    /// If cross compiling: `/path/to/build-dir/$TRIPLE`.
287    root: PathBuf,
288    /// The directory with rustc artifacts
289    deps: PathBuf,
290    /// The primary directory for build files
291    build: PathBuf,
292    /// The directory for artifacts, i.e. binaries, cdylibs, staticlibs
293    artifact: PathBuf,
294    /// The directory for incremental files
295    incremental: PathBuf,
296    /// The directory for fingerprints
297    fingerprint: PathBuf,
298    /// The directory for pre-uplifted examples: `build-dir/debug/examples`
299    examples: PathBuf,
300    /// The directory for temporary data of integration tests and benches
301    tmp: PathBuf,
302    /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
303    /// struct is `drop`ped.
304    ///
305    /// Will be `None` when the build-dir and target-dir are the same path as we cannot
306    /// lock the same path twice.
307    _lock: Option<FileLock>,
308    is_new_layout: bool,
309}
310
311impl BuildDirLayout {
312    /// Makes sure all directories stored in the Layout exist on the filesystem.
313    pub fn prepare(&mut self) -> CargoResult<()> {
314        if !self.is_new_layout {
315            paths::create_dir_all(&self.deps)?;
316            paths::create_dir_all(&self.fingerprint)?;
317        }
318        paths::create_dir_all(&self.incremental)?;
319        paths::create_dir_all(&self.examples)?;
320        paths::create_dir_all(&self.build)?;
321
322        Ok(())
323    }
324    /// Fetch the deps path.
325    pub fn deps(&self, pkg_dir: &str) -> PathBuf {
326        if self.is_new_layout {
327            self.deps_new_layout(pkg_dir)
328        } else {
329            self.legacy_deps().to_path_buf()
330        }
331    }
332    /// Fetch the deps path. (new layout)
333    ///
334    /// New features should consider using this so we can avoid their migrations.
335    pub fn deps_new_layout(&self, pkg_dir: &str) -> PathBuf {
336        self.build_unit(pkg_dir).join("deps")
337    }
338    /// Fetch the deps path. (old layout)
339    pub fn legacy_deps(&self) -> &Path {
340        &self.deps
341    }
342    pub fn root(&self) -> &Path {
343        &self.root
344    }
345    /// Fetch the build examples path.
346    pub fn examples(&self) -> &Path {
347        &self.examples
348    }
349    /// Fetch the incremental path.
350    pub fn incremental(&self) -> &Path {
351        &self.incremental
352    }
353    /// Fetch the fingerprint path.
354    pub fn fingerprint(&self, pkg_dir: &str) -> PathBuf {
355        if self.is_new_layout {
356            self.build_unit(pkg_dir).join("fingerprint")
357        } else {
358            self.legacy_fingerprint().to_path_buf().join(pkg_dir)
359        }
360    }
361    /// Fetch the fingerprint path. (old layout)
362    pub fn legacy_fingerprint(&self) -> &Path {
363        &self.fingerprint
364    }
365    /// Fetch the build path.
366    pub fn build(&self) -> &Path {
367        &self.build
368    }
369    /// Fetch the build script path.
370    pub fn build_script(&self, pkg_dir: &str) -> PathBuf {
371        if self.is_new_layout {
372            self.build_unit(pkg_dir).join("build-script")
373        } else {
374            self.build().join(pkg_dir)
375        }
376    }
377    /// Fetch the build script execution path.
378    pub fn build_script_execution(&self, pkg_dir: &str) -> PathBuf {
379        if self.is_new_layout {
380            self.build_unit(pkg_dir).join("build-script-execution")
381        } else {
382            self.build().join(pkg_dir)
383        }
384    }
385    /// Fetch the artifact path.
386    pub fn artifact(&self) -> &Path {
387        &self.artifact
388    }
389    /// Fetch the build unit path
390    pub fn build_unit(&self, pkg_dir: &str) -> PathBuf {
391        self.build().join(pkg_dir)
392    }
393    /// Create and return the tmp path.
394    pub fn prepare_tmp(&self) -> CargoResult<&Path> {
395        paths::create_dir_all(&self.tmp)?;
396        Ok(&self.tmp)
397    }
398}