Add --force flag for uv cache prune (#16137)

Matching #15992 

See https://github.com/astral-sh/setup-uv/issues/588
This commit is contained in:
Zanie Blue 2025-10-06 11:36:58 -05:00 committed by GitHub
parent 3bed81866f
commit 7e4edf0fb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 81 additions and 3 deletions

View file

@ -802,6 +802,13 @@ pub struct PruneArgs {
/// that were built from source. /// that were built from source.
#[arg(long)] #[arg(long)]
pub ci: bool, pub ci: bool,
/// Force removal of the cache, ignoring in-use checks.
///
/// By default, `uv cache prune` will block until no process is reading the cache. When
/// `--force` is used, `uv cache prune` will proceed without taking a lock.
#[arg(long)]
pub force: bool,
} }
#[derive(Args)] #[derive(Args)]

View file

@ -2,6 +2,7 @@ use std::fmt::Write;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use tracing::debug;
use uv_cache::{Cache, Removal}; use uv_cache::{Cache, Removal};
use uv_fs::Simplified; use uv_fs::Simplified;
@ -10,7 +11,12 @@ use crate::commands::{ExitStatus, human_readable_bytes};
use crate::printer::Printer; use crate::printer::Printer;
/// Prune all unreachable objects from the cache. /// Prune all unreachable objects from the cache.
pub(crate) fn cache_prune(ci: bool, cache: Cache, printer: Printer) -> Result<ExitStatus> { pub(crate) fn cache_prune(
ci: bool,
force: bool,
cache: Cache,
printer: Printer,
) -> Result<ExitStatus> {
if !cache.root().exists() { if !cache.root().exists() {
writeln!( writeln!(
printer.stderr(), printer.stderr(),
@ -19,7 +25,19 @@ pub(crate) fn cache_prune(ci: bool, cache: Cache, printer: Printer) -> Result<Ex
)?; )?;
return Ok(ExitStatus::Success); return Ok(ExitStatus::Success);
} }
let cache = cache.with_exclusive_lock()?;
let cache = if force {
// If `--force` is used, attempt to acquire the exclusive lock but do not block.
match cache.with_exclusive_lock_no_wait() {
Ok(cache) => cache,
Err(cache) => {
debug!("Cache is currently in use, proceeding due to `--force`");
cache
}
}
} else {
cache.with_exclusive_lock()?
};
writeln!( writeln!(
printer.stderr(), printer.stderr(),

View file

@ -1023,7 +1023,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
command: CacheCommand::Prune(args), command: CacheCommand::Prune(args),
}) => { }) => {
show_settings!(args); show_settings!(args);
commands::cache_prune(args.ci, cache, printer) commands::cache_prune(args.ci, args.force, cache, printer)
} }
Commands::Cache(CacheNamespace { Commands::Cache(CacheNamespace {
command: CacheCommand::Dir, command: CacheCommand::Dir,

View file

@ -196,6 +196,57 @@ fn prune_stale_symlink() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn prune_force() -> Result<()> {
let context = TestContext::new("3.12").with_filtered_counts();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("typing-extensions\niniconfig")?;
// Install a requirement, to populate the cache.
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();
// When unlocked, `--force` should still take a lock
uv_snapshot!(context.filters(), context.prune().arg("--verbose").arg("--force"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG uv [VERSION] ([COMMIT] DATE)
DEBUG Acquired lock for `[CACHE_DIR]/`
Pruning cache at: [CACHE_DIR]/
No unused entries found
DEBUG Released lock at `[CACHE_DIR]/.lock`
");
// Add a stale directory to the cache.
let simple = context.cache_dir.child("simple-v4");
simple.create_dir_all()?;
// When locked, `--force` should proceed without blocking
let _cache = uv_cache::Cache::from_path(context.cache_dir.path()).with_exclusive_lock();
uv_snapshot!(context.filters(), context.prune().arg("--verbose").arg("--force"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG uv [VERSION] ([COMMIT] DATE)
DEBUG Lock is busy for `[CACHE_DIR]/`
DEBUG Cache is currently in use, proceeding due to `--force`
Pruning cache at: [CACHE_DIR]/
DEBUG Removing dangling cache bucket: [CACHE_DIR]/simple-v4
Removed 1 directory
");
Ok(())
}
/// `cache prune --ci` should remove all unzipped archives. /// `cache prune --ci` should remove all unzipped archives.
#[test] #[test]
fn prune_unzipped() -> Result<()> { fn prune_unzipped() -> Result<()> {

View file

@ -6007,6 +6007,8 @@ uv cache prune [OPTIONS]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p></dd><dt id="uv-cache-prune--directory"><a href="#uv-cache-prune--directory"><code>--directory</code></a> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p> <p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p></dd><dt id="uv-cache-prune--directory"><a href="#uv-cache-prune--directory"><code>--directory</code></a> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p> <p>Relative paths are resolved with the given directory as the base.</p>
<p>See <code>--project</code> to only change the project root directory.</p> <p>See <code>--project</code> to only change the project root directory.</p>
</dd><dt id="uv-cache-prune--force"><a href="#uv-cache-prune--force"><code>--force</code></a></dt><dd><p>Force removal of the cache, ignoring in-use checks.</p>
<p>By default, <code>uv cache prune</code> will block until no process is reading the cache. When <code>--force</code> is used, <code>uv cache prune</code> will proceed without taking a lock.</p>
</dd><dt id="uv-cache-prune--help"><a href="#uv-cache-prune--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p> </dd><dt id="uv-cache-prune--help"><a href="#uv-cache-prune--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt id="uv-cache-prune--managed-python"><a href="#uv-cache-prune--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p> </dd><dt id="uv-cache-prune--managed-python"><a href="#uv-cache-prune--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p> <p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>