diff --git a/src/cargo/core/compiler/build_runner/compilation_files.rs b/src/cargo/core/compiler/build_runner/compilation_files.rs index 34d4660c5be..ed6c3e72fb6 100644 --- a/src/cargo/core/compiler/build_runner/compilation_files.rs +++ b/src/cargo/core/compiler/build_runner/compilation_files.rs @@ -10,6 +10,7 @@ use lazycell::LazyCell; use tracing::debug; use super::{BuildContext, BuildRunner, CompileKind, FileFlavor, Layout}; +use crate::core::compiler::layout::BuildUnitLockLocation; use crate::core::compiler::{CompileMode, CompileTarget, CrateType, FileType, Unit}; use crate::core::{Target, TargetKind, Workspace}; use crate::util::{self, CargoResult, StableHasher}; @@ -277,6 +278,13 @@ impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> { self.layout(unit.kind).build_dir().fingerprint(&dir) } + /// The path of the partial and full locks for a given build unit + /// when fine grain locking is enabled. + pub fn build_unit_lock(&self, unit: &Unit) -> BuildUnitLockLocation { + let dir = self.pkg_dir(unit); + self.layout(unit.kind).build_dir().build_unit_lock(&dir) + } + /// Directory where incremental output for the given unit should go. pub fn incremental_dir(&self, unit: &Unit) -> &Path { self.layout(unit.kind).build_dir().incremental() diff --git a/src/cargo/core/compiler/build_runner/mod.rs b/src/cargo/core/compiler/build_runner/mod.rs index 4bd2f732fc7..1d0728e2c9e 100644 --- a/src/cargo/core/compiler/build_runner/mod.rs +++ b/src/cargo/core/compiler/build_runner/mod.rs @@ -6,15 +6,19 @@ use std::sync::{Arc, Mutex}; use crate::core::PackageId; use crate::core::compiler::compilation::{self, UnitOutput}; +use crate::core::compiler::locking::LockingMode; use crate::core::compiler::{self, Unit, artifact}; use crate::util::cache_lock::CacheLockMode; use crate::util::errors::CargoResult; +use crate::util::flock::is_on_nfs_mount; +use crate::util::rlimit; use annotate_snippets::{Level, Message}; use anyhow::{Context as _, bail}; use cargo_util::paths; use filetime::FileTime; use itertools::Itertools; use jobserver::Client; +use tracing::{debug, warn}; use super::build_plan::BuildPlan; use super::custom_build::{self, BuildDeps, BuildScriptOutputs, BuildScripts}; @@ -89,6 +93,10 @@ pub struct BuildRunner<'a, 'gctx> { /// because the target has a type error. This is in an Arc> /// because it is continuously updated as the job progresses. pub failed_scrape_units: Arc>>, + + /// The locking mode to use for this build. + /// We use fine grain by default, but fallback to coarse grain for some systems. + pub locking_mode: LockingMode, } impl<'a, 'gctx> BuildRunner<'a, 'gctx> { @@ -111,6 +119,12 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { } }; + let locking_mode = if bcx.gctx.cli_unstable().fine_grain_locking { + determine_locking_mode(&bcx)? + } else { + LockingMode::Coarse + }; + Ok(Self { bcx, compilation: Compilation::new(bcx)?, @@ -128,6 +142,7 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { lto: HashMap::new(), metadata_for_doc_units: HashMap::new(), failed_scrape_units: Arc::new(Mutex::new(HashSet::new())), + locking_mode, }) } @@ -360,11 +375,11 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { #[tracing::instrument(skip_all)] pub fn prepare_units(&mut self) -> CargoResult<()> { let dest = self.bcx.profiles.get_dir_name(); - let host_layout = Layout::new(self.bcx.ws, None, &dest)?; + let host_layout = Layout::new(self.bcx.ws, None, &dest, &self.locking_mode)?; let mut targets = HashMap::new(); for kind in self.bcx.all_kinds.iter() { if let CompileKind::Target(target) = *kind { - let layout = Layout::new(self.bcx.ws, Some(target), &dest)?; + let layout = Layout::new(self.bcx.ws, Some(target), &dest, &self.locking_mode)?; targets.insert(target, layout); } } @@ -724,3 +739,54 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> { } } } + +/// Determines if we have enough resources to safely use fine grain locking. +/// This function will raises the number of max number file descriptors the current process +/// can have open at once to make sure we are able to compile without running out of fds. +/// +/// If we cannot acquire a safe max number of file descriptors, we fallback to coarse grain +/// locking. +pub fn determine_locking_mode(bcx: &BuildContext<'_, '_>) -> CargoResult { + let total_units = bcx.unit_graph.keys().len() as u64; + + if is_on_nfs_mount(bcx.ws.build_dir().as_path_unlocked()) { + debug!("NFS detected. Disabling file system locking"); + return Ok(LockingMode::None); + } + + // This is a bit arbitrary but if we do not have at least 10 times the remaining file + // descriptors than total build units there is a chance we could hit the limit. + // This is a fairly conservative estimate to make sure we don't hit max fd errors. + let safe_threshold = total_units * 10; + + let Ok(mut limit) = rlimit::get_max_file_descriptors() else { + return Ok(LockingMode::Coarse); + }; + + if limit.soft_limit >= safe_threshold { + // The limit is higher or equal to what we deemed safe, so + // there is no need to raise the limit. + return Ok(LockingMode::Fine); + } + + let display_fd_warning = || -> CargoResult<()> { + bcx.gctx.shell().verbose(|shell| shell.warn("ulimit was to low to safely enable fine grain locking, falling back to coarse grain locking")) + }; + + if limit.hard_limit < safe_threshold { + // The max we could raise the limit to is still not enough to safely compile. + display_fd_warning()?; + return Ok(LockingMode::Coarse); + } + + limit.soft_limit = safe_threshold; + + debug!("raising fd limit to {safe_threshold}"); + if let Err(err) = rlimit::set_max_file_descriptors(limit) { + warn!("failed to raise max fds: {err}"); + display_fd_warning()?; + return Ok(LockingMode::Coarse); + } + + return Ok(LockingMode::Fine); +} diff --git a/src/cargo/core/compiler/layout.rs b/src/cargo/core/compiler/layout.rs index d0207db8aea..90aa2bf9a31 100644 --- a/src/cargo/core/compiler/layout.rs +++ b/src/cargo/core/compiler/layout.rs @@ -103,6 +103,7 @@ use crate::core::Workspace; use crate::core::compiler::CompileTarget; +use crate::core::compiler::locking::LockingMode; use crate::util::{CargoResult, FileLock}; use cargo_util::paths; use std::path::{Path, PathBuf}; @@ -126,6 +127,7 @@ impl Layout { ws: &Workspace<'_>, target: Option, dest: &str, + locking_mode: &LockingMode, ) -> CargoResult { let is_new_layout = ws.gctx().cli_unstable().build_dir_new_layout; let mut root = ws.target_dir(); @@ -152,15 +154,30 @@ impl Layout { // For now we don't do any more finer-grained locking on the artifact // directory, so just lock the entire thing for the duration of this // compile. - let artifact_dir_lock = - dest.open_rw_exclusive_create(".cargo-lock", ws.gctx(), "build directory")?; + let artifact_dir_lock = match locking_mode { + LockingMode::None => None, + LockingMode::Fine => { + Some(dest.open_ro_shared_create(".cargo-lock", ws.gctx(), "build directory")?) + } + LockingMode::Coarse => { + Some(dest.open_rw_exclusive_create(".cargo-lock", ws.gctx(), "build directory")?) + } + }; let build_dir_lock = if root != build_root { - Some(build_dest.open_rw_exclusive_create( - ".cargo-lock", - ws.gctx(), - "build directory", - )?) + match locking_mode { + LockingMode::None => None, + LockingMode::Fine => Some(build_dest.open_ro_shared_create( + ".cargo-lock", + ws.gctx(), + "build directory", + )?), + LockingMode::Coarse => Some(build_dest.open_rw_exclusive_create( + ".cargo-lock", + ws.gctx(), + "build directory", + )?), + } } else { None }; @@ -222,7 +239,7 @@ pub struct ArtifactDirLayout { timings: PathBuf, /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this /// struct is `drop`ped. - _lock: FileLock, + _lock: Option, } impl ArtifactDirLayout { @@ -345,6 +362,14 @@ impl BuildDirLayout { self.build().join(pkg_dir) } } + /// Fetch the lock paths for a build unit + pub fn build_unit_lock(&self, pkg_dir: &str) -> BuildUnitLockLocation { + let dir = self.build_unit(pkg_dir); + BuildUnitLockLocation { + partial: dir.join("partial.lock"), + full: dir.join("full.lock"), + } + } /// Fetch the artifact path. pub fn artifact(&self) -> &Path { &self.artifact @@ -359,3 +384,10 @@ impl BuildDirLayout { Ok(&self.tmp) } } + +/// See [crate::core::compiler::locking] module docs for details about build system locking +/// structure. +pub struct BuildUnitLockLocation { + pub partial: PathBuf, + pub full: PathBuf, +} diff --git a/src/cargo/core/compiler/locking.rs b/src/cargo/core/compiler/locking.rs new file mode 100644 index 00000000000..4c336116396 --- /dev/null +++ b/src/cargo/core/compiler/locking.rs @@ -0,0 +1,372 @@ +//! This module handles the locking logic during compilation. +//! +//! The locking scheme is based on build unit level locking. +//! Each build unit consists of a partial and full lock used to represent multiple lock states. +//! +//! | State | `partial.lock` | `full.lock` | +//! |------------------------|----------------|--------------| +//! | Unlocked | `unlocked` | `unlocked` | +//! | Building Exclusive | `exclusive` | `exclusive` | +//! | Building Non-Exclusive | `shared` | `exclusive` | +//! | Shared Partial | `shared` | `unlocked` | +//! | Shared Full | `shared` | `shared` | +//! +//! Generally a build unit will full the following flow: +//! 1. Acquire a "building exclusive" lock for the current build unit. +//! 2. Acquire "shared" locks on all dependency build units. +//! 3. Begin building with rustc +//! 4. If we are building a library, downgrade to a "building non-exclusive" lock when the `.rmeta` has been generated. +//! 5. Once complete release all locks. +//! +//! Most build units only require metadata (.rmeta) from dependencies, so they can begin building +//! once the dependency units have produced the .rmeta. These units take a "shared partial" lock +//! which can be taken while the dependency still holds the "build non-exclusive" lock. +//! +//! Note that some build unit types like bin and proc-macros require the full dependency build +//! (.rlib). For these unit types they must take a "shared full" lock on dependency units which will +//! block until the dependency unit is fully built. +//! +//! The primary reason for the complexity here it to enable fine grain locking while also allowing pipelined builds. +//! +//! [`CompilationLock`] is the primary interface for locking. + +use std::{ + collections::{HashMap, HashSet}, + fs::{File, OpenOptions}, + path::{Path, PathBuf}, + sync::{Arc, Condvar, LazyLock, Mutex}, +}; + +use anyhow::{Context, anyhow}; +use itertools::Itertools; +use tracing::{instrument, trace}; + +use crate::{ + CargoResult, + core::compiler::{BuildRunner, Unit, layout::BuildUnitLockLocation}, +}; + +/// The locking mode that will be used for output directories. +#[derive(Debug)] +pub enum LockingMode { + /// Completely disables locking (used for filesystems that do not support locking) + None, + /// Fine grain locking (Build unit level) + Fine, + /// Coarse grain locking (Profile level) + Coarse, +} + +/// The type of lock to take when taking a shared lock. +/// See the module documentation for more information about shared lock types. +#[derive(Debug)] +pub enum SharedLockType { + /// A shared lock that might still be compiling a .rlib + Partial, + /// A shared lock that is guaranteed to not be compiling + Full, +} + +/// A lock for compiling a build unit. +/// +/// Internally this lock is made up of many [`UnitLock`]s for the unit and it's dependencies. +pub struct CompilationLock { + /// The path to the lock file of the unit to compile + unit: UnitLock, + /// The paths to lock files of the unit's dependencies + dependency_units: Vec, +} + +impl CompilationLock { + pub fn new(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self { + let unit_lock = UnitLock::new(build_runner.files().build_unit_lock(unit)); + + let dependency_units = all_dependency_units(build_runner, unit) + .into_iter() + .map(|unit| UnitLock::new(build_runner.files().build_unit_lock(&unit))) + .collect_vec(); + + Self { + unit: unit_lock, + dependency_units, + } + } + + #[instrument(skip(self))] + pub fn lock(&mut self, ty: &SharedLockType) -> CargoResult<()> { + self.unit.lock_exclusive()?; + + for d in self.dependency_units.iter_mut() { + d.lock_shared(ty)?; + } + + trace!("acquired lock: {:?}", self.unit.partial.parent()); + + Ok(()) + } + + pub fn rmeta_produced(&mut self) -> CargoResult<()> { + trace!("downgrading lock: {:?}", self.unit.partial.parent()); + + // Downgrade the lock on the unit we are building so that we can unblock other units to + // compile. We do not need to downgrade our dependency locks since they should always be a + // shared lock. + self.unit.downgrade()?; + + Ok(()) + } +} + +/// A lock for a single build unit. +struct UnitLock { + partial: PathBuf, + full: PathBuf, + guard: Option, +} + +struct UnitLockGuard { + partial: Arc, + full: Option>, +} + +impl Drop for UnitLockGuard { + fn drop(&mut self) { + self.partial.unlock().unwrap(); + if let Some(full) = &self.full { + full.unlock().unwrap(); + } + } +} + +impl UnitLock { + pub fn new(location: BuildUnitLockLocation) -> Self { + Self { + partial: location.partial, + full: location.full, + guard: None, + } + } + + pub fn lock_exclusive(&mut self) -> CargoResult<()> { + assert!(self.guard.is_none()); + + let partial = FileLockInterner::get_or_create_lock(&self.partial)?; + partial.lock()?; + + let full = FileLockInterner::get_or_create_lock(&self.full)?; + full.lock()?; + + self.guard = Some(UnitLockGuard { + partial, + full: Some(full), + }); + Ok(()) + } + + pub fn lock_shared(&mut self, ty: &SharedLockType) -> CargoResult<()> { + assert!(self.guard.is_none()); + + let partial = FileLockInterner::get_or_create_lock(&self.partial)?; + partial.lock_shared()?; + + let full = if matches!(ty, SharedLockType::Full) { + let full_lock = FileLockInterner::get_or_create_lock(&self.full)?; + full_lock.lock_shared()?; + Some(full_lock) + } else { + None + }; + + self.guard = Some(UnitLockGuard { partial, full }); + Ok(()) + } + + pub fn downgrade(&mut self) -> CargoResult<()> { + let guard = self + .guard + .as_ref() + .context("guard was None while calling downgrade")?; + guard.partial.downgrade()?; + + Ok(()) + } +} + +fn open_file>(f: T) -> CargoResult { + Ok(OpenOptions::new() + .read(true) + .create(true) + .write(true) + .append(true) + .open(f)?) +} + +fn all_dependency_units<'a>( + build_runner: &'a BuildRunner<'a, '_>, + unit: &Unit, +) -> HashSet<&'a Unit> { + fn inner<'a>( + build_runner: &'a BuildRunner<'a, '_>, + unit: &Unit, + results: &mut HashSet<&'a Unit>, + ) { + for dep in build_runner.unit_deps(unit) { + if results.insert(&dep.unit) { + inner(&build_runner, &dep.unit, results); + } + } + } + + let mut results = HashSet::new(); + inner(build_runner, unit, &mut results); + return results; +} + +/// An interner to manage [`RcFileLock`]s to make sharing across compilation jobs easier. +pub struct FileLockInterner { + locks: Mutex>>, +} + +impl FileLockInterner { + pub fn new() -> Self { + Self { + locks: Mutex::new(HashMap::new()), + } + } + + pub fn get_or_create_lock(path: &Path) -> CargoResult> { + static GLOBAL: LazyLock = LazyLock::new(FileLockInterner::new); + + let mut locks = GLOBAL + .locks + .lock() + .map_err(|_| anyhow!("lock was poisoned"))?; + + if let Some(lock) = locks.get(path) { + return Ok(Arc::clone(lock)); + } + + let file = open_file(&path)?; + + let lock = Arc::new(RcFileLock { + inner: Mutex::new(RcFileLockInner { + file, + share_count: 0, + exclusive: false, + }), + condvar: Condvar::new(), + }); + + locks.insert(path.to_path_buf(), Arc::clone(&lock)); + + return Ok(lock); + } +} + +/// A reference counted file lock. +/// +/// This lock is designed to reduce file descriptors by sharing a single file descriptor for a +/// given lock when the lock is shared. The motivation for this is to avoid hitting file descriptor +/// limits when fine grain locking is enabled. +pub struct RcFileLock { + inner: Mutex, + condvar: Condvar, +} + +struct RcFileLockInner { + file: File, + exclusive: bool, + share_count: u32, +} + +impl RcFileLock { + pub fn lock(&self) -> CargoResult<()> { + let mut inner = self + .inner + .lock() + .map_err(|_| anyhow!("lock was poisoned"))?; + + while inner.exclusive || inner.share_count > 0 { + inner = self + .condvar + .wait(inner) + .map_err(|_| anyhow!("lock was poisoned"))?; + } + + inner.file.lock()?; + inner.exclusive = true; + + Ok(()) + } + + pub fn lock_shared(&self) -> CargoResult<()> { + let mut inner = self + .inner + .lock() + .map_err(|_| anyhow!("lock was poisoned"))?; + + while inner.exclusive { + inner = self + .condvar + .wait(inner) + .map_err(|_| anyhow!("lock was poisoned"))?; + } + + if inner.share_count == 0 { + inner.file.lock_shared()?; + inner.share_count = 1; + } else { + inner.share_count += 1; + } + + Ok(()) + } + + pub fn unlock(&self) -> CargoResult<()> { + let mut inner = self + .inner + .lock() + .map_err(|_| anyhow!("lock was poisoned"))?; + + if inner.exclusive { + assert!(inner.share_count == 0); + inner.file.unlock()?; + self.condvar.notify_all(); + inner.exclusive = false; + } else { + if inner.share_count > 1 { + inner.share_count -= 1; + } else { + inner.file.unlock()?; + inner.share_count = 0; + self.condvar.notify_all(); + } + } + + Ok(()) + } + + pub fn downgrade(&self) -> CargoResult<()> { + let mut inner = self + .inner + .lock() + .map_err(|_| anyhow!("lock was poisoned"))?; + + assert!(inner.exclusive); + assert!(inner.share_count == 0); + + // NOTE: + // > Subsequent flock() calls on an already locked file will convert an existing lock to the new lock mode. + // https://man7.org/linux/man-pages/man2/flock.2.html + // + // However, the `std::file::File::lock/lock_shared` is allowed to change this in the + // future. So its probably up to us if we are okay with using this or if we want to use a + // different interface to flock. + inner.file.lock_shared()?; + + inner.exclusive = false; + inner.share_count = 1; + + Ok(()) + } +} diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 94acfcb50cf..095a2e8a9cc 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -45,6 +45,7 @@ pub mod future_incompat; pub(crate) mod job_queue; pub(crate) mod layout; mod links; +pub mod locking; mod lto; mod output_depinfo; mod output_sbom; @@ -95,6 +96,7 @@ use self::output_depinfo::output_depinfo; use self::output_sbom::build_sbom; use self::unit_graph::UnitDep; use crate::core::compiler::future_incompat::FutureIncompatReport; +use crate::core::compiler::locking::{CompilationLock, LockingMode, SharedLockType}; use crate::core::compiler::timings::SectionTiming; pub use crate::core::compiler::unit::{Unit, UnitInterner}; use crate::core::manifest::TargetSourcePath; @@ -351,7 +353,33 @@ fn rustc( output_options.show_diagnostics = false; } let env_config = Arc::clone(build_runner.bcx.gctx.env_config()?); + + let mut lock = if build_runner.bcx.gctx.cli_unstable().fine_grain_locking + && matches!(build_runner.locking_mode, LockingMode::Fine) + { + Some(CompilationLock::new(build_runner, unit)) + } else { + None + }; + + // For libraries, we only need rmeta so we only need a partial shared lock, but for things like + // proc-macros we need the rlib so we need a full shared lock to we know the compilation is + // completely done. + let dependency_locking_mode = match unit.requires_upstream_objects() { + true => SharedLockType::Full, + false => SharedLockType::Partial, + }; + return Ok(Work::new(move |state| { + if let Some(lock) = &mut lock { + lock.lock(&dependency_locking_mode) + .expect("failed to take lock"); + + // TODO: We should probably revalidate the fingerprint here as another Cargo instance could + // have already compiled the crate before we recv'd the lock. + // For large crates re-compiling here would be quiet costly. + } + // Artifacts are in a different location than typical units, // hence we must assure the crate- and target-dependent // directory is present. @@ -434,6 +462,7 @@ fn rustc( &manifest, &target, &mut output_options, + lock.as_mut(), ) }, ) @@ -969,7 +998,32 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult SharedLockType::Full, + false => SharedLockType::Partial, + }; + Ok(Work::new(move |state| { + if let Some(lock) = &mut lock { + lock.lock(&dependency_locking_mode) + .expect("failed to take lock"); + + // TODO: We should probably revalidate the fingerprint here as another Cargo instance could + // have already compiled the crate before we recv'd the lock. + // For large crates re-compiling here would be quiet costly. + } + add_custom_flags( &mut rustdoc, &build_script_outputs.lock().unwrap(), @@ -1010,6 +1064,7 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult, ) -> CargoResult<()> { - if on_stderr_line_inner(state, line, package_id, manifest, target, options)? { + if on_stderr_line_inner(state, line, package_id, manifest, target, options, lock)? { // Check if caching is enabled. if let Some((path, cell)) = &mut options.cache_cell { // Cache the output, which will be replayed later when Fresh. @@ -2020,6 +2076,7 @@ fn on_stderr_line_inner( manifest: &ManifestErrorContext, target: &Target, options: &mut OutputOptions, + lock: Option<&mut CompilationLock>, ) -> CargoResult { // We primarily want to use this function to process JSON messages from // rustc. The compiler should always print one JSON message per line, and @@ -2256,6 +2313,9 @@ fn on_stderr_line_inner( if artifact.artifact.ends_with(".rmeta") { debug!("looks like metadata finished early!"); state.rmeta_produced(); + if let Some(lock) = lock { + lock.rmeta_produced()?; + } } return Ok(false); } @@ -2470,7 +2530,15 @@ fn replay_output_cache( break; } let trimmed = line.trim_end_matches(&['\n', '\r'][..]); - on_stderr_line(state, trimmed, package_id, &manifest, &target, &mut options)?; + on_stderr_line( + state, + trimmed, + package_id, + &manifest, + &target, + &mut options, + None, + )?; line.clear(); } Ok(()) diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 0abd8bd2068..10193e01cec 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -127,6 +127,7 @@ use std::str::FromStr; use anyhow::{Error, bail}; use cargo_util::ProcessBuilder; use serde::{Deserialize, Serialize}; +use tracing::debug; use crate::GlobalContext; use crate::core::resolver::ResolveBehavior; @@ -861,6 +862,7 @@ unstable_cli_options!( dual_proc_macros: bool = ("Build proc-macros for both the host and the target"), feature_unification: bool = ("Enable new feature unification modes in workspaces"), features: Option>, + fine_grain_locking: bool = ("Use fine grain locking instead of locking the entire build cache"), fix_edition: Option = ("Permanently unstable edition migration helper"), gc: bool = ("Track cache usage and \"garbage collect\" unused files"), #[serde(deserialize_with = "deserialize_git_features")] @@ -1241,6 +1243,9 @@ impl CliUnstable { if self.gitoxide.is_none() && cargo_use_gitoxide_instead_of_git2() { self.gitoxide = GitoxideFeatures::safe().into(); } + + self.implicitly_enable_features_if_needed(); + Ok(warnings) } @@ -1379,6 +1384,7 @@ impl CliUnstable { "direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?, "dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?, "feature-unification" => self.feature_unification = parse_empty(k, v)?, + "fine-grain-locking" => self.fine_grain_locking = parse_empty(k, v)?, "fix-edition" => { let fe = v .ok_or_else(|| anyhow::anyhow!("-Zfix-edition expected a value"))? @@ -1510,6 +1516,13 @@ impl CliUnstable { ); } } + + fn implicitly_enable_features_if_needed(&mut self) { + if self.fine_grain_locking && !self.build_dir_new_layout { + debug!("-Zbuild-dir-new-layout implicitly enabled by -Zfine-grain-locking"); + self.build_dir_new_layout = true; + } + } } /// Returns the current release channel ("stable", "beta", "nightly", "dev"). diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index a70972fae1b..dc602c608c8 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -1,3 +1,4 @@ +use crate::core::compiler::locking::LockingMode; use crate::core::compiler::{CompileKind, CompileMode, Layout, RustcTargetData}; use crate::core::profiles::Profiles; use crate::core::{PackageIdSpec, PackageIdSpecQuery, TargetKind, Workspace}; @@ -5,6 +6,7 @@ use crate::ops; use crate::util::HumanBytes; use crate::util::edit_distance; use crate::util::errors::CargoResult; +use crate::util::flock::is_on_nfs_mount; use crate::util::interning::InternedString; use crate::util::{GlobalContext, Progress, ProgressStyle}; use anyhow::bail; @@ -116,15 +118,21 @@ fn clean_specs( let target_data = RustcTargetData::new(ws, &requested_kinds)?; let (pkg_set, resolve) = ops::resolve_ws(ws, dry_run)?; let prof_dir_name = profiles.get_dir_name(); - let host_layout = Layout::new(ws, None, &prof_dir_name)?; + let locking_mode = match is_on_nfs_mount(ws.build_dir().as_path_unlocked()) { + true => LockingMode::None, + false => LockingMode::Coarse, + }; + let host_layout = Layout::new(ws, None, &prof_dir_name, &locking_mode)?; // Convert requested kinds to a Vec of layouts. let target_layouts: Vec<(CompileKind, Layout)> = requested_kinds .into_iter() .filter_map(|kind| match kind { - CompileKind::Target(target) => match Layout::new(ws, Some(target), &prof_dir_name) { - Ok(layout) => Some(Ok((kind, layout))), - Err(e) => Some(Err(e)), - }, + CompileKind::Target(target) => { + match Layout::new(ws, Some(target), &prof_dir_name, &locking_mode) { + Ok(layout) => Some(Ok((kind, layout))), + Err(e) => Some(Err(e)), + } + } CompileKind::Host => None, }) .collect::>()?; diff --git a/src/cargo/util/flock.rs b/src/cargo/util/flock.rs index 89613f04a97..5e9d3549040 100644 --- a/src/cargo/util/flock.rs +++ b/src/cargo/util/flock.rs @@ -427,7 +427,7 @@ fn acquire( } #[cfg(all(target_os = "linux", not(target_env = "musl")))] -fn is_on_nfs_mount(path: &Path) -> bool { +pub fn is_on_nfs_mount(path: &Path) -> bool { use std::ffi::CString; use std::mem; use std::os::unix::prelude::*; @@ -445,7 +445,7 @@ fn is_on_nfs_mount(path: &Path) -> bool { } #[cfg(any(not(target_os = "linux"), target_env = "musl"))] -fn is_on_nfs_mount(_path: &Path) -> bool { +pub fn is_on_nfs_mount(_path: &Path) -> bool { false } diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index 62f181f42f6..fb72437cabb 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -41,7 +41,7 @@ mod dependency_queue; pub mod diagnostic_server; pub mod edit_distance; pub mod errors; -mod flock; +pub mod flock; pub mod frontmatter; pub mod graph; mod hasher; @@ -61,6 +61,7 @@ mod once; mod progress; mod queue; pub mod restricted_names; +pub mod rlimit; pub mod rustc; mod semver_eval_ext; mod semver_ext; diff --git a/src/cargo/util/rlimit.rs b/src/cargo/util/rlimit.rs new file mode 100644 index 00000000000..38a28d0fe80 --- /dev/null +++ b/src/cargo/util/rlimit.rs @@ -0,0 +1,106 @@ +#[cfg(unix)] +use libc::{RLIMIT_NOFILE, getrlimit, rlimit, setrlimit}; + +use crate::CargoResult; + +pub struct ResourceLimits { + pub soft_limit: u64, + pub hard_limit: u64, +} + +#[cfg(unix)] +pub fn get_max_file_descriptors() -> CargoResult { + let mut rlim = rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + + let result = unsafe { getrlimit(RLIMIT_NOFILE, &mut rlim) }; + if result != 0 { + return Err(anyhow::Error::from(std::io::Error::last_os_error()) + .context("Failed to get rlimit with error code")); + } + + return Ok(ResourceLimits { + soft_limit: rlim.rlim_cur, + hard_limit: rlim.rlim_max, + }); +} + +#[cfg(windows)] +pub fn get_max_file_descriptors() -> CargoResult { + let soft_limit = windows::getmaxstdio() as u64; + + return Ok(ResourceLimits { + soft_limit, + // Windows does not provide a way to get the hard limit so we return a estimated max. + // This is likely less than max for some systems but should be supported by most and is + // likely high enough that most projects do not run out of file descriptors. + // See: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-170#remarks + hard_limit: 8192, + }); +} + +#[cfg(unix)] +pub fn set_max_file_descriptors(limits: ResourceLimits) -> CargoResult<()> { + let rlim = rlimit { + rlim_cur: limits.soft_limit, + rlim_max: limits.hard_limit, + }; + let result = unsafe { setrlimit(RLIMIT_NOFILE, &rlim) }; + if result != 0 { + return Err(anyhow::Error::from(std::io::Error::last_os_error()) + .context("Failed to set rlimit with error code")); + } + + return Ok(()); +} + +#[cfg(windows)] +pub fn set_max_file_descriptors(limits: ResourceLimits) -> CargoResult<()> { + windows::setmaxstdio(limits.soft_limit as u32)?; + return Ok(()); +} + +#[cfg(windows)] +mod windows { + use std::io; + use std::os::raw::c_int; + + unsafe extern "C" { + fn _setmaxstdio(new_max: c_int) -> c_int; + fn _getmaxstdio() -> c_int; + } + + /// Sets a maximum for the number of simultaneously open files at the stream I/O level. + /// + /// See + pub fn setmaxstdio(new_max: u32) -> io::Result { + // A negative `new_max` will cause EINVAL. + // A negative `ret` should never appear. + // It is safe even if the return value is wrong. + #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] + unsafe { + let ret = _setmaxstdio(new_max as c_int); + if ret < 0 { + return Err(io::Error::last_os_error()); + } + Ok(ret as u32) + } + } + + /// Returns the number of simultaneously open files permitted at the stream I/O level. + /// + /// See + #[must_use] + pub fn getmaxstdio() -> u32 { + // A negative `ret` should never appear. + // It is safe even if the return value is wrong. + #[allow(clippy::cast_sign_loss)] + unsafe { + let ret = _getmaxstdio(); + debug_assert!(ret >= 0); + ret as u32 + } + } +} diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 2767e247ea1..a35810f5d63 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -96,6 +96,7 @@ Each new feature described below should explain how to use it. * [gc](#gc) --- Global cache garbage collection. * [open-namespaces](#open-namespaces) --- Allow multiple packages to participate in the same API namespace * [panic-immediate-abort](#panic-immediate-abort) --- Passes `-Cpanic=immediate-abort` to the compiler. + * [fine-grain-locking](#fine-grain-locking) --- Use fine grain locking instead of locking the entire build cache * rustdoc * [rustdoc-map](#rustdoc-map) --- Provides mappings for documentation to link to external sites like [docs.rs](https://docs.rs/). * [scrape-examples](#scrape-examples) --- Shows examples within documentation. @@ -1709,6 +1710,14 @@ panic-immediate-abort = true panic = "immediate-abort" ``` +## fine-grain-locking + +* Tracking Issue: [#4282](https://github.com/rust-lang/cargo/issues/4282) + +Use fine grain locking instead of locking the entire build cache. + +Note: Fine grain locking implicitly enables [build-dir-new-layout](#build-dir-new-layout) as fine grain locking builds on that directory reoganization. + ## `[lints.cargo]` * Tracking Issue: [#12235](https://github.com/rust-lang/cargo/issues/12235) diff --git a/tests/testsuite/build_dir.rs b/tests/testsuite/build_dir.rs index 5a3f2494939..1d329675b3f 100644 --- a/tests/testsuite/build_dir.rs +++ b/tests/testsuite/build_dir.rs @@ -29,7 +29,7 @@ fn binary_with_debug() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -46,6 +46,8 @@ fn binary_with_debug() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..][EXE] [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..].d +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -74,7 +76,7 @@ fn binary_with_release() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build --release") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build --release") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -100,6 +102,8 @@ fn binary_with_release() { [ROOT]/foo/build-dir/release/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/foo/build-dir/release/build/foo/[HASH]/deps/foo[..][EXE] [ROOT]/foo/build-dir/release/build/foo/[HASH]/deps/foo[..].d +[ROOT]/foo/build-dir/release/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/release/build/foo/[HASH]/full.lock "#]]); @@ -178,7 +182,7 @@ fn libs() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -194,7 +198,7 @@ fn should_default_to_target() { .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -211,6 +215,8 @@ fn should_default_to_target() { [ROOT]/foo/target/debug/build/foo/[HASH]/deps/foo[..].d [ROOT]/foo/target/debug/foo[EXE] [ROOT]/foo/target/debug/foo.d +[ROOT]/foo/target/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/target/debug/build/foo/[HASH]/full.lock "#]]); } @@ -221,7 +227,7 @@ fn should_respect_env_var() { .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .env("CARGO_BUILD_BUILD_DIR", "build-dir") .enable_mac_dsym() @@ -237,6 +243,8 @@ fn should_respect_env_var() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..][EXE] [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..].d +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); } @@ -267,7 +275,7 @@ fn build_script_should_output_to_build_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -296,6 +304,12 @@ fn build_script_should_output_to_build_dir() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/build-script-execution/output [ROOT]/foo/build-dir/debug/build/foo/[HASH]/build-script-execution/root-output [ROOT]/foo/build-dir/debug/build/foo/[HASH]/build-script-execution/stderr +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); } @@ -327,7 +341,7 @@ fn cargo_tmpdir_should_output_to_build_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout test") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking test") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -355,6 +369,12 @@ fn cargo_tmpdir_should_output_to_build_dir() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/bin-foo.json [ROOT]/foo/build-dir/tmp/foo.txt [ROOT]/foo/build-dir/.rustc_info.json +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -383,7 +403,7 @@ fn examples_should_output_to_build_dir_and_uplift_to_target_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build --examples") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build --examples") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -398,6 +418,8 @@ fn examples_should_output_to_build_dir_and_uplift_to_target_dir() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/foo/build-dir/debug/examples/foo[..][EXE] [ROOT]/foo/build-dir/debug/examples/foo[..].d +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -427,7 +449,7 @@ fn benches_should_output_to_build_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build --bench=foo") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build --bench=foo") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -448,6 +470,10 @@ fn benches_should_output_to_build_dir() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/bin-foo [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/bin-foo.json [ROOT]/foo/build-dir/.rustc_info.json +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -475,7 +501,7 @@ fn cargo_doc_should_output_to_target_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout doc") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking doc") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -500,7 +526,7 @@ fn cargo_package_should_build_in_build_dir_and_output_to_target_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout package") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking package") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -525,6 +551,8 @@ fn cargo_package_should_build_in_build_dir_and_output_to_target_dir() { [ROOT]/foo/build-dir/package/foo-0.0.1/Cargo.toml.orig [ROOT]/foo/build-dir/package/foo-0.0.1/src/main.rs [ROOT]/foo/build-dir/package/foo-0.0.1.crate +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -552,7 +580,7 @@ fn cargo_publish_should_only_touch_build_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout publish") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking publish") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .replace_crates_io(registry.index_url()) .enable_mac_dsym() @@ -581,7 +609,7 @@ fn cargo_clean_should_clean_the_target_dir_and_build_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -596,6 +624,8 @@ fn cargo_clean_should_clean_the_target_dir_and_build_dir() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..][EXE] [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..].d +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -609,7 +639,7 @@ fn cargo_clean_should_clean_the_target_dir_and_build_dir() { "#]]); - p.cargo("-Zbuild-dir-new-layout clean") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking clean") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -650,7 +680,7 @@ fn cargo_clean_should_remove_correct_files() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -672,10 +702,14 @@ fn cargo_clean_should_remove_correct_files() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/bin-foo.json [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/dep-bin-foo [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp +[ROOT]/foo/build-dir/debug/build/bar/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/bar/[HASH]/full.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); - p.cargo("-Zbuild-dir-new-layout clean -p bar") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking clean -p bar") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -690,6 +724,8 @@ fn cargo_clean_should_remove_correct_files() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/bin-foo.json [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/dep-bin-foo [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); } @@ -708,7 +744,7 @@ fn timings_report_should_output_to_target_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build --timings") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build --timings") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -733,7 +769,7 @@ fn future_incompat_should_output_to_build_dir() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .arg("--future-incompat-report") .env("RUSTFLAGS", "-Zfuture-incompat-test") @@ -756,7 +792,7 @@ fn template_should_error_for_invalid_variables() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .with_status(101) @@ -782,7 +818,7 @@ fn template_should_suggest_nearest_variable() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .with_status(101) .with_stderr_data(str![[r#" @@ -808,7 +844,7 @@ fn template_workspace_root() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -825,6 +861,8 @@ fn template_workspace_root() { [ROOT]/foo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..][EXE] [ROOT]/foo/build-dir/debug/build/foo/[HASH]/deps/foo[..].d +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -853,7 +891,7 @@ fn template_cargo_cache_home() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -872,6 +910,8 @@ fn template_cargo_cache_home() { [ROOT]/home/.cargo/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/home/.cargo/build-dir/debug/build/foo/[HASH]/deps/foo[..][EXE] [ROOT]/home/.cargo/build-dir/debug/build/foo/[HASH]/deps/foo[..].d +[ROOT]/home/.cargo/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/home/.cargo/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -910,7 +950,7 @@ fn template_workspace_path_hash() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -933,6 +973,8 @@ fn template_workspace_path_hash() { [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/fingerprint/invoked.timestamp [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/deps/foo[..][EXE] [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/deps/foo[..].d +[ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -980,7 +1022,7 @@ fn template_workspace_path_hash_should_handle_symlink() { .build(); // Build from the non-symlinked directory - p.cargo("-Zbuild-dir-new-layout check") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking check") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .run(); @@ -1000,6 +1042,8 @@ fn template_workspace_path_hash_should_handle_symlink() { [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/fingerprint/lib-foo.json [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/deps/foo-[HASH].d [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/deps/libfoo-[HASH].rmeta +[ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -1019,7 +1063,7 @@ fn template_workspace_path_hash_should_handle_symlink() { foo_dir.rm_rf(); // Run cargo from the symlinked dir - p.cargo("-Zbuild-dir-new-layout check") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking check") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .enable_mac_dsym() .cwd(&symlinked_dir) @@ -1039,6 +1083,8 @@ fn template_workspace_path_hash_should_handle_symlink() { [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/fingerprint/lib-foo.json [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/deps/foo-[HASH].d [ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/deps/libfoo-[HASH].rmeta +[ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/partial.lock +[ROOT]/foo/foo/[HASH]/build-dir/debug/build/foo/[HASH]/full.lock "#]]); @@ -1065,7 +1111,7 @@ fn template_should_handle_reject_unmatched_brackets() { ) .build(); - p.cargo("-Zbuild-dir-new-layout build") + p.cargo("-Zbuild-dir-new-layout -Zfine-grain-locking build") .masquerade_as_nightly_cargo(&["new build-dir layout"]) .with_status(101) .with_stderr_data(str![[r#" diff --git a/tests/testsuite/cargo/z_help/stdout.term.svg b/tests/testsuite/cargo/z_help/stdout.term.svg index 86cebba751f..8dfea4c5f92 100644 --- a/tests/testsuite/cargo/z_help/stdout.term.svg +++ b/tests/testsuite/cargo/z_help/stdout.term.svg @@ -1,7 +1,7 @@ - +