diff --git a/src/config/mod.rs b/src/config/mod.rs index 871e52dab..ec13b8bd9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -283,9 +283,14 @@ fn load_tools(settings: &Settings) -> Result { if settings.experimental { tools.extend(EXPERIMENTAL_CORE_PLUGINS.clone()); } - let plugins = Tool::list()? + let plugins = ExternalPlugin::list()? .into_par_iter() - .map(|p| (p.name.clone(), Arc::new(p))) + .map(|p| { + ( + p.name.clone(), + Arc::new(Tool::new(p.name.clone(), Box::new(p))), + ) + }) .collect::>(); tools.extend(plugins); for tool in &settings.disable_tools { diff --git a/src/plugins/core/bun.rs b/src/plugins/core/bun.rs index 08e722999..0d42f5e24 100644 --- a/src/plugins/core/bun.rs +++ b/src/plugins/core/bun.rs @@ -107,7 +107,7 @@ impl Plugin for BunPlugin { Ok(vec![".bun-version".into()]) } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { assert!(matches!( &ctx.tv.request, ToolVersionRequest::Version { .. } diff --git a/src/plugins/core/deno.rs b/src/plugins/core/deno.rs index 3abd7679c..c5dec07d8 100644 --- a/src/plugins/core/deno.rs +++ b/src/plugins/core/deno.rs @@ -105,7 +105,7 @@ impl Plugin for DenoPlugin { Ok(vec![".deno-version".into()]) } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { assert!(matches!( &ctx.tv.request, ToolVersionRequest::Version { .. } diff --git a/src/plugins/core/go.rs b/src/plugins/core/go.rs index 6d94610fa..60d34f270 100644 --- a/src/plugins/core/go.rs +++ b/src/plugins/core/go.rs @@ -149,7 +149,7 @@ impl Plugin for GoPlugin { Ok(vec![".go-version".into()]) } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { let tarball_path = self.download(&ctx.tv, &ctx.pr)?; self.install(&ctx.tv, &ctx.pr, &tarball_path)?; self.verify(ctx.config, &ctx.tv, &ctx.pr)?; @@ -157,7 +157,7 @@ impl Plugin for GoPlugin { Ok(()) } - fn uninstall_version(&self, _config: &Config, tv: &ToolVersion) -> Result<()> { + fn uninstall_version_impl(&self, _config: &Config, tv: &ToolVersion) -> Result<()> { let gopath = self.gopath(tv); if gopath.exists() { cmd!("chmod", "-R", "u+wx", gopath).run()?; diff --git a/src/plugins/core/java.rs b/src/plugins/core/java.rs index e81d6d68a..457ee90e8 100644 --- a/src/plugins/core/java.rs +++ b/src/plugins/core/java.rs @@ -262,7 +262,7 @@ impl Plugin for JavaPlugin { Ok(aliases) } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { assert!(matches!( &ctx.tv.request, ToolVersionRequest::Version { .. } diff --git a/src/plugins/core/node.rs b/src/plugins/core/node.rs index bd2abf61c..9027f76d9 100644 --- a/src/plugins/core/node.rs +++ b/src/plugins/core/node.rs @@ -260,7 +260,7 @@ impl Plugin for NodePlugin { Ok(body.to_string()) } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { let opts = BuildOpts::new(ctx)?; debug!("node build opts: {:#?}", opts); if *env::RTX_NODE_COMPILE { diff --git a/src/plugins/core/node_build.rs b/src/plugins/core/node_build.rs index 5e0cf2913..2290529b2 100644 --- a/src/plugins/core/node_build.rs +++ b/src/plugins/core/node_build.rs @@ -252,7 +252,7 @@ impl Plugin for NodeBuildPlugin { exit(0); } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { self.install_node_build()?; ctx.pr.set_message("running node-build"); let mut cmd = CmdLineRunner::new(&ctx.config.settings, self.node_build_bin()) diff --git a/src/plugins/core/python.rs b/src/plugins/core/python.rs index 9092e393d..9519d60d2 100644 --- a/src/plugins/core/python.rs +++ b/src/plugins/core/python.rs @@ -169,7 +169,7 @@ impl Plugin for PythonPlugin { Ok(vec![".python-version".to_string()]) } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { self.install_python_build()?; if matches!(&ctx.tv.request, ToolVersionRequest::Ref(..)) { return Err(eyre!("Ref versions not supported for python")); diff --git a/src/plugins/core/ruby.rs b/src/plugins/core/ruby.rs index d68d6aa39..e184a67d5 100644 --- a/src/plugins/core/ruby.rs +++ b/src/plugins/core/ruby.rs @@ -346,7 +346,7 @@ impl Plugin for RubyPlugin { Ok(v) } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { self.update_build_tool()?; assert!(matches!( &ctx.tv.request, diff --git a/src/plugins/external_plugin.rs b/src/plugins/external_plugin.rs index a67f42ca9..b772e4343 100644 --- a/src/plugins/external_plugin.rs +++ b/src/plugins/external_plugin.rs @@ -81,6 +81,13 @@ impl ExternalPlugin { } } + pub fn list() -> Result> { + Ok(file::dir_subdirs(&dirs::PLUGINS)? + .into_iter() + .map(Self::new) + .collect()) + } + fn get_repo_url(&self, config: &Config) -> Result { self.repo_url .clone() @@ -388,7 +395,7 @@ impl Plugin for ExternalPlugin { fn latest_stable_version(&self, settings: &Settings) -> Result> { if !self.has_latest_stable_script() { - return Ok(None); + return self.latest_version(settings, Some("latest".into())); } self.latest_stable_cache .get_or_try_init(|| self.fetch_latest_stable(settings)) @@ -617,7 +624,7 @@ impl Plugin for ExternalPlugin { exit(result.status.code().unwrap_or(1)); } - fn install_version(&self, ctx: &InstallContext) -> Result<()> { + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { let mut sm = self.script_man_for_tv(ctx.config, &ctx.tv); for p in ctx.ts.list_paths(ctx.config) { @@ -636,7 +643,7 @@ impl Plugin for ExternalPlugin { Ok(()) } - fn uninstall_version(&self, config: &Config, tv: &ToolVersion) -> Result<()> { + fn uninstall_version_impl(&self, config: &Config, tv: &ToolVersion) -> Result<()> { if self.plugin_path.join("bin/uninstall").exists() { self.script_man_for_tv(config, tv) .run(&config.settings, &Script::Uninstall)?; diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 29d84a165..e52d6db11 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,22 +1,29 @@ use std::collections::{BTreeMap, HashMap}; use std::fmt::Debug; +use std::fs::File; use std::path::{Path, PathBuf}; use clap::Command; use color_eyre::eyre::Result; use console::style; +use eyre::WrapErr; +use itertools::Itertools; +use regex::Regex; +use versions::Versioning; pub use external_plugin::ExternalPlugin; pub use script_manager::{Script, ScriptManager}; use crate::config::{Config, Settings}; -use crate::file; -use crate::file::display_path; +use crate::file::{display_path, remove_all, remove_all_with_warning}; use crate::install_context::InstallContext; use crate::lock_file::LockFile; -use crate::toolset::{ToolVersion, Toolset}; +use crate::runtime_symlinks::is_runtime_symlink; +use crate::tool::Tool; +use crate::toolset::{ToolVersion, ToolVersionRequest, Toolset}; use crate::ui::multi_progress_report::MultiProgressReport; use crate::ui::progress_report::{ProgressReport, PROG_TEMPLATE}; +use crate::{dirs, file}; pub mod core; mod external_plugin; @@ -31,10 +38,102 @@ pub trait Plugin: Debug + Send + Sync { fn get_type(&self) -> PluginType { PluginType::Core } + fn installs_path(&self) -> PathBuf { + dirs::INSTALLS.join(self.name()) + } + fn cache_path(&self) -> PathBuf { + dirs::CACHE.join(self.name()) + } + fn downloads_path(&self) -> PathBuf { + dirs::DOWNLOADS.join(self.name()) + } fn list_remote_versions(&self, settings: &Settings) -> Result>; - fn latest_stable_version(&self, _settings: &Settings) -> Result> { - Ok(None) + fn latest_stable_version(&self, settings: &Settings) -> Result> { + self.latest_version(settings, Some("latest".into())) + } + fn list_installed_versions(&self) -> Result> { + Ok(match self.installs_path().exists() { + true => file::dir_subdirs(&self.installs_path())? + .iter() + .filter(|v| !is_runtime_symlink(&self.installs_path().join(v))) + .filter(|v| !self.installs_path().join(v).join("incomplete").exists()) + .cloned() + .sorted_by_cached_key(|v| Versioning::new(v).unwrap_or_default()) + .collect(), + false => vec![], + }) + } + fn is_version_installed(&self, tv: &ToolVersion) -> bool { + match tv.request { + ToolVersionRequest::System(_) => true, + _ => { + tv.install_path().exists() + && !self.incomplete_file_path(tv).exists() + && !is_runtime_symlink(&tv.install_path()) + } + } + } + fn is_version_outdated(&self, tool: &Tool, config: &Config, tv: &ToolVersion) -> bool { + let latest = match tv.latest_version(config, tool) { + Ok(latest) => latest, + Err(e) => { + debug!("Error getting latest version for {}: {:#}", self.name(), e); + return false; + } + }; + !self.is_version_installed(tv) || tv.version != latest + } + fn symlink_path(&self, tv: &ToolVersion) -> Option { + match tv.install_path() { + path if path.is_symlink() => Some(path), + _ => None, + } + } + fn create_symlink(&self, version: &str, target: &Path) -> Result<()> { + let link = self.installs_path().join(version); + file::create_dir_all(link.parent().unwrap())?; + file::make_symlink(target, &link) + } + fn list_installed_versions_matching(&self, query: &str) -> Result> { + let versions = self.list_installed_versions()?; + fuzzy_match_filter(versions, query) + } + fn list_versions_matching(&self, settings: &Settings, query: &str) -> Result> { + let versions = self.list_remote_versions(settings)?; + fuzzy_match_filter(versions, query) } + fn latest_version(&self, settings: &Settings, query: Option) -> Result> { + match query { + Some(query) => { + let matches = self.list_versions_matching(settings, &query)?; + Ok(find_match_in_list(&matches, &query)) + } + None => self.latest_stable_version(settings), + } + } + fn latest_installed_version(&self, query: Option) -> Result> { + match query { + Some(query) => { + let matches = self.list_installed_versions_matching(&query)?; + Ok(find_match_in_list(&matches, &query)) + } + None => { + let installed_symlink = self.installs_path().join("latest"); + if installed_symlink.exists() { + let target = installed_symlink.read_link()?; + let version = target + .file_name() + .ok_or_else(|| eyre!("Invalid symlink target"))? + .to_string_lossy() + .to_string(); + Ok(Some(version)) + } else { + Ok(None) + } + } + } + } + fn get_remote_url(&self) -> Option { None } @@ -61,6 +160,12 @@ pub trait Plugin: Debug + Send + Sync { fn uninstall(&self, _pr: &ProgressReport) -> Result<()> { Ok(()) } + fn purge(&self, pr: &ProgressReport) -> Result<()> { + rmdir(&self.installs_path(), pr)?; + rmdir(&self.cache_path(), pr)?; + rmdir(&self.downloads_path(), pr)?; + Ok(()) + } fn get_aliases(&self, _settings: &Settings) -> Result> { Ok(BTreeMap::new()) } @@ -82,8 +187,69 @@ pub trait Plugin: Debug + Send + Sync { ) -> Result<()> { unimplemented!() } - fn install_version(&self, ctx: &InstallContext) -> Result<()>; - fn uninstall_version(&self, _config: &Config, _tv: &ToolVersion) -> Result<()> { + fn install_version(&self, mut ctx: InstallContext) -> Result<()> { + if self.is_version_installed(&ctx.tv) { + if ctx.force { + self.uninstall_version(ctx.config, &ctx.tv, &ctx.pr, false)?; + } else { + return Ok(()); + } + } + self.decorate_progress_bar(&mut ctx.pr, Some(&ctx.tv)); + let _lock = self.get_lock(&ctx.tv.install_path(), ctx.force)?; + self.create_install_dirs(&ctx.tv)?; + + if let Err(e) = self.install_version_impl(&ctx) { + self.cleanup_install_dirs_on_error(&ctx.config.settings, &ctx.tv); + return Err(e); + } + self.cleanup_install_dirs(&ctx.config.settings, &ctx.tv); + // attempt to touch all the .tool-version files to trigger updates in hook-env + let mut touch_dirs = vec![dirs::DATA.to_path_buf()]; + touch_dirs.extend(ctx.config.config_files.keys().cloned()); + for path in touch_dirs { + let err = file::touch_dir(&path); + if let Err(err) = err { + debug!("error touching config file: {:?} {:?}", path, err); + } + } + if let Err(err) = file::remove_file(self.incomplete_file_path(&ctx.tv)) { + debug!("error removing incomplete file: {:?}", err); + } + ctx.pr.set_message(""); + ctx.pr.finish(); + + Ok(()) + } + fn install_version_impl(&self, ctx: &InstallContext) -> Result<()>; + fn uninstall_version( + &self, + config: &Config, + tv: &ToolVersion, + pr: &ProgressReport, + dryrun: bool, + ) -> Result<()> { + pr.set_message(format!("uninstall {tv}")); + + if !dryrun { + self.uninstall_version_impl(config, tv)?; + } + let rmdir = |dir: &Path| { + if !dir.exists() { + return Ok(()); + } + pr.set_message(format!("removing {}", display_path(dir))); + if dryrun { + return Ok(()); + } + remove_all_with_warning(dir) + }; + rmdir(&tv.install_path())?; + rmdir(&tv.download_path())?; + rmdir(&tv.cache_path())?; + Ok(()) + } + fn uninstall_version_impl(&self, _config: &Config, _tv: &ToolVersion) -> Result<()> { Ok(()) } fn list_bin_paths( @@ -92,7 +258,10 @@ pub trait Plugin: Debug + Send + Sync { _ts: &Toolset, tv: &ToolVersion, ) -> Result> { - Ok(vec![tv.install_short_path().join("bin")]) + match tv.request { + ToolVersionRequest::System(_) => Ok(vec![]), + _ => Ok(vec![tv.install_short_path().join("bin")]), + } } fn exec_env( &self, @@ -102,6 +271,22 @@ pub trait Plugin: Debug + Send + Sync { ) -> Result> { Ok(HashMap::new()) } + fn which( + &self, + config: &Config, + ts: &Toolset, + tv: &ToolVersion, + bin_name: &str, + ) -> Result> { + let bin_paths = self.list_bin_paths(config, ts, tv)?; + for bin_path in bin_paths { + let bin_path = bin_path.join(bin_name); + if bin_path.exists() { + return Ok(Some(bin_path)); + } + } + Ok(None) + } fn get_lock(&self, path: &Path, force: bool) -> Result> { let lock = if force { @@ -116,7 +301,28 @@ pub trait Plugin: Debug + Send + Sync { }; Ok(lock) } - + fn create_install_dirs(&self, tv: &ToolVersion) -> Result<()> { + let _ = remove_all_with_warning(tv.install_path()); + let _ = remove_all_with_warning(tv.download_path()); + let _ = remove_all_with_warning(tv.cache_path()); + let _ = file::remove_file(tv.install_path()); // removes if it is a symlink + file::create_dir_all(tv.install_path())?; + file::create_dir_all(tv.download_path())?; + file::create_dir_all(tv.cache_path())?; + File::create(self.incomplete_file_path(tv))?; + Ok(()) + } + fn cleanup_install_dirs_on_error(&self, settings: &Settings, tv: &ToolVersion) { + if !settings.always_keep_install { + let _ = remove_all_with_warning(tv.install_path()); + self.cleanup_install_dirs(settings, tv); + } + } + fn cleanup_install_dirs(&self, settings: &Settings, tv: &ToolVersion) { + if !settings.always_keep_download && !settings.always_keep_install { + let _ = remove_all_with_warning(tv.download_path()); + } + } fn decorate_progress_bar(&self, pr: &mut ProgressReport, tv: Option<&ToolVersion>) { pr.set_style(PROG_TEMPLATE.clone()); let tool = match tv { @@ -130,6 +336,10 @@ pub trait Plugin: Debug + Send + Sync { )); pr.enable_steady_tick(); } + + fn incomplete_file_path(&self, tv: &ToolVersion) -> PathBuf { + tv.install_path().join("incomplete") + } } pub fn unalias_plugin(plugin_name: &str) -> &str { @@ -140,6 +350,49 @@ pub fn unalias_plugin(plugin_name: &str) -> &str { } } +fn fuzzy_match_filter(versions: Vec, query: &str) -> Result> { + let mut query = query; + if query == "latest" { + query = "[0-9].*"; + } + let query_regex = Regex::new(&format!("^{}([-.].+)?$", query))?; + let version_regex = regex!( + r"(^Available versions:|-src|-dev|-latest|-stm|[-\\.]rc|-milestone|-alpha|-beta|[-\\.]pre|-next|(a|b|c)[0-9]+|snapshot|SNAPSHOT|master)" + ); + let versions = versions + .into_iter() + .filter(|v| { + if query == v { + return true; + } + if version_regex.is_match(v) { + return false; + } + query_regex.is_match(v) + }) + .collect(); + Ok(versions) +} +fn find_match_in_list(list: &[String], query: &str) -> Option { + let v = match list.contains(&query.to_string()) { + true => Some(query.to_string()), + false => list.last().map(|s| s.to_string()), + }; + v +} +fn rmdir(dir: &Path, pr: &ProgressReport) -> Result<()> { + if !dir.exists() { + return Ok(()); + } + pr.set_message(format!("removing {}", &dir.to_string_lossy())); + remove_all(dir).wrap_err_with(|| { + format!( + "Failed to remove directory {}", + style(&dir.to_string_lossy()).cyan().for_stderr() + ) + }) +} + pub enum PluginType { #[allow(dead_code)] Core, diff --git a/src/tool.rs b/src/tool.rs index dfed80cfc..476ddab8a 100644 --- a/src/tool.rs +++ b/src/tool.rs @@ -1,26 +1,22 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap}; use std::fmt::{Debug, Display}; -use std::fs::File; + use std::path::{Path, PathBuf}; use clap::Command; -use color_eyre::eyre::{eyre, Context, Result}; -use console::style; -use itertools::Itertools; -use regex::Regex; -use versions::Versioning; +use color_eyre::eyre::Result; use crate::config::{Config, Settings}; -use crate::file::{display_path, remove_all, remove_all_with_warning}; + +use crate::dirs; use crate::install_context::InstallContext; -use crate::plugins::{ExternalPlugin, Plugin}; -use crate::runtime_symlinks::is_runtime_symlink; -use crate::toolset::{ToolVersion, ToolVersionRequest, Toolset}; +use crate::plugins::Plugin; +use crate::toolset::{ToolVersion, Toolset}; use crate::ui::multi_progress_report::MultiProgressReport; -use crate::ui::progress_report::{ProgressReport, PROG_TEMPLATE}; -use crate::{dirs, file}; +use crate::ui::progress_report::ProgressReport; +#[derive(Debug)] pub struct Tool { pub name: String, pub plugin: Box, @@ -42,16 +38,6 @@ impl Tool { } } - pub fn list() -> Result> { - Ok(file::dir_subdirs(&dirs::PLUGINS)? - .into_iter() - .map(|name| { - let plugin = ExternalPlugin::new(name.clone()); - Self::new(name, Box::new(plugin)) - }) - .collect()) - } - pub fn is_installed(&self) -> bool { self.plugin.is_installed() } @@ -69,29 +55,11 @@ impl Tool { } pub fn list_installed_versions(&self) -> Result> { - Ok(match self.installs_path.exists() { - true => file::dir_subdirs(&self.installs_path)? - .iter() - .filter(|v| !is_runtime_symlink(&self.installs_path.join(v))) - // TODO: share logic with incomplete_file_path - .filter(|v| { - !dirs::CACHE - .join(&self.name) - .join(v) - .join("incomplete") - .exists() - }) - .map(|v| Versioning::new(v).unwrap_or_default()) - .sorted() - .map(|v| v.to_string()) - .collect(), - false => vec![], - }) + self.plugin.list_installed_versions() } pub fn list_installed_versions_matching(&self, query: &str) -> Result> { - let versions = self.list_installed_versions()?; - self.fuzzy_match_filter(versions, query) + self.plugin.list_installed_versions_matching(query) } pub fn list_remote_versions(&self, settings: &Settings) -> Result> { @@ -99,8 +67,7 @@ impl Tool { } pub fn list_versions_matching(&self, settings: &Settings, query: &str) -> Result> { - let versions = self.list_remote_versions(settings)?; - self.fuzzy_match_filter(versions, query) + self.plugin.list_versions_matching(settings, query) } pub fn latest_version( @@ -108,36 +75,11 @@ impl Tool { settings: &Settings, query: Option, ) -> Result> { - match query { - Some(query) => { - let matches = self.list_versions_matching(settings, &query)?; - Ok(find_match_in_list(&matches, &query)) - } - None => self.latest_stable_version(settings), - } + self.plugin.latest_version(settings, query) } pub fn latest_installed_version(&self, query: Option) -> Result> { - match query { - Some(query) => { - let matches = self.list_installed_versions_matching(&query)?; - Ok(find_match_in_list(&matches, &query)) - } - None => { - let installed_symlink = self.installs_path.join("latest"); - if installed_symlink.exists() { - let target = installed_symlink.read_link()?; - let version = target - .file_name() - .ok_or_else(|| eyre!("Invalid symlink target"))? - .to_string_lossy() - .to_string(); - Ok(Some(version)) - } else { - Ok(None) - } - } - } + self.plugin.latest_installed_version(query) } pub fn get_aliases(&self, settings: &Settings) -> Result> { @@ -148,96 +90,28 @@ impl Tool { self.plugin.legacy_filenames(settings) } - fn latest_stable_version(&self, settings: &Settings) -> Result> { - if let Some(latest) = self.plugin.latest_stable_version(settings)? { - Ok(Some(latest)) - } else { - self.latest_version(settings, Some("latest".into())) - } - } - pub fn decorate_progress_bar(&self, pr: &mut ProgressReport, tv: Option<&ToolVersion>) { - pr.set_style(PROG_TEMPLATE.clone()); - let tool = match tv { - Some(tv) => tv.to_string(), - None => self.name.to_string(), - }; - pr.set_prefix(format!( - "{} {} ", - style("rtx").dim().for_stderr(), - style(tool).cyan().for_stderr(), - )); - pr.enable_steady_tick(); + self.plugin.decorate_progress_bar(pr, tv); } pub fn is_version_installed(&self, tv: &ToolVersion) -> bool { - match tv.request { - ToolVersionRequest::System(_) => true, - _ => { - tv.install_path().exists() - && !self.incomplete_file_path(tv).exists() - && !is_runtime_symlink(&tv.install_path()) - } - } + self.plugin.is_version_installed(tv) } pub fn is_version_outdated(&self, config: &Config, tv: &ToolVersion) -> bool { - let latest = match tv.latest_version(config, self) { - Ok(latest) => latest, - Err(e) => { - debug!("Error getting latest version for {}: {:#}", self.name, e); - return false; - } - }; - !self.is_version_installed(tv) || tv.version != latest + self.plugin.is_version_outdated(self, config, tv) } pub fn symlink_path(&self, tv: &ToolVersion) -> Option { - match tv.install_path() { - path if path.is_symlink() => Some(path), - _ => None, - } + self.plugin.symlink_path(tv) } pub fn create_symlink(&self, version: &str, target: &Path) -> Result<()> { - let link = self.installs_path.join(version); - file::create_dir_all(link.parent().unwrap())?; - file::make_symlink(target, &link) - } - - pub fn install_version(&self, mut ctx: InstallContext) -> Result<()> { - if self.is_version_installed(&ctx.tv) { - if ctx.force { - self.uninstall_version(ctx.config, &ctx.tv, &ctx.pr, false)?; - } else { - return Ok(()); - } - } - self.decorate_progress_bar(&mut ctx.pr, Some(&ctx.tv)); - let _lock = self.get_lock(&ctx.tv.install_path(), ctx.force)?; - self.create_install_dirs(&ctx.tv)?; - - if let Err(e) = self.plugin.install_version(&ctx) { - self.cleanup_install_dirs_on_error(&ctx.config.settings, &ctx.tv); - return Err(e); - } - self.cleanup_install_dirs(&ctx.config.settings, &ctx.tv); - // attempt to touch all the .tool-version files to trigger updates in hook-env - let mut touch_dirs = vec![dirs::DATA.to_path_buf()]; - touch_dirs.extend(ctx.config.config_files.keys().cloned()); - for path in touch_dirs { - let err = file::touch_dir(&path); - if let Err(err) = err { - debug!("error touching config file: {:?} {:?}", path, err); - } - } - if let Err(err) = file::remove_file(self.incomplete_file_path(&ctx.tv)) { - debug!("error removing incomplete file: {:?}", err); - } - ctx.pr.set_message(""); - ctx.pr.finish(); + self.plugin.create_symlink(version, target) + } - Ok(()) + pub fn install_version(&self, ctx: InstallContext) -> Result<()> { + self.plugin.install_version(ctx) } pub fn uninstall_version( @@ -247,25 +121,7 @@ impl Tool { pr: &ProgressReport, dryrun: bool, ) -> Result<()> { - pr.set_message(format!("uninstall {tv}")); - - if !dryrun { - self.plugin.uninstall_version(config, tv)?; - } - let rmdir = |dir: &Path| { - if !dir.exists() { - return Ok(()); - } - pr.set_message(format!("removing {}", display_path(dir))); - if dryrun { - return Ok(()); - } - remove_all_with_warning(dir) - }; - rmdir(&tv.install_path())?; - rmdir(&tv.download_path())?; - rmdir(&tv.cache_path())?; - Ok(()) + self.plugin.uninstall_version(config, tv, pr, dryrun) } pub fn ensure_installed( @@ -283,10 +139,7 @@ impl Tool { self.plugin.uninstall(pr) } pub fn purge(&self, pr: &ProgressReport) -> Result<()> { - rmdir(&self.installs_path, pr)?; - rmdir(&self.cache_path, pr)?; - rmdir(&self.downloads_path, pr)?; - Ok(()) + self.plugin.purge(pr) } pub fn external_commands(&self) -> Result> { @@ -309,10 +162,7 @@ impl Tool { ts: &Toolset, tv: &ToolVersion, ) -> Result> { - match tv.request { - ToolVersionRequest::System(_) => Ok(vec![]), - _ => self.plugin.list_bin_paths(config, ts, tv), - } + self.plugin.list_bin_paths(config, ts, tv) } pub fn exec_env( &self, @@ -320,10 +170,7 @@ impl Tool { ts: &Toolset, tv: &ToolVersion, ) -> Result> { - match tv.request { - ToolVersionRequest::System(_) => Ok(HashMap::new()), - _ => self.plugin.exec_env(config, ts, tv), - } + self.plugin.exec_env(config, ts, tv) } pub fn which( @@ -333,83 +180,8 @@ impl Tool { tv: &ToolVersion, bin_name: &str, ) -> Result> { - let bin_paths = self.plugin.list_bin_paths(config, ts, tv)?; - for bin_path in bin_paths { - let bin_path = bin_path.join(bin_name); - if bin_path.exists() { - return Ok(Some(bin_path)); - } - } - Ok(None) - } - - fn incomplete_file_path(&self, tv: &ToolVersion) -> PathBuf { - tv.cache_path().join("incomplete") + self.plugin.which(config, ts, tv, bin_name) } - - fn create_install_dirs(&self, tv: &ToolVersion) -> Result<()> { - let _ = remove_all_with_warning(tv.install_path()); - let _ = remove_all_with_warning(tv.download_path()); - let _ = remove_all_with_warning(tv.cache_path()); - let _ = file::remove_file(tv.install_path()); // removes if it is a symlink - file::create_dir_all(tv.install_path())?; - file::create_dir_all(tv.download_path())?; - file::create_dir_all(tv.cache_path())?; - File::create(self.incomplete_file_path(tv))?; - Ok(()) - } - fn cleanup_install_dirs_on_error(&self, settings: &Settings, tv: &ToolVersion) { - if !settings.always_keep_install { - let _ = remove_all_with_warning(tv.install_path()); - self.cleanup_install_dirs(settings, tv); - } - } - fn cleanup_install_dirs(&self, settings: &Settings, tv: &ToolVersion) { - if !settings.always_keep_download && !settings.always_keep_install { - let _ = remove_all_with_warning(tv.download_path()); - } - } - - fn get_lock(&self, path: &Path, force: bool) -> Result> { - self.plugin.get_lock(path, force) - } - - fn fuzzy_match_filter(&self, versions: Vec, query: &str) -> Result> { - let mut query = query; - if query == "latest" { - query = "[0-9].*"; - } - let query_regex = Regex::new(&format!("^{}([-.].+)?$", query))?; - let version_regex = regex!( - r"(^Available versions:|-src|-dev|-latest|-stm|[-\\.]rc|-milestone|-alpha|-beta|[-\\.]pre|-next|(a|b|c)[0-9]+|snapshot|SNAPSHOT|master)" - ); - let versions = versions - .into_iter() - .filter(|v| { - if query == v { - return true; - } - if version_regex.is_match(v) { - return false; - } - query_regex.is_match(v) - }) - .collect(); - Ok(versions) - } -} - -fn rmdir(dir: &Path, pr: &ProgressReport) -> Result<()> { - if !dir.exists() { - return Ok(()); - } - pr.set_message(format!("removing {}", &dir.to_string_lossy())); - remove_all(dir).wrap_err_with(|| { - format!( - "Failed to remove directory {}", - style(&dir.to_string_lossy()).cyan().for_stderr() - ) - }) } impl PartialEq for Tool { @@ -418,30 +190,12 @@ impl PartialEq for Tool { } } -impl Debug for Tool { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Tool") - .field("name", &self.name) - .field("installs_path", &self.installs_path) - .field("plugin", &self.plugin) - .finish() - } -} - impl Display for Tool { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", &self.name) } } -fn find_match_in_list(list: &[String], query: &str) -> Option { - let v = match list.contains(&query.to_string()) { - true => Some(query.to_string()), - false => list.last().map(|s| s.to_string()), - }; - v -} - impl PartialOrd for Tool { fn partial_cmp(&self, other: &Self) -> Option { Some(self.name.cmp(&other.name)) @@ -455,21 +209,3 @@ impl Ord for Tool { } impl Eq for Tool {} - -#[cfg(test)] -mod tests { - use crate::plugins::PluginName; - - use super::*; - - #[test] - fn test_debug() { - let plugin = ExternalPlugin::new(PluginName::from("dummy")); - let tool = Tool::new("dummy".to_string(), Box::new(plugin)); - let debug = format!("{:?}", tool); - assert!(debug.contains("Tool")); - assert!(debug.contains("name")); - assert!(debug.contains("installs_path")); - assert!(debug.contains("plugin")); - } -}