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}