From ab1641964d842ddf900cf0193b187904ee122c68 Mon Sep 17 00:00:00 2001 From: Jeff Dickey <216188+jdx@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:25:30 -0600 Subject: [PATCH] uninstall: tweak behavior Fixes #1115 --- src/cli/uninstall.rs | 165 +++++++++++++++++++++++------------- src/file.rs | 16 +++- src/plugins/mod.rs | 6 ++ src/runtime_symlinks.rs | 4 +- src/toolset/tool_version.rs | 30 ++++++- 5 files changed, 158 insertions(+), 63 deletions(-) diff --git a/src/cli/uninstall.rs b/src/cli/uninstall.rs index 9f1ebc33e2..aa958ceab5 100644 --- a/src/cli/uninstall.rs +++ b/src/cli/uninstall.rs @@ -1,9 +1,13 @@ use console::style; use eyre::{Result, WrapErr}; +use itertools::Itertools; +use rayon::prelude::*; +use std::sync::Arc; use crate::cli::args::tool::{ToolArg, ToolArgParser}; use crate::config::Config; use crate::output::Output; +use crate::plugins::Plugin; use crate::toolset::{ToolVersion, ToolVersionRequest, ToolsetBuilder}; use crate::ui::multi_progress_report::MultiProgressReport; use crate::{runtime_symlinks, shims}; @@ -13,7 +17,7 @@ use crate::{runtime_symlinks, shims}; #[clap(verbatim_doc_comment, alias = "remove", alias = "rm", after_long_help = AFTER_LONG_HELP)] pub struct Uninstall { /// Tool(s) to remove - #[clap(required = true, value_name = "TOOL@VERSION", value_parser = ToolArgParser)] + #[clap(value_name = "TOOL@VERSION", value_parser = ToolArgParser, required_unless_present = "all")] tool: Vec, /// Delete all installed versions @@ -28,13 +32,14 @@ pub struct Uninstall { impl Uninstall { pub fn run(self, mut config: Config, _out: &mut Output) -> Result<()> { let runtimes = ToolArg::double_tool_condition(&self.tool); - - let mut tool_versions = vec![]; - if self.all { - for runtime in runtimes { - let tool = config.get_or_create_plugin(&runtime.plugin); - let query = runtime.tvr.map(|tvr| tvr.version()).unwrap_or_default(); - let tvs = tool + let mut tool_versions = runtimes + .into_iter() + .map(|a| (config.get_or_create_plugin(&a.plugin), a)) + .collect_vec() + .into_par_iter() + .map(|(tool, a)| { + let query = a.tvr.as_ref().map(|tvr| tvr.version()).unwrap_or_default(); + let mut tvs = tool .list_installed_versions()? .into_iter() .filter(|v| v.starts_with(&query)) @@ -44,64 +49,108 @@ impl Uninstall { (tool.clone(), tv) }) .collect::>(); + if let Some(tvr) = &a.tvr { + tvs.push(( + tool.clone(), + tvr.resolve(&config, tool.clone(), Default::default(), true)?, + )); + } if tvs.is_empty() { warn!("no versions found for {}", style(&tool).cyan().for_stderr()); } - tool_versions.extend(tvs); - } - } else { - tool_versions = runtimes - .into_iter() - .map(|a| { - let tool = config.get_or_create_plugin(&a.plugin); - let tvs = match a.tvr { - Some(tvr) => { - vec![tvr.resolve(&config, tool.clone(), Default::default(), false)?] - } - None => { - let ts = ToolsetBuilder::new().build(&mut config)?; - match ts.versions.get(&a.plugin) { - Some(tvl) => tvl.versions.clone(), - None => bail!( - "no versions found for {}", - style(&tool).cyan().for_stderr() - ), - } - } - }; - Ok(tvs - .into_iter() - .map(|tv| (tool.clone(), tv)) - .collect::>()) - }) - .collect::>>()? - .into_iter() - .flatten() - .collect::>(); - } - - let mpr = MultiProgressReport::new(&config.settings); - for (plugin, tv) in tool_versions { - if !plugin.is_version_installed(&tv) { - warn!("{} is not installed", style(&tv).cyan().for_stderr()); - continue; - } + Ok(tvs) + }) + .collect::>>()? + .into_iter() + .flatten() + .unique() + .sorted() + .collect::>(); - let mut pr = mpr.add(); - plugin.decorate_progress_bar(&mut pr, Some(&tv)); - if let Err(err) = plugin.uninstall_version(&config, &tv, &pr, self.dry_run) { - pr.error(err.to_string()); - return Err(eyre!(err).wrap_err(format!("failed to uninstall {tv}"))); - } - pr.finish_with_message("uninstalled"); - } + if self.tool.is_empty() && self.all {} - let ts = ToolsetBuilder::new().build(&mut config)?; - shims::reshim(&config, &ts).wrap_err("failed to reshim")?; - runtime_symlinks::rebuild(&config)?; + // let mpr = MultiProgressReport::new(&config.settings); + // for (plugin, tv) in tool_versions { + // if !plugin.is_version_installed(&tv) { + // warn!("{} is not installed", style(&tv).cyan().for_stderr()); + // continue; + // } + // + // let mut pr = mpr.add(); + // plugin.decorate_progress_bar(&mut pr, Some(&tv)); + // if let Err(err) = plugin.uninstall_version(&config, &tv, &pr, self.dry_run) { + // pr.error(err.to_string()); + // return Err(eyre!(err).wrap_err(format!("failed to uninstall {tv}"))); + // } + // pr.finish_with_message("uninstalled"); + // } + // + // let ts = ToolsetBuilder::new().build(&mut config)?; + // shims::reshim(&config, &ts).wrap_err("failed to reshim")?; + // runtime_symlinks::rebuild(&config)?; Ok(()) } + + fn get_all_tool_versions( + &self, + config: &mut Config, + ) -> Result, ToolVersion)>> { + let ts = ToolsetBuilder::new().build(config)?; + let tool_versions = ts + .versions + .into_iter() + .map(|(plugin, tvl)| { + tvl.versions + .into_iter() + .map(|tv| (plugin.clone(), tv)) + .collect::>() + }) + .flatten() + .collect::>(); + Ok(tool_versions) + } + fn get_requested_tool_versions( + &self, + config: &mut Config, + ) -> Result, ToolVersion)>> { + let runtimes = ToolArg::double_tool_condition(&self.tool); + let tool_versions = runtimes + .into_iter() + .map(|a| (config.get_or_create_plugin(&a.plugin), a)) + .collect_vec() + .into_par_iter() + .map(|(tool, a)| { + let query = a.tvr.as_ref().map(|tvr| tvr.version()).unwrap_or_default(); + let mut tvs = tool + .list_installed_versions()? + .into_iter() + .filter(|v| v.starts_with(&query)) + .map(|v| { + let tvr = ToolVersionRequest::new(tool.name().into(), &v); + let tv = ToolVersion::new(tool.clone(), tvr, Default::default(), v); + (tool.clone(), tv) + }) + .collect::>(); + if let Some(tvr) = &a.tvr { + tvs.push(( + tool.clone(), + tvr.resolve(&config, tool.clone(), Default::default(), true)?, + )); + } + if tvs.is_empty() { + warn!("no versions found for {}", style(&tool).cyan().for_stderr()); + } + Ok(tvs) + }) + .collect::>>()? + .into_iter() + .flatten() + .unique() + .sorted() + .collect::>(); + Ok(tool_versions) + } } static AFTER_LONG_HELP: &str = color_print::cstr!( diff --git a/src/file.rs b/src/file.rs index fa114ebe36..cb76ea4423 100644 --- a/src/file.rs +++ b/src/file.rs @@ -36,8 +36,14 @@ pub fn remove_file>(path: P) -> Result<()> { pub fn remove_dir>(path: P) -> Result<()> { let path = path.as_ref(); - trace!("rmdir {}", display_path(path)); - fs::remove_dir(path).wrap_err_with(|| format!("failed rmdir: {}", display_path(path))) + (|| -> Result<()> { + if path.exists() && is_empty_dir(path)? { + trace!("rmdir {}", display_path(path)); + fs::remove_dir(path)?; + } + Ok(()) + })() + .wrap_err_with(|| format!("failed to remove_dir: {}", display_path(path))) } pub fn remove_all_with_warning>(path: P) -> Result<()> { @@ -207,6 +213,12 @@ pub fn make_executable(path: &Path) -> Result<()> { Ok(()) } +fn is_empty_dir(path: &Path) -> Result { + path.read_dir() + .map(|mut i| i.next().is_none()) + .wrap_err_with(|| format!("failed to read_dir: {}", display_path(path))) +} + pub struct FindUp { current_dir: PathBuf, current_dir_filenames: Vec, diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 4873e4e37d..4f48ba482c 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, HashMap}; use std::fmt::{Debug, Display}; use std::fs::File; +use std::hash::Hash; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -409,6 +410,11 @@ impl PartialEq for dyn Plugin { self.get_type() == other.get_type() && self.name() == other.name() } } +impl Hash for dyn Plugin { + fn hash(&self, state: &mut H) { + self.name().hash(state) + } +} impl PartialOrd for dyn Plugin { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) diff --git a/src/runtime_symlinks.rs b/src/runtime_symlinks.rs index af4957dd11..dfd1684bcd 100644 --- a/src/runtime_symlinks.rs +++ b/src/runtime_symlinks.rs @@ -30,8 +30,8 @@ pub fn rebuild(config: &Config) -> Result<()> { make_symlink(&to, &from)?; } remove_missing_symlinks(plugin.clone())?; - // attempt to remove the installs dir (will fail if not empty) - let _ = file::remove_dir(&installs_dir); + // remove install dir if empty + file::remove_dir(&installs_dir)?; } Ok(()) } diff --git a/src/toolset/tool_version.rs b/src/toolset/tool_version.rs index ee51a7bfc0..ea483030c0 100644 --- a/src/toolset/tool_version.rs +++ b/src/toolset/tool_version.rs @@ -1,5 +1,7 @@ +use std::cmp::Ordering; use std::fmt::{Display, Formatter}; use std::fs; +use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::sync::Arc; @@ -13,7 +15,7 @@ use crate::plugins::{Plugin, PluginName}; use crate::toolset::{ToolVersionOptions, ToolVersionRequest}; /// represents a single version of a tool for a particular plugin -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] pub struct ToolVersion { pub request: ToolVersionRequest, pub plugin_name: PluginName, @@ -244,6 +246,32 @@ impl Display for ToolVersion { } } +impl PartialEq for ToolVersion { + fn eq(&self, other: &Self) -> bool { + self.plugin_name == other.plugin_name && self.version == other.version + } +} +impl Eq for ToolVersion {} +impl PartialOrd for ToolVersion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for ToolVersion { + fn cmp(&self, other: &Self) -> Ordering { + match self.plugin_name.cmp(&other.plugin_name) { + Ordering::Equal => self.version.cmp(&other.version), + o => return o, + } + } +} +impl Hash for ToolVersion { + fn hash(&self, state: &mut H) { + self.plugin_name.hash(state); + self.version.hash(state); + } +} + /// subtracts sub from orig and removes suffix /// e.g. version_sub("18.2.3", "2") -> "16" /// e.g. version_sub("18.2.3", "0.1") -> "18.1"