Skip to content

Commit

Permalink
Add --extracted to cache prune
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Jul 24, 2024
1 parent 17d63b0 commit 588ad1b
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 7 deletions.
30 changes: 29 additions & 1 deletion crates/uv-cache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ impl Cache {
}

/// Run the garbage collector on the cache, removing any dangling entries.
pub fn prune(&self) -> Result<Removal, io::Error> {
pub fn prune(&self, all_unzipped: bool) -> Result<Removal, io::Error> {
let mut summary = Removal::default();

// First, remove any top-level directories that are unused. These typically represent
Expand Down Expand Up @@ -386,6 +386,34 @@ impl Cache {
Err(err) => return Err(err),
}

// Third, if enabled, remove all unzipped wheels, leaving only the wheel archives.
if all_unzipped {
// Remove the entire pre-built wheel cache, since every entry is an unzipped wheel.
match fs::read_dir(self.bucket(CacheBucket::Wheels)) {
Ok(entries) => {
for entry in entries {
let entry = entry?;
let path = fs_err::canonicalize(entry.path())?;
if path.is_dir() {
debug!("Removing unzipped wheel entry: {}", path.display());
summary += rm_rf(path)?;
}
}
}
Err(err) if err.kind() == io::ErrorKind::NotFound => (),
Err(err) => return Err(err),
}

// Remove any unzipped wheels (i.e., symlinks) from the built wheels cache.
for entry in walkdir::WalkDir::new(self.bucket(CacheBucket::SourceDistributions)) {
let entry = entry?;
if entry.file_type().is_symlink() {
debug!("Removing unzipped wheel entry: {}", entry.path().display());
summary += rm_rf(entry.path())?;
}
}
}

// Third, remove any unused archives (by searching for archives that are not symlinked).
// TODO(charlie): Remove any unused source distributions. This requires introspecting the
// cache contents, e.g., reading and deserializing the manifests.
Expand Down
16 changes: 15 additions & 1 deletion crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ pub enum CacheCommand {
/// Clear the cache, removing all entries or those linked to specific packages.
Clean(CleanArgs),
/// Prune all unreachable objects from the cache.
Prune,
Prune(PruneArgs),
/// Show the cache directory.
Dir,
}
Expand All @@ -291,6 +291,20 @@ pub struct CleanArgs {
pub package: Vec<PackageName>,
}

#[derive(Args, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct PruneArgs {
/// Whether to remove unzipped wheels from the cache, leaving only zipped wheel entries.
///
/// By default, uv stores unzipped wheels in the cache, which enables high-performance package
/// installation. In some scenarios, though, persisting unzipped wheels may be undesirable. For
/// example, in GitHub Actions or other CI environments, uploading unzipped wheels to a remote
/// cache may have a negative impact on cache performance. Pruning unzipped wheels will leave
/// the cache with any built wheels in their zipped form.
#[arg(long)]
pub all_unzipped: bool,
}

#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct PipNamespace {
Expand Down
8 changes: 6 additions & 2 deletions crates/uv/src/commands/cache_prune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ use crate::commands::{human_readable_bytes, ExitStatus};
use crate::printer::Printer;

/// Prune all unreachable objects from the cache.
pub(crate) fn cache_prune(cache: &Cache, printer: Printer) -> Result<ExitStatus> {
pub(crate) fn cache_prune(
all_unzipped: bool,
cache: &Cache,
printer: Printer,
) -> Result<ExitStatus> {
if !cache.root().exists() {
writeln!(
printer.stderr(),
Expand All @@ -27,7 +31,7 @@ pub(crate) fn cache_prune(cache: &Cache, printer: Printer) -> Result<ExitStatus>
)?;

let summary = cache
.prune()
.prune(all_unzipped)
.with_context(|| format!("Failed to prune cache at: {}", cache.root().user_display()))?;

// Write a summary of the number of files and directories removed.
Expand Down
7 changes: 5 additions & 2 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,8 +538,11 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
commands::cache_clean(&args.package, &cache, printer)
}
Commands::Cache(CacheNamespace {
command: CacheCommand::Prune,
}) => commands::cache_prune(&cache, printer),
command: CacheCommand::Prune(args),
}) => {
show_settings!(args);
commands::cache_prune(args.all_unzipped, &cache, printer)
}
Commands::Cache(CacheNamespace {
command: CacheCommand::Dir,
}) => {
Expand Down
54 changes: 53 additions & 1 deletion crates/uv/tests/cache_prune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::prelude::*;

use common::uv_snapshot;
use indoc::indoc;

use crate::common::TestContext;

Expand Down Expand Up @@ -171,3 +171,55 @@ fn prune_stale_symlink() -> Result<()> {

Ok(())
}

/// `cache prune --all-unzips` should remove all unzipped archives.
#[test]
fn prune_unzipped() -> Result<()> {
let context = TestContext::new("3.12");

let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(indoc! { r"
source-distribution==0.0.1
" })?;

// Install a requirement, to populate the cache.
uv_snapshot!(context.filters(), context.pip_sync().env_remove("UV_EXCLUDE_NEWER").arg("requirements.txt").arg("--reinstall"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ source-distribution==0.0.1
"###);

uv_snapshot!(context.filters(), context.prune().arg("--all-unzipped"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Pruning cache at: [CACHE_DIR]/
Removed 151 files ([SIZE])
"###);

// Reinstalling the source distribution should not require re-downloading the source
// distribution.
uv_snapshot!(context.filters(), context.pip_sync().env_remove("UV_EXCLUDE_NEWER").arg("requirements.txt").arg("--reinstall").arg("--offline"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- source-distribution==0.0.1
+ source-distribution==0.0.1
"###);

Ok(())
}

0 comments on commit 588ad1b

Please sign in to comment.