From 77ffdf2e352b2c86c7e719782fb54cd781d2bd94 Mon Sep 17 00:00:00 2001 From: Jeff Dickey <216188+jdxcode@users.noreply.github.com> Date: Sat, 11 Mar 2023 19:05:12 -0600 Subject: [PATCH] feat: support bin/latest-stable (#301) Fixes #21 --- README.md | 5 +-- e2e/test_zigmod | 15 +++++++ src/cli/install.rs | 5 +-- src/cli/latest.rs | 9 ++-- src/plugins/mod.rs | 83 +++++++++++++++++++++++++++++++---- src/plugins/script_manager.rs | 8 ++-- src/toolset/tool_version.rs | 7 +++ 7 files changed, 108 insertions(+), 24 deletions(-) create mode 100755 e2e/test_zigmod diff --git a/README.md b/README.md index 22ce5aaa2..bb0c4114c 100644 --- a/README.md +++ b/README.md @@ -1213,9 +1213,8 @@ Options: Examples: $ rtx install nodejs@18.0.0 # install specific nodejs version $ rtx install nodejs@18 # install fuzzy nodejs version - $ rtx install nodejs # install version specified in .tool-versions - $ rtx install # installs all runtimes specified in .tool-versions for installed plugins - $ rtx install --all # installs all runtimes and all plugins + $ rtx install nodejs # install version specified in .tool-versions or .rtx.toml + $ rtx install # installs all runtimes specified in .tool-versions or .rtx.toml ``` ### `rtx latest` diff --git a/e2e/test_zigmod b/e2e/test_zigmod new file mode 100755 index 000000000..ee35694be --- /dev/null +++ b/e2e/test_zigmod @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +assert() { + local actual + actual="$($*)" + if [[ "$actual" != "$2" ]]; then + echo "Expected '$2' but got '$actual'" + exit 1 + fi +} + +eval "$(rtx activate bash)" && eval "$(rtx hook-env)" +rtx plugin install https://github.com/kachick/asdf-zigmod +rtx x --install-missing zigmod@latest -- zigmod version diff --git a/src/cli/install.rs b/src/cli/install.rs index 2f504cf69..93d38b331 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -199,9 +199,8 @@ static AFTER_LONG_HELP: Lazy = Lazy::new(|| { {} $ rtx install nodejs@18.0.0 # install specific nodejs version $ rtx install nodejs@18 # install fuzzy nodejs version - $ rtx install nodejs # install version specified in .tool-versions - $ rtx install # installs all runtimes specified in .tool-versions for installed plugins - $ rtx install --all # installs all runtimes and all plugins + $ rtx install nodejs # install version specified in .tool-versions or .rtx.toml + $ rtx install # installs all runtimes specified in .tool-versions or .rtx.toml "#, style("Examples:").bold().underlined()} }); diff --git a/src/cli/latest.rs b/src/cli/latest.rs index 8b890baf3..3de5688e7 100644 --- a/src/cli/latest.rs +++ b/src/cli/latest.rs @@ -26,11 +26,8 @@ pub struct Latest { impl Command for Latest { fn run(self, config: Config, out: &mut Output) -> Result<()> { let prefix = match self.runtime.version { - RuntimeArgVersion::None => match self.asdf_version { - Some(version) => version, - None => "latest".to_string(), - }, - RuntimeArgVersion::Version(version) => version, + RuntimeArgVersion::None => self.asdf_version, + RuntimeArgVersion::Version(version) => Some(version), _ => Err(eyre!( "invalid version: {}", style(&self.runtime).cyan().for_stderr() @@ -47,7 +44,7 @@ impl Command for Latest { })?; plugin.clear_remote_version_cache()?; - if let Some(version) = plugin.latest_version(&config.settings, &prefix)? { + if let Some(version) = plugin.latest_version(&config.settings, prefix)? { rtxprintln!(out, "{}", version); } Ok(()) diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 09cb813dd..052eee476 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -42,6 +42,7 @@ pub struct Plugin { installs_path: PathBuf, script_man: ScriptManager, remote_version_cache: CacheManager>, + latest_stable_cache: CacheManager>, alias_cache: CacheManager>, legacy_filename_cache: CacheManager>, } @@ -64,6 +65,10 @@ impl Plugin { .with_fresh_duration(fresh_duration) .with_fresh_file(plugin_path.clone()) .with_fresh_file(plugin_path.join("bin/list-all")), + latest_stable_cache: CacheManager::new(cache_path.join("latest_stable.msgpack.z")) + .with_fresh_duration(fresh_duration) + .with_fresh_file(plugin_path.clone()) + .with_fresh_file(plugin_path.join("bin/latest-stable")), alias_cache: CacheManager::new(cache_path.join("aliases.msgpack.z")) .with_fresh_file(plugin_path.clone()) .with_fresh_file(plugin_path.join("bin/list-aliases")), @@ -186,13 +191,30 @@ impl Plugin { Ok(()) } - pub fn latest_version(&self, settings: &Settings, query: &str) -> Result> { - let matches = self.list_versions_matching(settings, query)?; - let v = match matches.contains(&query.to_string()) { - true => Some(query.to_string()), - false => matches.last().map(|v| v.to_string()), - }; - Ok(v) + pub fn latest_version( + &self, + settings: &Settings, + query: Option, + ) -> Result> { + match query { + Some(query) => { + let matches = self.list_versions_matching(settings, &query)?; + let v = match matches.contains(&query) { + true => Some(query), + false => matches.last().map(|v| v.to_string()), + }; + Ok(v) + } + None => self.latest_stable_version(settings), + } + } + + fn latest_stable_version(&self, settings: &Settings) -> Result> { + if let Some(latest) = self.get_latest_stable(settings)? { + Ok(Some(latest)) + } else { + self.latest_version(settings, Some("latest".into())) + } } pub fn list_versions_matching(&self, settings: &Settings, query: &str) -> Result> { @@ -228,7 +250,8 @@ impl Plugin { } pub fn clear_remote_version_cache(&self) -> Result<()> { - self.remote_version_cache.clear() + self.remote_version_cache.clear()?; + self.latest_stable_cache.clear() } pub fn list_remote_versions(&self, settings: &Settings) -> Result<&Vec> { self.remote_version_cache @@ -272,6 +295,20 @@ impl Plugin { }) .cloned() } + fn get_latest_stable(&self, settings: &Settings) -> Result> { + if !self.has_latest_stable_script() { + return Ok(None); + } + self.latest_stable_cache + .get_or_try_init(|| self.fetch_latest_stable(settings)) + .with_context(|| { + format!( + "Failed fetching latest stable version for plugin {}", + style(&self.name).cyan().for_stderr() + ) + }) + .cloned() + } pub fn external_commands(&self) -> Result>> { let command_path = self.plugin_path.join("lib/commands"); @@ -364,6 +401,18 @@ impl Plugin { .map(|v| v.into()) .collect()) } + fn fetch_latest_stable(&self, settings: &Settings) -> Result> { + let latest_stable = self + .script_man + .read(settings, &Script::LatestStable, settings.verbose)? + .trim() + .to_string(); + Ok(if latest_stable.is_empty() { + None + } else { + Some(latest_stable) + }) + } fn has_list_all_script(&self) -> bool { self.script_man.script_exists(&Script::ListAll) @@ -374,6 +423,9 @@ impl Plugin { fn has_list_legacy_filenames_script(&self) -> bool { self.script_man.script_exists(&Script::ListLegacyFilenames) } + fn has_latest_stable_script(&self) -> bool { + self.script_man.script_exists(&Script::LatestStable) + } fn fetch_aliases(&self, settings: &Settings) -> Result> { let stdout = self .script_man @@ -469,7 +521,20 @@ mod tests { assert_cli!("plugin", "add", "tiny"); let settings = Settings::default(); let plugin = Plugin::new(&PluginName::from("tiny")); - let version = plugin.latest_version(&settings, "1.0.0").unwrap().unwrap(); + let version = plugin + .latest_version(&settings, Some("1.0.0".into())) + .unwrap() + .unwrap(); assert_str_eq!(version, "1.0.0"); + let version = plugin.latest_version(&settings, None).unwrap().unwrap(); + assert_str_eq!(version, "3.1.0"); + } + + #[test] + fn test_latest_stable() { + let settings = Settings::default(); + let plugin = Plugin::new(&PluginName::from("dummy")); + let version = plugin.latest_version(&settings, None).unwrap().unwrap(); + assert_str_eq!(version, "2.0.0"); } } diff --git a/src/plugins/script_manager.rs b/src/plugins/script_manager.rs index 053d76946..46c4ed327 100644 --- a/src/plugins/script_manager.rs +++ b/src/plugins/script_manager.rs @@ -31,23 +31,25 @@ pub enum Script { // PostUninstall, // Plugin + LatestStable, + ListAliases, ListAll, ListLegacyFilenames, - ListAliases, ParseLegacyFile(String), // RuntimeVersion Download, + ExecEnv, Install, - Uninstall, ListBinPaths, - ExecEnv, + Uninstall, } impl Display for Script { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { // Plugin + Script::LatestStable => write!(f, "latest-stable"), Script::ListAll => write!(f, "list-all"), Script::ListLegacyFilenames => write!(f, "list-legacy-filenames"), Script::ListAliases => write!(f, "list-aliases"), diff --git a/src/toolset/tool_version.rs b/src/toolset/tool_version.rs index 6729fdb3e..61b8354ab 100644 --- a/src/toolset/tool_version.rs +++ b/src/toolset/tool_version.rs @@ -79,6 +79,13 @@ impl ToolVersion { return Ok(rtv); } + if v == "latest" { + let v = plugin.latest_version(&config.settings, None)?; + if let Some(v) = v { + let rtv = RuntimeVersion::new(config, plugin, v, self.clone()); + return Ok(rtv); + } + } let matches = plugin.list_versions_matching(&config.settings, &v)?; if matches.contains(&v) { let rtv = RuntimeVersion::new(config, plugin, v, self.clone());