Add a `uv cache size` command (#16032)

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Implement `uv cache size` to output the cache directory size in raw
bytes by default, with a `--human` option for human-readable output.

close #15821

<!-- How was it tested? -->

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
chisato 2025-11-03 04:44:28 +08:00 committed by GitHub
parent 17c9656df2
commit 8b479efd2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 222 additions and 3 deletions

View File

@ -757,7 +757,6 @@ pub enum CacheCommand {
Prune(PruneArgs), Prune(PruneArgs),
/// Show the cache directory. /// Show the cache directory.
/// ///
///
/// By default, the cache is stored in `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Unix and /// By default, the cache is stored in `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Unix and
/// `%LOCALAPPDATA%\uv\cache` on Windows. /// `%LOCALAPPDATA%\uv\cache` on Windows.
/// ///
@ -770,6 +769,12 @@ pub enum CacheCommand {
/// Note that it is important for performance for the cache directory to be located on the same /// Note that it is important for performance for the cache directory to be located on the same
/// file system as the Python environment uv is operating on. /// file system as the Python environment uv is operating on.
Dir, Dir,
/// Show the cache size.
///
/// Displays the total size of the cache directory. This includes all downloaded and built
/// wheels, source distributions, and other cached data. By default, outputs the size in raw
/// bytes; use `--human` for human-readable output.
Size(SizeArgs),
} }
#[derive(Args, Debug)] #[derive(Args, Debug)]
@ -811,6 +816,13 @@ pub struct PruneArgs {
pub force: bool, pub force: bool,
} }
#[derive(Args, Debug)]
pub struct SizeArgs {
/// Display the cache size in human-readable format (e.g., `1.2 GiB` instead of raw bytes).
#[arg(long = "human", short = 'H', alias = "human-readable")]
pub human: bool,
}
#[derive(Args)] #[derive(Args)]
pub struct PipNamespace { pub struct PipNamespace {
#[command(subcommand)] #[command(subcommand)]

View File

@ -20,6 +20,7 @@ bitflags::bitflags! {
const FORMAT = 1 << 8; const FORMAT = 1 << 8;
const NATIVE_AUTH = 1 << 9; const NATIVE_AUTH = 1 << 9;
const S3_ENDPOINT = 1 << 10; const S3_ENDPOINT = 1 << 10;
const CACHE_SIZE = 1 << 11;
} }
} }
@ -40,6 +41,7 @@ impl PreviewFeatures {
Self::FORMAT => "format", Self::FORMAT => "format",
Self::NATIVE_AUTH => "native-auth", Self::NATIVE_AUTH => "native-auth",
Self::S3_ENDPOINT => "s3-endpoint", Self::S3_ENDPOINT => "s3-endpoint",
Self::CACHE_SIZE => "cache-size",
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"), _ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
} }
} }
@ -88,6 +90,7 @@ impl FromStr for PreviewFeatures {
"format" => Self::FORMAT, "format" => Self::FORMAT,
"native-auth" => Self::NATIVE_AUTH, "native-auth" => Self::NATIVE_AUTH,
"s3-endpoint" => Self::S3_ENDPOINT, "s3-endpoint" => Self::S3_ENDPOINT,
"cache-size" => Self::CACHE_SIZE,
_ => { _ => {
warn_user_once!("Unknown preview feature: `{part}`"); warn_user_once!("Unknown preview feature: `{part}`");
continue; continue;

View File

@ -0,0 +1,53 @@
use std::fmt::Write;
use anyhow::Result;
use crate::commands::{ExitStatus, human_readable_bytes};
use crate::printer::Printer;
use uv_cache::Cache;
use uv_preview::{Preview, PreviewFeatures};
use uv_warnings::warn_user;
/// Display the total size of the cache.
pub(crate) fn cache_size(
cache: &Cache,
human_readable: bool,
printer: Printer,
preview: Preview,
) -> Result<ExitStatus> {
if !preview.is_enabled(PreviewFeatures::CACHE_SIZE) {
warn_user!(
"`uv cache size` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::CACHE_SIZE
);
}
if !cache.root().exists() {
if human_readable {
writeln!(printer.stdout_important(), "0B")?;
} else {
writeln!(printer.stdout_important(), "0")?;
}
return Ok(ExitStatus::Success);
}
// Walk the entire cache root
let total_bytes: u64 = walkdir::WalkDir::new(cache.root())
.follow_links(false)
.into_iter()
.filter_map(Result::ok)
.filter_map(|entry| match entry.metadata() {
Ok(metadata) if metadata.is_file() => Some(metadata.len()),
_ => None,
})
.sum();
if human_readable {
let (bytes, unit) = human_readable_bytes(total_bytes);
writeln!(printer.stdout_important(), "{bytes:.1}{unit}")?;
} else {
writeln!(printer.stdout_important(), "{total_bytes}")?;
}
Ok(ExitStatus::Success)
}

View File

@ -17,6 +17,7 @@ pub(crate) use build_frontend::build_frontend;
pub(crate) use cache_clean::cache_clean; pub(crate) use cache_clean::cache_clean;
pub(crate) use cache_dir::cache_dir; pub(crate) use cache_dir::cache_dir;
pub(crate) use cache_prune::cache_prune; pub(crate) use cache_prune::cache_prune;
pub(crate) use cache_size::cache_size;
pub(crate) use help::help; pub(crate) use help::help;
pub(crate) use pip::check::pip_check; pub(crate) use pip::check::pip_check;
pub(crate) use pip::compile::pip_compile; pub(crate) use pip::compile::pip_compile;
@ -75,6 +76,7 @@ mod build_frontend;
mod cache_clean; mod cache_clean;
mod cache_dir; mod cache_dir;
mod cache_prune; mod cache_prune;
mod cache_size;
mod diagnostics; mod diagnostics;
mod help; mod help;
pub(crate) mod pip; pub(crate) mod pip;

View File

@ -1058,6 +1058,9 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
commands::cache_dir(&cache); commands::cache_dir(&cache);
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Commands::Cache(CacheNamespace {
command: CacheCommand::Size(args),
}) => commands::cache_size(&cache, args.human, printer, globals.preview),
Commands::Build(args) => { Commands::Build(args) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::BuildSettings::resolve(args, filesystem, environment); let args = settings::BuildSettings::resolve(args, filesystem, environment);

View File

@ -0,0 +1,59 @@
use assert_cmd::assert::OutputAssertExt;
use crate::common::{TestContext, uv_snapshot};
/// Test that `cache size` returns 0 for an empty cache directory (raw output).
#[test]
fn cache_size_empty_raw() {
let context = TestContext::new("3.12");
// Clean cache first to ensure truly empty state
context.clean().assert().success();
uv_snapshot!(context.cache_size().arg("--preview"), @r"
success: true
exit_code: 0
----- stdout -----
0
----- stderr -----
");
}
/// Test that `cache size` returns raw bytes after installing packages.
#[test]
fn cache_size_with_packages_raw() {
let context = TestContext::new("3.12");
// Install a requirement to populate the cache.
context.pip_install().arg("iniconfig").assert().success();
// Check cache size is now positive (raw bytes).
uv_snapshot!(context.with_filtered_cache_size().filters(), context.cache_size().arg("--preview"), @r"
success: true
exit_code: 0
----- stdout -----
[SIZE]
----- stderr -----
");
}
/// Test that `cache size --human` returns human-readable format after installing packages.
#[test]
fn cache_size_with_packages_human() {
let context = TestContext::new("3.12");
// Install a requirement to populate the cache.
context.pip_install().arg("iniconfig").assert().success();
// Check cache size with --human flag
uv_snapshot!(context.with_filtered_cache_size().filters(), context.cache_size().arg("--preview").arg("--human"), @r"
success: true
exit_code: 0
----- stdout -----
[SIZE]
----- stderr -----
");
}

View File

@ -169,6 +169,20 @@ impl TestContext {
self self
} }
/// Add extra filtering for cache size output
#[must_use]
pub fn with_filtered_cache_size(mut self) -> Self {
// Filter raw byte counts (numbers on their own line)
self.filters
.push((r"(?m)^\d+\n".to_string(), "[SIZE]\n".to_string()));
// Filter human-readable sizes (e.g., "384.2 KiB")
self.filters.push((
r"(?m)^\d+(\.\d+)? [KMGT]i?B\n".to_string(),
"[SIZE]\n".to_string(),
));
self
}
/// Add extra standard filtering for Windows-compatible missing file errors. /// Add extra standard filtering for Windows-compatible missing file errors.
pub fn with_filtered_missing_file_error(mut self) -> Self { pub fn with_filtered_missing_file_error(mut self) -> Self {
// The exact message string depends on the system language, so we remove it. // The exact message string depends on the system language, so we remove it.
@ -1265,6 +1279,14 @@ impl TestContext {
command command
} }
/// Create a `uv cache size` command.
pub fn cache_size(&self) -> Command {
let mut command = Self::new_command();
command.arg("cache").arg("size");
self.add_shared_options(&mut command, false);
command
}
/// Create a `uv build_backend` command. /// Create a `uv build_backend` command.
/// ///
/// Note that this command is hidden and only invoking it through a build frontend is supported. /// Note that this command is hidden and only invoking it through a build frontend is supported.

View File

@ -19,6 +19,9 @@ mod cache_clean;
#[cfg(all(feature = "python", feature = "pypi"))] #[cfg(all(feature = "python", feature = "pypi"))]
mod cache_prune; mod cache_prune;
#[cfg(all(feature = "python", feature = "pypi"))]
mod cache_size;
#[cfg(all(feature = "python", feature = "pypi", feature = "test-ecosystem"))] #[cfg(all(feature = "python", feature = "pypi", feature = "test-ecosystem"))]
mod ecosystem; mod ecosystem;

View File

@ -7831,7 +7831,7 @@ fn preview_features() {
show_settings: true, show_settings: true,
preview: Preview { preview: Preview {
flags: PreviewFeatures( flags: PreviewFeatures(
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT, PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE,
), ),
}, },
python_preference: Managed, python_preference: Managed,
@ -8059,7 +8059,7 @@ fn preview_features() {
show_settings: true, show_settings: true,
preview: Preview { preview: Preview {
flags: PreviewFeatures( flags: PreviewFeatures(
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT, PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE,
), ),
}, },
python_preference: Managed, python_preference: Managed,

View File

@ -5919,6 +5919,7 @@ uv cache [OPTIONS] <COMMAND>
<dl class="cli-reference"><dt><a href="#uv-cache-clean"><code>uv cache clean</code></a></dt><dd><p>Clear the cache, removing all entries or those linked to specific packages</p></dd> <dl class="cli-reference"><dt><a href="#uv-cache-clean"><code>uv cache clean</code></a></dt><dd><p>Clear the cache, removing all entries or those linked to specific packages</p></dd>
<dt><a href="#uv-cache-prune"><code>uv cache prune</code></a></dt><dd><p>Prune all unreachable objects from the cache</p></dd> <dt><a href="#uv-cache-prune"><code>uv cache prune</code></a></dt><dd><p>Prune all unreachable objects from the cache</p></dd>
<dt><a href="#uv-cache-dir"><code>uv cache dir</code></a></dt><dd><p>Show the cache directory</p></dd> <dt><a href="#uv-cache-dir"><code>uv cache dir</code></a></dt><dd><p>Show the cache directory</p></dd>
<dt><a href="#uv-cache-size"><code>uv cache size</code></a></dt><dd><p>Show the cache size</p></dd>
</dl> </dl>
### uv cache clean ### uv cache clean
@ -6115,6 +6116,67 @@ uv cache dir [OPTIONS]
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p> <p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
</dd></dl> </dd></dl>
### uv cache size
Show the cache size.
Displays the total size of the cache directory. This includes all downloaded and built wheels, source distributions, and other cached data. By default, outputs the size in raw bytes; use `--human` for human-readable output.
<h3 class="cli-reference">Usage</h3>
```
uv cache size [OPTIONS]
```
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt id="uv-cache-size--allow-insecure-host"><a href="#uv-cache-size--allow-insecure-host"><code>--allow-insecure-host</code></a>, <code>--trusted-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-cache-size--cache-dir"><a href="#uv-cache-size--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
<p>To view the location of the cache directory, run <code>uv cache dir</code>.</p>
<p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p></dd><dt id="uv-cache-size--color"><a href="#uv-cache-size--color"><code>--color</code></a> <i>color-choice</i></dt><dd><p>Control the use of color in output.</p>
<p>By default, uv will automatically detect support for colors when writing to a terminal.</p>
<p>Possible values:</p>
<ul>
<li><code>auto</code>: Enables colored output only when the output is going to a terminal or TTY with support</li>
<li><code>always</code>: Enables colored output regardless of the detected environment</li>
<li><code>never</code>: Disables colored output</li>
</ul></dd><dt id="uv-cache-size--config-file"><a href="#uv-cache-size--config-file"><code>--config-file</code></a> <i>config-file</i></dt><dd><p>The path to a <code>uv.toml</code> file to use for configuration.</p>
<p>While uv configuration can be included in a <code>pyproject.toml</code> file, it is not allowed in this context.</p>
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p></dd><dt id="uv-cache-size--directory"><a href="#uv-cache-size--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>See <code>--project</code> to only change the project root directory.</p>
<p>May also be set with the <code>UV_WORKING_DIRECTORY</code> environment variable.</p></dd><dt id="uv-cache-size--help"><a href="#uv-cache-size--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt id="uv-cache-size--human"><a href="#uv-cache-size--human"><code>--human</code></a>, <code>--human-readable</code>, <code>-H</code></dt><dd><p>Display the cache size in human-readable format (e.g., <code>1.2 GiB</code> instead of raw bytes)</p>
</dd><dt id="uv-cache-size--managed-python"><a href="#uv-cache-size--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>May also be set with the <code>UV_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-cache-size--native-tls"><a href="#uv-cache-size--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform's native certificate store.</p>
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
<p>However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.</p>
<p>May also be set with the <code>UV_NATIVE_TLS</code> environment variable.</p></dd><dt id="uv-cache-size--no-cache"><a href="#uv-cache-size--no-cache"><code>--no-cache</code></a>, <code>--no-cache-dir</code>, <code>-n</code></dt><dd><p>Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation</p>
<p>May also be set with the <code>UV_NO_CACHE</code> environment variable.</p></dd><dt id="uv-cache-size--no-config"><a href="#uv-cache-size--no-config"><code>--no-config</code></a></dt><dd><p>Avoid discovering configuration files (<code>pyproject.toml</code>, <code>uv.toml</code>).</p>
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p></dd><dt id="uv-cache-size--no-managed-python"><a href="#uv-cache-size--no-managed-python"><code>--no-managed-python</code></a></dt><dd><p>Disable use of uv-managed Python versions.</p>
<p>Instead, uv will search for a suitable Python version on the system.</p>
<p>May also be set with the <code>UV_NO_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-cache-size--no-progress"><a href="#uv-cache-size--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
<p>For example, spinners or progress bars.</p>
<p>May also be set with the <code>UV_NO_PROGRESS</code> environment variable.</p></dd><dt id="uv-cache-size--no-python-downloads"><a href="#uv-cache-size--no-python-downloads"><code>--no-python-downloads</code></a></dt><dd><p>Disable automatic downloads of Python.</p>
</dd><dt id="uv-cache-size--offline"><a href="#uv-cache-size--offline"><code>--offline</code></a></dt><dd><p>Disable network access.</p>
<p>When disabled, uv will only use locally cached data and locally available files.</p>
<p>May also be set with the <code>UV_OFFLINE</code> environment variable.</p></dd><dt id="uv-cache-size--project"><a href="#uv-cache-size--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
<p>All <code>pyproject.toml</code>, <code>uv.toml</code>, and <code>.python-version</code> files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (<code>.venv</code>).</p>
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
<p>See <code>--directory</code> to change the working directory entirely.</p>
<p>This setting has no effect when used in the <code>uv pip</code> interface.</p>
<p>May also be set with the <code>UV_PROJECT</code> environment variable.</p></dd><dt id="uv-cache-size--quiet"><a href="#uv-cache-size--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
</dd><dt id="uv-cache-size--verbose"><a href="#uv-cache-size--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output.</p>
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
</dd></dl>
## uv self ## uv self
Manage the uv executable Manage the uv executable