#
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            paths::create_dir_all(&self.examples)?;
    318        }
    319        paths::create_dir_all(&self.incremental)?;
    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.deps(pkg_dir)
    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")
    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}