diff --git a/src/bin/cargo/commands/clean.rs b/src/bin/cargo/commands/clean.rs index 68b2db6632b..c44a2968114 100644 --- a/src/bin/cargo/commands/clean.rs +++ b/src/bin/cargo/commands/clean.rs @@ -1,10 +1,11 @@ use crate::command_prelude::*; -use cargo::core::gc::{parse_human_size, parse_time_span}; -use cargo::core::gc::{AutoGcKind, GcOpts}; +use crate::util::cache_lock::CacheLockMode; +use cargo::core::gc::Gc; +use cargo::core::gc::{parse_human_size, parse_time_span, GcOpts}; +use cargo::core::global_cache_tracker::GlobalCacheTracker; +use cargo::ops::CleanContext; use cargo::ops::{self, CleanOptions}; use cargo::util::print_available_packages; -use cargo::CargoResult; -use clap::builder::{PossibleValuesParser, TypedValueParser}; use std::time::Duration; pub fn cli() -> Command { @@ -19,145 +20,106 @@ pub fn cli() -> Command { .arg_target_dir() .arg_manifest_path() .arg_dry_run("Display what would be deleted without deleting anything") - // NOTE: Not all of these options may get stabilized. Some of them are - // very low-level details, and may not be something typical users need. - .arg( - optional_opt( - "gc", - "Delete old and unused files (unstable) (comma separated)", - ) - .hide(true) - .value_name("KINDS") - .value_parser( - PossibleValuesParser::new(["all", "download", "target"]).map(|x| { - match x.as_str() { - "all" => AutoGcKind::All, - "download" => AutoGcKind::Download, - "target" => panic!("target is not yet implemented"), - x => panic!("possible value out of sync with `{x}`"), - } - }), - ) - .require_equals(true), - ) - .arg( - opt( - "max-src-age", - "Deletes source cache files that have not been used \ - since the given age (unstable)", - ) - .value_name("DURATION") - .value_parser(parse_time_span) - .hide(true), - ) - .arg( - opt( - "max-crate-age", - "Deletes crate cache files that have not been used \ - since the given age (unstable)", - ) - .value_name("DURATION") - .value_parser(parse_time_span) - .hide(true), - ) - .arg( - opt( - "max-index-age", - "Deletes registry indexes that have not been used \ - since the given age (unstable)", - ) - .value_name("DURATION") - .value_parser(parse_time_span) - .hide(true), - ) - .arg( - opt( - "max-git-co-age", - "Deletes git dependency checkouts that have not been used \ - since the given age (unstable)", - ) - .value_name("DURATION") - .value_parser(parse_time_span) - .hide(true), - ) - .arg( - opt( - "max-git-db-age", - "Deletes git dependency clones that have not been used \ - since the given age (unstable)", - ) - .value_name("DURATION") - .value_parser(parse_time_span) - .hide(true), - ) - .arg( - opt( - "max-download-age", - "Deletes any downloaded cache data that has not been used \ - since the given age (unstable)", - ) - .value_name("DURATION") - .value_parser(parse_time_span) - .hide(true), - ) - .arg( - opt( - "max-src-size", - "Deletes source cache files until the cache is under the given size (unstable)", - ) - .value_name("SIZE") - .value_parser(parse_human_size) - .hide(true), - ) - .arg( - opt( - "max-crate-size", - "Deletes crate cache files until the cache is under the given size (unstable)", - ) - .value_name("SIZE") - .value_parser(parse_human_size) - .hide(true), - ) - .arg( - opt( - "max-git-size", - "Deletes git dependency caches until the cache is under the given size (unstable)", - ) - .value_name("SIZE") - .value_parser(parse_human_size) - .hide(true), - ) - .arg( - opt( - "max-download-size", - "Deletes downloaded cache data until the cache is under the given size (unstable)", - ) - .value_name("SIZE") - .value_parser(parse_human_size) - .hide(true), - ) - // These are unimplemented. Leaving here as a guide for how this is - // intended to evolve. These will likely change, this is just a sketch - // of ideas. - .arg( - opt( - "max-target-age", - "Deletes any build artifact files that have not been used \ - since the given age (unstable) (UNIMPLEMENTED)", - ) - .value_name("DURATION") - .value_parser(parse_time_span) - .hide(true), - ) - .arg( - opt( - "max-target-size", - "Deletes build artifact files until the cache is under the given size \ - (unstable) (UNIMPLEMENTED)", - ) - .value_name("SIZE") - .value_parser(parse_human_size) - .hide(true), + .args_conflicts_with_subcommands(true) + .subcommand( + subcommand("gc") + .about("Clean global caches") + .hide(true) + // FIXME: arg_quiet doesn't work because `config_configure` + // doesn't know about subcommands. + .arg_dry_run("Display what would be deleted without deleting anything") + // NOTE: Not all of these options may get stabilized. Some of them are + // very low-level details, and may not be something typical users need. + .arg( + opt( + "max-src-age", + "Deletes source cache files that have not been used \ + since the given age (unstable)", + ) + .value_name("DURATION") + .value_parser(parse_time_span), + ) + .arg( + opt( + "max-crate-age", + "Deletes crate cache files that have not been used \ + since the given age (unstable)", + ) + .value_name("DURATION") + .value_parser(parse_time_span), + ) + .arg( + opt( + "max-index-age", + "Deletes registry indexes that have not been used \ + since the given age (unstable)", + ) + .value_name("DURATION") + .value_parser(parse_time_span), + ) + .arg( + opt( + "max-git-co-age", + "Deletes git dependency checkouts that have not been used \ + since the given age (unstable)", + ) + .value_name("DURATION") + .value_parser(parse_time_span), + ) + .arg( + opt( + "max-git-db-age", + "Deletes git dependency clones that have not been used \ + since the given age (unstable)", + ) + .value_name("DURATION") + .value_parser(parse_time_span), + ) + .arg( + opt( + "max-download-age", + "Deletes any downloaded cache data that has not been used \ + since the given age (unstable)", + ) + .value_name("DURATION") + .value_parser(parse_time_span), + ) + .arg( + opt( + "max-src-size", + "Deletes source cache files until the cache is under the \ + given size (unstable)", + ) + .value_name("SIZE") + .value_parser(parse_human_size), + ) + .arg( + opt( + "max-crate-size", + "Deletes crate cache files until the cache is under the \ + given size (unstable)", + ) + .value_name("SIZE") + .value_parser(parse_human_size), + ) + .arg( + opt( + "max-git-size", + "Deletes git dependency caches until the cache is under \ + the given size (unstable)", + ) + .value_name("SIZE") + .value_parser(parse_human_size), + ) + .arg( + opt( + "max-download-size", + "Deletes downloaded cache data until the cache is under \ + the given size (unstable)", + ) + .value_name("SIZE") + .value_parser(parse_human_size), + ), ) .after_help(color_print::cstr!( "Run `cargo help clean` for more detailed information.\n" @@ -165,77 +127,21 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - let ws = args.workspace(config); - - if args.is_present_with_zero_values("package") { - print_available_packages(&ws?)?; - return Ok(()); - } - - let unstable_gc = |opt| { - config.cli_unstable().fail_if_stable_opt_custom_z( - opt, - 12633, - "gc", - config.cli_unstable().gc, - ) - }; - let unstable_size_opt = |opt| -> CargoResult> { - let arg = args.get_one::(opt).copied(); - if arg.is_some() { - unstable_gc(opt)?; + match args.subcommand() { + Some(("gc", args)) => { + return gc(config, args); } - Ok(arg) - }; - let unstable_duration_opt = |opt| -> CargoResult> { - let arg = args.get_one::(opt).copied(); - if arg.is_some() { - unstable_gc(opt)?; - } - Ok(arg) - }; - let unimplemented_opt = |opt| -> CargoResult<()> { - if args.contains_id(opt) { - anyhow::bail!("option --{opt} is not yet implemented"); + Some((cmd, _)) => { + unreachable!("unexpected command {}", cmd) } - Ok(()) - }; - let unimplemented_size_opt = |opt| -> CargoResult> { - unimplemented_opt(opt)?; - Ok(None) - }; - let unimplemented_duration_opt = |opt| -> CargoResult> { - unimplemented_opt(opt)?; - Ok(None) - }; - - let mut gc: Vec<_> = args - .get_many::("gc") - .unwrap_or_default() - .cloned() - .collect(); - if gc.is_empty() && args.contains_id("gc") { - gc.push(AutoGcKind::All); - } - if !gc.is_empty() { - unstable_gc("gc")?; + None => {} } - let mut gc_opts = GcOpts { - max_src_age: unstable_duration_opt("max-src-age")?, - max_crate_age: unstable_duration_opt("max-crate-age")?, - max_index_age: unstable_duration_opt("max-index-age")?, - max_git_co_age: unstable_duration_opt("max-git-co-age")?, - max_git_db_age: unstable_duration_opt("max-git-db-age")?, - max_src_size: unstable_size_opt("max-src-size")?, - max_crate_size: unstable_size_opt("max-crate-size")?, - max_git_size: unstable_size_opt("max-git-size")?, - max_download_size: unstable_size_opt("max-download-size")?, - max_target_age: unimplemented_duration_opt("max-target-age")?, - max_target_size: unimplemented_size_opt("max-target-size")?, - }; - let max_download_age = unstable_duration_opt("max-download-age")?; - gc_opts.update_for_auto_gc(config, &gc, max_download_age)?; + let ws = args.workspace(config)?; + + if args.is_present_with_zero_values("package") { + print_available_packages(&ws)?; + } let opts = CleanOptions { config, @@ -245,8 +151,48 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { profile_specified: args.contains_id("profile") || args.flag("release"), doc: args.flag("doc"), dry_run: args.dry_run(), - gc_opts, }; - ops::clean(ws, &opts)?; + ops::clean(&ws, &opts)?; + Ok(()) +} + +fn gc(config: &Config, args: &ArgMatches) -> CliResult { + config.cli_unstable().fail_if_stable_command( + config, + "clean gc", + 12633, + "gc", + config.cli_unstable().gc, + )?; + + let size_opt = |opt| -> Option { args.get_one::(opt).copied() }; + let duration_opt = |opt| -> Option { args.get_one::(opt).copied() }; + let mut gc_opts = GcOpts { + max_src_age: duration_opt("max-src-age"), + max_crate_age: duration_opt("max-crate-age"), + max_index_age: duration_opt("max-index-age"), + max_git_co_age: duration_opt("max-git-co-age"), + max_git_db_age: duration_opt("max-git-db-age"), + max_src_size: size_opt("max-src-size"), + max_crate_size: size_opt("max-crate-size"), + max_git_size: size_opt("max-git-size"), + max_download_size: size_opt("max-download-size"), + }; + if let Some(age) = duration_opt("max-download-age") { + gc_opts.set_max_download_age(age); + } + // If the user sets any options, then only perform the options requested. + // If no options are set, do the default behavior. + if !gc_opts.is_download_cache_opt_set() { + gc_opts.update_for_auto_gc(config)?; + } + + let _lock = config.acquire_package_cache_lock(CacheLockMode::MutateExclusive)?; + let mut cache_track = GlobalCacheTracker::new(&config)?; + let mut gc = Gc::new(config, &mut cache_track)?; + let mut clean_ctx = CleanContext::new(config); + clean_ctx.dry_run = args.dry_run(); + gc.gc(&mut clean_ctx, &gc_opts)?; + clean_ctx.display_summary()?; Ok(()) } diff --git a/src/bin/cargo/commands/config.rs b/src/bin/cargo/commands/config.rs index 84c5e9209b8..feea9ed2876 100644 --- a/src/bin/cargo/commands/config.rs +++ b/src/bin/cargo/commands/config.rs @@ -31,9 +31,13 @@ pub fn cli() -> Command { } pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { - config - .cli_unstable() - .fail_if_stable_command(config, "config", 9301)?; + config.cli_unstable().fail_if_stable_command( + config, + "config", + 9301, + "unstable-options", + config.cli_unstable().unstable_options, + )?; match args.subcommand() { Some(("get", args)) => { let opts = cargo_config::GetOptions { diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 6259074dc38..2ce4a57c072 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -1157,8 +1157,10 @@ impl CliUnstable { config: &Config, command: &str, issue: u32, + z_name: &str, + enabled: bool, ) -> CargoResult<()> { - if self.unstable_options { + if enabled { return Ok(()); } let see = format!( @@ -1168,10 +1170,9 @@ impl CliUnstable { ); if config.nightly_features_allowed { bail!( - "the `cargo {}` command is unstable, pass `-Z unstable-options` to enable it\n\ - {}", - command, - see + "the `cargo {command}` command is unstable, pass `-Z {z_name}` \ + to enable it\n\ + {see}", ); } else { bail!( diff --git a/src/cargo/core/gc.rs b/src/cargo/core/gc.rs index 2a8f4267a8f..565078ff006 100644 --- a/src/cargo/core/gc.rs +++ b/src/cargo/core/gc.rs @@ -23,8 +23,7 @@ use crate::core::global_cache_tracker::{self, GlobalCacheTracker}; use crate::ops::CleanContext; use crate::util::cache_lock::{CacheLock, CacheLockMode}; use crate::{CargoResult, Config}; -use anyhow::format_err; -use anyhow::{bail, Context}; +use anyhow::{format_err, Context}; use serde::Deserialize; use std::time::Duration; @@ -136,11 +135,6 @@ pub struct GcOpts { pub max_git_size: Option, /// The `--max-download-size` CLI option. pub max_download_size: Option, - - /// The `--max-target-age` CLI option (UNIMPLEMENTED). - pub max_target_age: Option, - /// The `--max-target-size` CLI option (UNIMPLEMENTED). - pub max_target_size: Option, } impl GcOpts { @@ -165,129 +159,71 @@ impl GcOpts { || self.max_download_size.is_some() } - /// Returns whether any target directory cleaning options are set. - pub fn is_target_opt_set(&self) -> bool { - self.max_target_size.is_some() || self.max_target_age.is_some() + /// Updates the `GcOpts` to incorporate the specified max download age. + /// + /// "Download" means any cached data that can be re-downloaded. + pub fn set_max_download_age(&mut self, max_download_age: Duration) { + self.max_src_age = Some(maybe_newer_span(max_download_age, self.max_src_age)); + self.max_crate_age = Some(maybe_newer_span(max_download_age, self.max_crate_age)); + self.max_index_age = Some(maybe_newer_span(max_download_age, self.max_index_age)); + self.max_git_co_age = Some(maybe_newer_span(max_download_age, self.max_git_co_age)); + self.max_git_db_age = Some(maybe_newer_span(max_download_age, self.max_git_db_age)); } /// Updates the configuration of this [`GcOpts`] to incorporate the - /// settings from config and the given CLI options. - /// - /// * `kinds` is a list of [`AutoGcKind`] that is being requested to - /// perform. This corresponds to the `cargo clean --gc` flag. If empty, - /// no config options are incorporated. - /// * `max_download_age` is the `--max-download-age` CLI option which - /// requires special handling since it implicitly overlaps two options. - /// It will use the newer value of either this or the explicit value. - /// - /// The `kinds` list is used in a few different ways: - /// - /// * If empty, uses only the options the user specified on the - /// command-line, like `cargo clean --max-crate-size=…`. - /// * If the user specified a `cargo clean --gc` option, then the `kinds` - /// list is filled in with whatever `--gc` option the user picked, and - /// then this function *merges* the settings between the requested - /// `--gc` option and any options that were explicitly specified. - /// * [`AutoGcKind::All`] is used in `cargo clean` when no options are - /// specified. - pub fn update_for_auto_gc( - &mut self, - config: &Config, - kinds: &[AutoGcKind], - max_download_age: Option, - ) -> CargoResult<()> { + /// settings from config. + pub fn update_for_auto_gc(&mut self, config: &Config) -> CargoResult<()> { let auto_config = config .get::>("gc.auto")? .unwrap_or_default(); - self.update_for_auto_gc_config(&auto_config, kinds, max_download_age) + self.update_for_auto_gc_config(&auto_config) } - fn update_for_auto_gc_config( - &mut self, - auto_config: &AutoConfig, - kinds: &[AutoGcKind], - max_download_age: Option, - ) -> CargoResult<()> { - for kind in kinds { - if matches!(kind, AutoGcKind::All | AutoGcKind::Download) { - self.max_src_age = newer_time_span_for_config( - self.max_src_age, - "gc.auto.max-src-age", - auto_config - .max_src_age - .as_deref() - .unwrap_or(DEFAULT_MAX_AGE_EXTRACTED), - )?; - self.max_crate_age = newer_time_span_for_config( - self.max_crate_age, - "gc.auto.max-crate-age", - auto_config - .max_crate_age - .as_deref() - .unwrap_or(DEFAULT_MAX_AGE_DOWNLOADED), - )?; - self.max_index_age = newer_time_span_for_config( - self.max_index_age, - "gc.auto.max-index-age", - auto_config - .max_index_age - .as_deref() - .unwrap_or(DEFAULT_MAX_AGE_DOWNLOADED), - )?; - self.max_git_co_age = newer_time_span_for_config( - self.max_git_co_age, - "gc.auto.max-git-co-age", - auto_config - .max_git_co_age - .as_deref() - .unwrap_or(DEFAULT_MAX_AGE_EXTRACTED), - )?; - self.max_git_db_age = newer_time_span_for_config( - self.max_git_db_age, - "gc.auto.max-git-db-age", - auto_config - .max_git_db_age - .as_deref() - .unwrap_or(DEFAULT_MAX_AGE_DOWNLOADED), - )?; - } - if matches!(kind, AutoGcKind::Target) { - bail!("target is unimplemented"); - } - } - if let Some(max_download_age) = max_download_age { - self.max_src_age = Some(maybe_newer_span(max_download_age, self.max_src_age)); - self.max_crate_age = Some(maybe_newer_span(max_download_age, self.max_crate_age)); - self.max_index_age = Some(maybe_newer_span(max_download_age, self.max_index_age)); - self.max_git_co_age = Some(maybe_newer_span(max_download_age, self.max_git_co_age)); - self.max_git_db_age = Some(maybe_newer_span(max_download_age, self.max_git_db_age)); - } + fn update_for_auto_gc_config(&mut self, auto_config: &AutoConfig) -> CargoResult<()> { + self.max_src_age = newer_time_span_for_config( + self.max_src_age, + "gc.auto.max-src-age", + auto_config + .max_src_age + .as_deref() + .unwrap_or(DEFAULT_MAX_AGE_EXTRACTED), + )?; + self.max_crate_age = newer_time_span_for_config( + self.max_crate_age, + "gc.auto.max-crate-age", + auto_config + .max_crate_age + .as_deref() + .unwrap_or(DEFAULT_MAX_AGE_DOWNLOADED), + )?; + self.max_index_age = newer_time_span_for_config( + self.max_index_age, + "gc.auto.max-index-age", + auto_config + .max_index_age + .as_deref() + .unwrap_or(DEFAULT_MAX_AGE_DOWNLOADED), + )?; + self.max_git_co_age = newer_time_span_for_config( + self.max_git_co_age, + "gc.auto.max-git-co-age", + auto_config + .max_git_co_age + .as_deref() + .unwrap_or(DEFAULT_MAX_AGE_EXTRACTED), + )?; + self.max_git_db_age = newer_time_span_for_config( + self.max_git_db_age, + "gc.auto.max-git-db-age", + auto_config + .max_git_db_age + .as_deref() + .unwrap_or(DEFAULT_MAX_AGE_DOWNLOADED), + )?; Ok(()) } } -/// The kind of automatic garbage collection to perform. -/// -/// "Automatic" is the kind of gc performed automatically by Cargo in any -/// command that is already doing a bunch of work. See [`auto_gc`] for more. -#[derive(Clone, Debug)] -pub enum AutoGcKind { - /// Automatically clean up the downloaded files *and* the target directory. - /// - /// This is the mode used by default. - All, - /// Automatically clean only downloaded files. - /// - /// This corresponds to `cargo clean --gc=download`. - Download, - /// Automatically clean only the target directory. - /// - /// THIS IS NOT IMPLEMENTED. - /// - /// This corresponds to `cargo clean --gc=target`. - Target, -} - /// Garbage collector. /// /// See the module docs at [`crate::core::gc`] for more information on GC. @@ -342,7 +278,7 @@ impl<'a, 'config> Gc<'a, 'config> { return Ok(()); } let mut gc_opts = GcOpts::default(); - gc_opts.update_for_auto_gc_config(&auto_config, &[AutoGcKind::All], None)?; + gc_opts.update_for_auto_gc_config(&auto_config)?; self.gc(clean_ctx, &gc_opts)?; if !clean_ctx.dry_run { self.global_cache_tracker.set_last_auto_gc()?; diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 35c7063f494..923b2decdc4 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -1,10 +1,7 @@ use crate::core::compiler::{CompileKind, CompileMode, Layout, RustcTargetData}; -use crate::core::gc::{AutoGcKind, Gc, GcOpts}; -use crate::core::global_cache_tracker::GlobalCacheTracker; use crate::core::profiles::Profiles; use crate::core::{PackageIdSpec, TargetKind, Workspace}; use crate::ops; -use crate::util::cache_lock::CacheLockMode; use crate::util::edit_distance; use crate::util::errors::CargoResult; use crate::util::interning::InternedString; @@ -28,7 +25,6 @@ pub struct CleanOptions<'cfg> { pub doc: bool, /// If set, doesn't delete anything. pub dry_run: bool, - pub gc_opts: GcOpts, } pub struct CleanContext<'cfg> { @@ -41,76 +37,45 @@ pub struct CleanContext<'cfg> { } /// Cleans various caches. -pub fn clean(ws: CargoResult>, opts: &CleanOptions<'_>) -> CargoResult<()> { +pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { + let mut target_dir = ws.target_dir(); let config = opts.config; let mut ctx = CleanContext::new(config); ctx.dry_run = opts.dry_run; - let any_download_cache_opts = opts.gc_opts.is_download_cache_opt_set(); - - // The following options need a workspace. - let any_ws_opts = !opts.spec.is_empty() - || !opts.targets.is_empty() - || opts.profile_specified - || opts.doc - || opts.gc_opts.is_target_opt_set(); - - // When no options are specified, do the default action. - let no_opts_specified = !any_download_cache_opts && !any_ws_opts; - - if any_ws_opts || no_opts_specified { - let ws = ws?; - let mut target_dir = ws.target_dir(); - - if opts.doc { - if !opts.spec.is_empty() { - // FIXME: https://github.com/rust-lang/cargo/issues/8790 - // This should support the ability to clean specific packages - // within the doc directory. It's a little tricky since it - // needs to find all documentable targets, but also consider - // the fact that target names might overlap with dependency - // names and such. - bail!("--doc cannot be used with -p"); - } - // If the doc option is set, we just want to delete the doc directory. - target_dir = target_dir.join("doc"); - ctx.remove_paths(&[target_dir.into_path_unlocked()])?; - } else { - let profiles = Profiles::new(&ws, opts.requested_profile)?; - - if opts.profile_specified { - // After parsing profiles we know the dir-name of the profile, if a profile - // was passed from the command line. If so, delete only the directory of - // that profile. - let dir_name = profiles.get_dir_name(); - target_dir = target_dir.join(dir_name); - } - - // If we have a spec, then we need to delete some packages, otherwise, just - // remove the whole target directory and be done with it! - // - // 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() { - ctx.remove_paths(&[target_dir.into_path_unlocked()])?; - } else { - clean_specs(&mut ctx, &ws, &profiles, &opts.targets, &opts.spec)?; - } + if opts.doc { + if !opts.spec.is_empty() { + // FIXME: https://github.com/rust-lang/cargo/issues/8790 + // This should support the ability to clean specific packages + // within the doc directory. It's a little tricky since it + // needs to find all documentable targets, but also consider + // the fact that target names might overlap with dependency + // names and such. + bail!("--doc cannot be used with -p"); + } + // If the doc option is set, we just want to delete the doc directory. + target_dir = target_dir.join("doc"); + ctx.remove_paths(&[target_dir.into_path_unlocked()])?; + } else { + let profiles = Profiles::new(&ws, opts.requested_profile)?; + + if opts.profile_specified { + // After parsing profiles we know the dir-name of the profile, if a profile + // was passed from the command line. If so, delete only the directory of + // that profile. + let dir_name = profiles.get_dir_name(); + target_dir = target_dir.join(dir_name); } - } - if config.cli_unstable().gc { - let _lock = config.acquire_package_cache_lock(CacheLockMode::MutateExclusive)?; - let mut cache_track = GlobalCacheTracker::new(&config)?; - let mut gc = Gc::new(config, &mut cache_track)?; - if no_opts_specified { - // This is the behavior for `cargo clean` without *any* options. - // It uses the defaults from config to determine what is cleaned. - let mut gc_opts = opts.gc_opts.clone(); - gc_opts.update_for_auto_gc(config, &[AutoGcKind::All], None)?; - gc.gc(&mut ctx, &gc_opts)?; + // If we have a spec, then we need to delete some packages, otherwise, just + // remove the whole target directory and be done with it! + // + // 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() { + ctx.remove_paths(&[target_dir.into_path_unlocked()])?; } else { - gc.gc(&mut ctx, &opts.gc_opts)?; + clean_specs(&mut ctx, &ws, &profiles, &opts.targets, &opts.spec)?; } } @@ -424,7 +389,7 @@ impl<'cfg> CleanContext<'cfg> { Ok(()) } - fn display_summary(&self) -> CargoResult<()> { + pub fn display_summary(&self) -> CargoResult<()> { let status = if self.dry_run { "Summary" } else { "Removed" }; let byte_count = if self.total_bytes_removed == 0 { String::new() diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 7cdec3d069e..b855ec1c3df 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1432,10 +1432,9 @@ max-git-db-age = "3 months" ### Manual garbage collection with `cargo clean` -Manual deletion can be done with the `cargo clean` command. +Manual deletion can be done with the `cargo clean gc` command. Deletion of cache contents can be performed by passing one of the cache options: -- `--gc` --- Performs the same garbage collection that is performed by the once-a-day automatic deletion. - `--max-src-age=DURATION` --- Deletes source cache files that have not been used since the given age. - `--max-crate-age=DURATION` --- Deletes crate cache files that have not been used since the given age. - `--max-index-age=DURATION` --- Deletes registry indexes that have not been used since then given age (including their `.crate` and `src` files). @@ -1452,8 +1451,9 @@ A DURATION is specified in the form "N seconds/minutes/days/weeks/months" where A SIZE is specified in the form "N *suffix*" where *suffix* is B, kB, MB, GB, kiB, MiB, or GiB, and N is an integer or floating point number. If no suffix is specified, the number is the number of bytes. ```sh -cargo clean --max-download-age=1week -cargo clean --max-git-size=0 --max-download-size=100MB +cargo clean gc +cargo clean gc --max-download-age=1week +cargo clean gc --max-git-size=0 --max-download-size=100MB ``` # Stabilized and removed features diff --git a/tests/testsuite/global_cache_tracker.rs b/tests/testsuite/global_cache_tracker.rs index fe241ec1ba8..27216d96c72 100644 --- a/tests/testsuite/global_cache_tracker.rs +++ b/tests/testsuite/global_cache_tracker.rs @@ -173,44 +173,18 @@ fn auto_gc_gated() { } #[cargo_test] -fn cache_clean_options_gated() { - // Checks that all cache clean options require -Zgc. - let p = project().build(); - for opt in [ - "--gc", - "--max-src-age=0 day", - "--max-index-age=0 day", - "--max-git-co-age=0 day", - "--max-git-db-age=0 day", - "--max-download-age=0 day", - "--max-src-size=0", - "--max-crate-size=0", - "--max-download-size=0", - ] { - let trimmed_opt = opt.trim_start_matches('-').split('=').next().unwrap(); - p.cargo("clean") - .arg(opt) - .with_status(101) - .with_stderr(&format!( - "\ -error: the `{trimmed_opt}` flag is unstable, [..] +fn clean_gc_gated() { + cargo_process("clean gc") + .with_status(101) + .with_stderr( + "\ +error: the `cargo clean gc` command is unstable, and only available on the \ +nightly channel of Cargo, but this is the `stable` channel See [..] -See [..] for more information about the `{trimmed_opt}` flag. -" - )) - .run(); - } - - for opt in ["--max-target-age=0 day", "--max-target-size=0"] { - let trimmed_opt = opt.split('=').next().unwrap(); - p.cargo("clean") - .arg(opt) - .with_status(101) - .with_stderr(&format!( - "error: option {trimmed_opt} is not yet implemented" - )) - .run(); - } +See [..] +", + ) + .run(); } #[cargo_test] @@ -722,7 +696,7 @@ fn both_git_and_http_index_cleans() { // Running in the future without these indexes should delete them. p.change_file("Cargo.toml", &basic_manifest("foo", "0.2.0")); - p.cargo("clean --gc -Zgc") + p.cargo("clean gc -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .run(); let lock = config @@ -763,7 +737,7 @@ fn clean_gc_dry_run() { .map(|p| p.to_str().unwrap()) .join("\n"); - p.cargo("clean --gc --dry-run -v -Zgc") + p.cargo("clean gc --dry-run -v -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stdout_unordered(&expected_files) .with_stderr( @@ -773,7 +747,7 @@ fn clean_gc_dry_run() { .run(); // Again, make sure the information is still tracked. - p.cargo("clean --gc --dry-run -v -Zgc") + p.cargo("clean gc --dry-run -v -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stdout_unordered(&expected_files) .with_stderr( @@ -785,14 +759,14 @@ fn clean_gc_dry_run() { #[cargo_test] fn clean_default_gc() { - // `clean` without options should also gc + // `clean gc` without options should also gc let p = basic_foo_bar_project(); // Populate the last-use data. p.cargo("fetch -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) .run(); - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr_unordered( "\ @@ -935,7 +909,7 @@ fn max_size() { format!("{files} files") }; write!(stderr, "[REMOVED] {files_display}{total_display}").unwrap(); - cargo_process(&format!("clean -Zgc -v --max-crate-size={clean_size}")) + cargo_process(&format!("clean gc -Zgc -v --max-crate-size={clean_size}")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr_unordered(&stderr) .run(); @@ -958,7 +932,7 @@ fn max_size() { format!(", {bytes}B total") }; write!(stderr, "[REMOVED] {files_display}{total_display}").unwrap(); - cargo_process(&format!("clean -Zgc -v --max-src-size={clean_size}")) + cargo_process(&format!("clean gc -Zgc -v --max-src-size={clean_size}")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr_unordered(&stderr) .run(); @@ -993,7 +967,7 @@ fn max_size_untracked_crate() { std::fs::write(cache.join(name), "x".repeat(size as usize)).unwrap() } // This should scan the directory and populate the db with the size information. - cargo_process("clean -Zgc -v --max-crate-size=100000") + cargo_process("clean gc -Zgc -v --max-crate-size=100000") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr("[REMOVED] 0 files") .run(); @@ -1069,7 +1043,7 @@ fn max_size_untracked_src_from_use() { drop(lock); // Fix the size. - p.cargo("clean -v --max-src-size=10000 -Zgc") + p.cargo("clean gc -v --max-src-size=10000 -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr("[REMOVED] 0 files") .run(); @@ -1084,7 +1058,7 @@ fn max_size_untracked_src_from_clean() { let (config, p) = max_size_untracked_prepare(); // Clean should scan the src and update the db. - p.cargo("clean -v --max-src-size=10000 -Zgc") + p.cargo("clean gc -v --max-src-size=10000 -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr("[REMOVED] 0 files") .run(); @@ -1138,7 +1112,7 @@ fn max_download_size() { format!(", {bytes}B total") }; write!(stderr, "[REMOVED] {files_display}{total_display}",).unwrap(); - cargo_process(&format!("clean -Zgc -v --max-download-size={max_size}")) + cargo_process(&format!("clean gc -Zgc -v --max-download-size={max_size}")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr_unordered(&stderr) .run(); @@ -1233,7 +1207,7 @@ fn package_cache_lock_during_build() { // Cleaning while a command is running should block. let mut clean_cmd = p_foo2 - .cargo("clean --max-download-size=0 -Zgc") + .cargo("clean gc --max-download-size=0 -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .build_command(); clean_cmd.stderr(Stdio::piped()); @@ -1316,7 +1290,7 @@ fn delete_index_also_deletes_crates() { assert_eq!(get_registry_names("src"), ["bar-1.0.0"]); assert_eq!(get_registry_names("cache"), ["bar-1.0.0.crate"]); - p.cargo("clean") + p.cargo("clean gc") .arg("--max-index-age=0 days") .arg("-Zgc") .masquerade_as_nightly_cargo(&["gc"]) @@ -1377,7 +1351,7 @@ fn clean_syncs_missing_files() { } // Clean should update the db. - p.cargo("clean -v --max-download-size=1GB -Zgc") + p.cargo("clean gc -v --max-download-size=1GB -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr("[REMOVED] 0 files") .run(); @@ -1436,7 +1410,7 @@ fn can_handle_future_schema() -> anyhow::Result<()> { conn.pragma_update(None, "user_version", &(user_version + 1))?; drop(conn); // Verify it doesn't blow up. - p.cargo("clean --max-download-size=0 -Zgc") + p.cargo("clean gc --max-download-size=0 -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr("[REMOVED] 4 files, [..] total") .run(); @@ -1495,7 +1469,7 @@ fn clean_max_git_age() { assert_eq!(co_names.len(), 2); // Delete the first checkout - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .arg("--max-git-co-age=3 days") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( @@ -1512,7 +1486,7 @@ fn clean_max_git_age() { assert_eq!(co_names.len(), 1); // delete the second checkout - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .arg("--max-git-co-age=0 days") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( @@ -1529,7 +1503,7 @@ fn clean_max_git_age() { assert_eq!(co_names.len(), 0); // delete the db - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .arg("--max-git-db-age=1 days") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( @@ -1586,7 +1560,7 @@ fn clean_max_src_crate_age() { ); // Delete the old src. - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .arg("--max-src-age=3 days") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( @@ -1598,7 +1572,7 @@ fn clean_max_src_crate_age() { .run(); // delete the second src - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .arg("--max-src-age=0 days") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( @@ -1610,7 +1584,7 @@ fn clean_max_src_crate_age() { .run(); // delete the old crate - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .arg("--max-crate-age=3 days") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( @@ -1622,7 +1596,7 @@ fn clean_max_src_crate_age() { .run(); // delete the seecond crate - p.cargo("clean -v -Zgc") + p.cargo("clean gc -v -Zgc") .arg("--max-crate-age=0 days") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( @@ -1634,30 +1608,6 @@ fn clean_max_src_crate_age() { .run(); } -#[cargo_test] -fn clean_doc_with_cache() { - // clean --doc with other cache flags should do both. - let p = basic_foo_bar_project(); - p.cargo("doc -Zgc") - .masquerade_as_nightly_cargo(&["gc"]) - .env("__CARGO_TEST_LAST_USE_NOW", months_ago_unix(4)) - .run(); - assert_eq!(get_registry_names("src"), ["bar-1.0.0"]); - assert_eq!(get_registry_names("cache"), ["bar-1.0.0.crate"]); - assert!(p.build_dir().join("doc").exists()); - p.cargo("clean --doc --max-download-size=0 -v -Zgc") - .masquerade_as_nightly_cargo(&["gc"]) - .with_stderr_unordered( - "\ -[REMOVING] [ROOT]/foo/target/doc -[REMOVING] [ROOT]/home/.cargo/registry/src/[..]/bar-1.0.0 -[REMOVING] [ROOT]/home/.cargo/registry/cache/[..]/bar-1.0.0.crate -[REMOVED] [..] -", - ) - .run(); -} - #[cargo_test] fn clean_max_git_size() { // clean --max-git-size @@ -1727,7 +1677,7 @@ fn clean_max_git_size() { let threshold = db_size + second_co_size; - p.cargo(&format!("clean --max-git-size={threshold} -Zgc -v")) + p.cargo(&format!("clean gc --max-git-size={threshold} -Zgc -v")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr(&format!( "\ @@ -1738,7 +1688,7 @@ fn clean_max_git_size() { .run(); // And then try cleaning everything. - p.cargo("clean --max-git-size=0 -Zgc -v") + p.cargo("clean gc --max-git-size=0 -Zgc -v") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr_unordered(&format!( "\ @@ -1773,7 +1723,7 @@ fn clean_max_git_size_untracked() { // The db_name of "example" depends on the sorting order of the names ("e" // should be after "c"), so that the db comes after the checkouts. setup_fake_git_sizes("example", 5000, &[1000, 2000]); - cargo_process(&format!("clean -Zgc -v --max-git-size=7000")) + cargo_process(&format!("clean gc -Zgc -v --max-git-size=7000")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( "\ @@ -1782,7 +1732,7 @@ fn clean_max_git_size_untracked() { ", ) .run(); - cargo_process(&format!("clean -Zgc -v --max-git-size=5000")) + cargo_process(&format!("clean gc -Zgc -v --max-git-size=5000")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( "\ @@ -1791,7 +1741,7 @@ fn clean_max_git_size_untracked() { ", ) .run(); - cargo_process(&format!("clean -Zgc -v --max-git-size=0")) + cargo_process(&format!("clean gc -Zgc -v --max-git-size=0")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( "\ @@ -1812,7 +1762,7 @@ fn clean_max_git_size_deletes_co_from_db() { setup_fake_git_sizes("abc", 5000, &[1000, 2000]); // This deletes everything because it tries to delete the db, which then // deletes all checkouts. - cargo_process(&format!("clean -Zgc -v --max-git-size=3000")) + cargo_process(&format!("clean gc -Zgc -v --max-git-size=3000")) .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( "\ @@ -1833,7 +1783,7 @@ fn handles_missing_index() { .masquerade_as_nightly_cargo(&["gc"]) .run(); paths::home().join(".cargo/registry/index").rm_rf(); - cargo_process("clean -v --max-download-size=0 -Zgc") + cargo_process("clean gc -v --max-download-size=0 -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr_unordered( "\ @@ -1873,7 +1823,7 @@ fn handles_missing_git_db() { .masquerade_as_nightly_cargo(&["gc"]) .run(); paths::home().join(".cargo/git/db").rm_rf(); - cargo_process("clean -v --max-git-size=0 -Zgc") + cargo_process("clean gc -v --max-git-size=0 -Zgc") .masquerade_as_nightly_cargo(&["gc"]) .with_stderr( "\