From 2f375dab7c563de95ee2d835b0304ae8e307a600 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Thu, 11 Nov 2021 16:12:34 -0800 Subject: [PATCH] clean: add --include-cache option This patch adds the `--include-cache` flag to `cargo clean`, which allows the command to also remove related artifacts from `CARGO_HOME` such as downloaded `.crate` files in `~/.cargo/cache/` and extracted source directories in `~/.cargo/src/`. Note that this feature is not intended to replace the `cargo-cache` command which does smart cache management. Instead, this command simply blows away whatever it's asked to delete with no smarts whatsoever. Fixes #3289. --- src/bin/cargo/commands/clean.rs | 5 ++ src/cargo/core/source/mod.rs | 22 ++++++- src/cargo/ops/cargo_clean.rs | 44 ++++++++++++++ src/cargo/sources/directory.rs | 10 +++- src/cargo/sources/git/source.rs | 10 +++- src/cargo/sources/path.rs | 10 +++- src/cargo/sources/registry/local.rs | 4 ++ src/cargo/sources/registry/mod.rs | 10 ++++ src/cargo/sources/registry/remote.rs | 4 ++ src/cargo/sources/replaced.rs | 9 +++ tests/testsuite/clean.rs | 87 +++++++++++++++++++++++++++- 11 files changed, 210 insertions(+), 5 deletions(-) diff --git a/src/bin/cargo/commands/clean.rs b/src/bin/cargo/commands/clean.rs index 0c61f30aaaa..8d978c8d39b 100644 --- a/src/bin/cargo/commands/clean.rs +++ b/src/bin/cargo/commands/clean.rs @@ -7,6 +7,10 @@ pub fn cli() -> App { subcommand("clean") .about("Remove artifacts that cargo has generated in the past") .arg(opt("quiet", "No output printed to stdout").short("q")) + .arg(opt( + "include-cache", + "Whether to clean Cargo's cache directories", + )) .arg_package_spec_simple("Package to clean artifacts for") .arg_manifest_path() .arg_target_triple("Target triple to clean output for") @@ -31,6 +35,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { requested_profile: args.get_profile_name(config, "dev", ProfileChecking::Custom)?, profile_specified: args.is_present("profile") || args.is_present("release"), doc: args.is_present("doc"), + include_cache: args.is_present("include-cache"), }; ops::clean(&ws, &opts)?; Ok(()) diff --git a/src/cargo/core/source/mod.rs b/src/cargo/core/source/mod.rs index 81009ea6529..1fcb244f96d 100644 --- a/src/cargo/core/source/mod.rs +++ b/src/cargo/core/source/mod.rs @@ -3,7 +3,7 @@ use std::fmt; use crate::core::package::PackageSet; use crate::core::{Dependency, Package, PackageId, Summary}; -use crate::util::{CargoResult, Config}; +use crate::util::{CargoResult, Config, Filesystem}; mod source_id; @@ -101,6 +101,10 @@ pub trait Source { /// Query if a package is yanked. Only registry sources can mark packages /// as yanked. This ignores the yanked whitelist. fn is_yanked(&mut self, _pkg: PackageId) -> CargoResult; + + fn source_cache(&mut self) -> Option<&mut Filesystem>; + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem>; } pub enum MaybePackage { @@ -178,6 +182,14 @@ impl<'a, T: Source + ?Sized + 'a> Source for Box { fn is_yanked(&mut self, pkg: PackageId) -> CargoResult { (**self).is_yanked(pkg) } + + fn source_cache(&mut self) -> Option<&mut Filesystem> { + (**self).source_cache() + } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + (**self).dot_crate_cache() + } } impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T { @@ -240,6 +252,14 @@ impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T { fn is_yanked(&mut self, pkg: PackageId) -> CargoResult { (**self).is_yanked(pkg) } + + fn source_cache(&mut self) -> Option<&mut Filesystem> { + (**self).source_cache() + } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + (**self).dot_crate_cache() + } } /// A `HashMap` of `SourceId` -> `Box`. diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 76b6927519a..04e67ee3457 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -2,6 +2,7 @@ use crate::core::compiler::{CompileKind, CompileMode, Layout, RustcTargetData}; use crate::core::profiles::Profiles; use crate::core::{PackageIdSpec, TargetKind, Workspace}; use crate::ops; +use crate::sources::SourceConfigMap; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; use crate::util::lev_distance; @@ -9,6 +10,7 @@ use crate::util::Config; use anyhow::Context as _; use cargo_util::paths; +use std::collections::{hash_map, HashMap}; use std::fs; use std::path::Path; @@ -24,6 +26,8 @@ pub struct CleanOptions<'a> { pub requested_profile: InternedString, /// Whether to just clean the doc directory pub doc: bool, + /// Whether to also clean the package cache + pub include_cache: bool, } /// Cleans the package's build artifacts. @@ -53,6 +57,12 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { // Note that we don't bother grabbing a lock here as we're just going to // blow it all away anyway. if opts.spec.is_empty() { + if opts.include_cache { + // We also need to remove src/ and cache/ from CARGO_HOME. + rm_rf(&config.registry_cache_path().into_path_unlocked(), config)?; + rm_rf(&config.registry_source_path().into_path_unlocked(), config)?; + } + return rm_rf(&target_dir.into_path_unlocked(), config); } @@ -133,6 +143,16 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { } let packages = pkg_set.get_many(pkg_ids)?; + let mut registries = HashMap::new(); + let (_pc_lock, sources) = if opts.include_cache { + ( + Some(config.acquire_package_cache_lock()?), + Some(SourceConfigMap::new(config)?), + ) + } else { + (None, None) + }; + for pkg in packages { let pkg_dir = format!("{}-*", pkg.name()); @@ -141,6 +161,30 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { rm_rf_glob(&layout.fingerprint().join(&pkg_dir), config)?; } + if let Some(sources) = &sources { + let source_id = pkg.package_id().source_id(); + let registry = match registries.entry(source_id) { + hash_map::Entry::Occupied(o) => o.into_mut(), + hash_map::Entry::Vacant(v) => { + let reg = sources.load(source_id, &Default::default())?; + v.insert(reg) + } + }; + + // The as_path_unlocked are okay since we've acquired the package cache lock. + if let Some(src_path) = registry.source_cache() { + rm_rf_glob(&src_path.as_path_unlocked().join(&pkg_dir), config)?; + } + if let Some(cache_path) = registry.dot_crate_cache() { + rm_rf_glob( + &cache_path + .as_path_unlocked() + .join(&format!("{}.crate", pkg_dir)), + config, + )?; + } + } + for target in pkg.targets() { if target.is_custom_build() { // Get both the build_script_build and the output directory. diff --git a/src/cargo/sources/directory.rs b/src/cargo/sources/directory.rs index 7a00b560f87..7c8c6d13c67 100644 --- a/src/cargo/sources/directory.rs +++ b/src/cargo/sources/directory.rs @@ -6,7 +6,7 @@ use crate::core::source::MaybePackage; use crate::core::{Dependency, Package, PackageId, Source, SourceId, Summary}; use crate::sources::PathSource; use crate::util::errors::CargoResult; -use crate::util::Config; +use crate::util::{Config, Filesystem}; use anyhow::Context as _; use cargo_util::{paths, Sha256}; @@ -205,4 +205,12 @@ impl<'cfg> Source for DirectorySource<'cfg> { fn is_yanked(&mut self, _pkg: PackageId) -> CargoResult { Ok(false) } + + fn source_cache(&mut self) -> Option<&mut Filesystem> { + None + } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + None + } } diff --git a/src/cargo/sources/git/source.rs b/src/cargo/sources/git/source.rs index 9d7c42b829f..79ef284c5c3 100644 --- a/src/cargo/sources/git/source.rs +++ b/src/cargo/sources/git/source.rs @@ -5,7 +5,7 @@ use crate::sources::git::utils::GitRemote; use crate::sources::PathSource; use crate::util::errors::CargoResult; use crate::util::hex::short_hash; -use crate::util::Config; +use crate::util::{Config, Filesystem}; use anyhow::Context; use log::trace; use std::fmt::{self, Debug, Formatter}; @@ -212,6 +212,14 @@ impl<'cfg> Source for GitSource<'cfg> { fn is_yanked(&mut self, _pkg: PackageId) -> CargoResult { Ok(false) } + + fn source_cache(&mut self) -> Option<&mut Filesystem> { + None + } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + None + } } #[cfg(test)] diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index 64e0fdd73e1..038024ada83 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use crate::core::source::MaybePackage; use crate::core::{Dependency, Package, PackageId, Source, SourceId, Summary}; use crate::ops; -use crate::util::{internal, CargoResult, Config}; +use crate::util::{internal, CargoResult, Config, Filesystem}; use anyhow::Context as _; use cargo_util::paths; use filetime::FileTime; @@ -541,4 +541,12 @@ impl<'cfg> Source for PathSource<'cfg> { fn is_yanked(&mut self, _pkg: PackageId) -> CargoResult { Ok(false) } + + fn source_cache(&mut self) -> Option<&mut Filesystem> { + None + } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + None + } } diff --git a/src/cargo/sources/registry/local.rs b/src/cargo/sources/registry/local.rs index cccc553ee9e..2d0cee9088b 100644 --- a/src/cargo/sources/registry/local.rs +++ b/src/cargo/sources/registry/local.rs @@ -120,4 +120,8 @@ impl<'cfg> RegistryData for LocalRegistry<'cfg> { ) -> CargoResult { panic!("this source doesn't download") } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + None + } } diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index d9df11bbfd2..c0f0f18fd93 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -435,6 +435,8 @@ pub trait RegistryData { /// (remote=git, local=files). fn index_path(&self) -> &Filesystem; + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem>; + /// Loads the JSON for a specific named package from the index. /// /// * `root` is the root path to the index. @@ -790,4 +792,12 @@ impl<'cfg> Source for RegistrySource<'cfg> { } self.index.is_yanked(pkg, &mut *self.ops) } + + fn source_cache(&mut self) -> Option<&mut Filesystem> { + Some(&mut self.src_path) + } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + self.ops.dot_crate_cache() + } } diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs index f3bc0edb53a..0fb1ba13898 100644 --- a/src/cargo/sources/registry/remote.rs +++ b/src/cargo/sources/registry/remote.rs @@ -329,6 +329,10 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { } false } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + Some(&mut self.cache_path) + } } impl<'cfg> Drop for RemoteRegistry<'cfg> { diff --git a/src/cargo/sources/replaced.rs b/src/cargo/sources/replaced.rs index 468df095cd6..cec2bceb99f 100644 --- a/src/cargo/sources/replaced.rs +++ b/src/cargo/sources/replaced.rs @@ -1,6 +1,7 @@ use crate::core::source::MaybePackage; use crate::core::{Dependency, Package, PackageId, Source, SourceId, Summary}; use crate::util::errors::CargoResult; +use crate::util::Filesystem; use anyhow::Context as _; @@ -127,4 +128,12 @@ impl<'cfg> Source for ReplacedSource<'cfg> { fn is_yanked(&mut self, pkg: PackageId) -> CargoResult { self.inner.is_yanked(pkg) } + + fn source_cache(&mut self) -> Option<&mut Filesystem> { + self.inner.source_cache() + } + + fn dot_crate_cache(&mut self) -> Option<&mut Filesystem> { + self.inner.dot_crate_cache() + } } diff --git a/tests/testsuite/clean.rs b/tests/testsuite/clean.rs index cabfd641047..7f65e36e928 100644 --- a/tests/testsuite/clean.rs +++ b/tests/testsuite/clean.rs @@ -1,7 +1,9 @@ //! Tests for the `cargo clean` command. +use cargo::core::SourceId; +use cargo_test_support::install::cargo_home; use cargo_test_support::paths::is_symlink; -use cargo_test_support::registry::Package; +use cargo_test_support::registry::{registry_url, Package}; use cargo_test_support::{basic_bin_manifest, basic_manifest, git, main_file, project, rustc_host}; use std::env; use std::path::Path; @@ -299,6 +301,89 @@ fn clean_verbose() { p.cargo("build").run(); } +#[cargo_test] +fn clean_include_cache() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1" + baz = "0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + Package::new("bar", "0.1.0").publish(); + Package::new("baz", "0.1.0").publish(); + + p.cargo("build").run(); + + let src_cache = cargo_home().join("registry").join("src"); + let dot_crate_cache = cargo_home().join("registry").join("cache"); + assert!(src_cache.exists()); + assert!(dot_crate_cache.exists()); + p.cargo("clean --include-cache").with_stdout("").run(); + assert!(!src_cache.exists()); + assert!(!dot_crate_cache.exists()); +} +#[cargo_test] +fn clean_package_include_cache() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1" + baz = "0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + Package::new("bar", "0.1.0").publish(); + Package::new("baz", "0.1.0").publish(); + + p.cargo("build").run(); + + let id = SourceId::for_registry(®istry_url()).unwrap(); + let hash = cargo::util::hex::short_hash(&id); + let src_cache = cargo_home() + .join("registry") + .join("src") + .join(format!("-{}", hash)); + let bar_src_cache = src_cache.join(format!("bar-0.1.0")); + let baz_src_cache = src_cache.join(format!("baz-0.1.0")); + let dot_crate_cache = cargo_home() + .join("registry") + .join("cache") + .join(format!("-{}", hash)); + let bar_dot_crate_cache = dot_crate_cache.join(format!("bar-0.1.0.crate")); + let baz_dot_crate_cache = dot_crate_cache.join(format!("baz-0.1.0.crate")); + assert!(bar_src_cache.exists()); + assert!(baz_src_cache.exists()); + assert!(bar_dot_crate_cache.exists()); + assert!(baz_dot_crate_cache.exists()); + p.cargo("clean --include-cache -p bar") + .with_stdout("") + .run(); + assert!(!bar_src_cache.exists()); + assert!(baz_src_cache.exists()); + assert!(!bar_dot_crate_cache.exists()); + assert!(baz_dot_crate_cache.exists()); +} + #[cargo_test] fn clean_remove_rlib_rmeta() { let p = project()