From 0ff49015ad33e136047328d262a44410afcdfce3 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Wed, 13 Nov 2024 09:04:03 -0600 Subject: [PATCH] refactor: consolidate tool/plugin loading to install_state.rs --- docs/registry.md | 9 + e2e/cli/test_link | 15 +- ...st_plugins_install => test_plugin_install} | 0 e2e/plugins/test_plugin_update | 4 + e2e/tools/test_runtime_symlinks | 10 + registry.toml | 1 + src/backend/aqua.rs | 7 +- src/backend/asdf.rs | 53 ++-- src/backend/backend_meta.rs | 83 ----- src/backend/backend_type.rs | 55 ++++ src/backend/cargo.rs | 6 +- src/backend/go.rs | 5 +- src/backend/mod.rs | 298 ++++++++---------- src/backend/npm.rs | 5 +- src/backend/pipx.rs | 5 +- src/backend/spm.rs | 5 +- src/backend/ubi.rs | 5 +- src/backend/vfox.rs | 31 +- src/cli/alias/get.rs | 2 +- src/cli/alias/ls.rs | 2 +- src/cli/args/backend_arg.rs | 179 +++++++---- src/cli/args/tool_arg.rs | 28 +- src/cli/asdf.rs | 4 +- src/cli/backends/ls.rs | 8 +- ...i__backends__ls__tests__backends_list.snap | 1 + src/cli/current.rs | 14 +- src/cli/doctor.rs | 28 +- src/cli/external.rs | 6 +- src/cli/install.rs | 7 +- src/cli/latest.rs | 3 +- src/cli/link.rs | 2 +- src/cli/ls.rs | 24 +- src/cli/ls_remote.rs | 2 +- src/cli/outdated.rs | 2 +- src/cli/plugins/ls.rs | 36 ++- src/cli/plugins/ls_remote.rs | 21 +- src/cli/plugins/uninstall.rs | 28 +- src/cli/plugins/update.rs | 50 +-- src/cli/prune.rs | 2 +- src/cli/registry.rs | 2 +- src/cli/shell.rs | 2 +- ..._latest__tests__latest_missing_plugin.snap | 2 +- src/cli/sync/node.rs | 14 +- src/cli/sync/python.rs | 4 +- src/cli/uninstall.rs | 26 +- src/cli/upgrade.rs | 15 +- src/cli/use.rs | 8 +- src/cli/where.rs | 7 +- src/config/config_file/legacy_version.rs | 6 +- src/config/config_file/mise_toml.rs | 16 +- src/config/config_file/mod.rs | 5 +- ...fig_file__mise_toml__tests__fixture-3.snap | 32 +- ...e__mise_toml__tests__replace_versions.snap | 8 +- src/config/config_file/tool_versions.rs | 12 +- src/config/env_directive.rs | 2 +- src/config/mod.rs | 48 ++- src/eager.rs | 15 +- src/file.rs | 7 +- src/lockfile.rs | 2 +- src/plugins/asdf_plugin.rs | 34 +- src/plugins/core/bun.rs | 14 +- src/plugins/core/deno.rs | 14 +- src/plugins/core/erlang.rs | 24 +- src/plugins/core/go.rs | 13 +- src/plugins/core/java.rs | 17 +- src/plugins/core/mod.rs | 79 ++--- src/plugins/core/node.rs | 19 +- src/plugins/core/python.rs | 23 +- src/plugins/core/ruby.rs | 23 +- src/plugins/core/ruby_windows.rs | 21 +- src/plugins/core/zig.rs | 14 +- src/plugins/mod.rs | 119 +++---- src/plugins/vfox_plugin.rs | 34 +- src/registry.rs | 36 ++- src/runtime_symlinks.rs | 36 +-- src/shims.rs | 8 +- ...untime_symlinks__tests__list_symlinks.snap | 13 - src/test.rs | 3 + src/toolset/builder.rs | 14 +- src/toolset/install_state.rs | 221 +++++++++++++ src/toolset/mod.rs | 48 ++- src/toolset/tool_request.rs | 87 ++--- src/toolset/tool_request_set.rs | 15 +- src/toolset/tool_version.rs | 58 ++-- src/versions_host.rs | 4 +- 85 files changed, 1117 insertions(+), 1153 deletions(-) rename e2e/plugins/{test_plugins_install => test_plugin_install} (100%) create mode 100644 e2e/plugins/test_plugin_update create mode 100644 e2e/tools/test_runtime_symlinks delete mode 100644 src/backend/backend_meta.rs create mode 100644 src/backend/backend_type.rs delete mode 100644 src/snapshots/mise__runtime_symlinks__tests__list_symlinks.snap create mode 100644 src/toolset/install_state.rs diff --git a/docs/registry.md b/docs/registry.md index e21b960feb..fde9b44094 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -84,6 +84,7 @@ editLink: false | btrace | [asdf:joschi/asdf-btrace](https://github.com/joschi/asdf-btrace) | | buf | [ubi:bufbuild/buf](https://github.com/bufbuild/buf) [asdf:truepay/asdf-buf](https://github.com/truepay/asdf-buf) | | buildpack | [asdf:johnlayton/asdf-buildpack](https://github.com/johnlayton/asdf-buildpack) | +| bun | [core:bun](https://mise.jdx.dev/lang/bun.html) [vfox:ahai-code/vfox-bun](https://github.com/ahai-code/vfox-bun) | | bun | [core:bun](https://mise.jdx.dev/lang/bun.html) | | bundler | [asdf:jonathanmorley/asdf-bundler](https://github.com/jonathanmorley/asdf-bundler) | | cabal | [asdf:sestrella/asdf-ghcup](https://github.com/sestrella/asdf-ghcup) | @@ -169,6 +170,7 @@ editLink: false | dbmate | [asdf:juusujanar/asdf-dbmate](https://github.com/juusujanar/asdf-dbmate) | | deck | [asdf:nutellinoit/asdf-deck](https://github.com/nutellinoit/asdf-deck) | | delta | [ubi:dandavison/delta](https://github.com/dandavison/delta) [asdf:andweeb/asdf-delta](https://github.com/andweeb/asdf-delta) | +| deno | [core:deno](https://mise.jdx.dev/lang/deno.html) [vfox:version-fox/vfox-deno](https://github.com/version-fox/vfox-deno) | | deno | [core:deno](https://mise.jdx.dev/lang/deno.html) | | dep | [asdf:paxosglobal/asdf-dep](https://github.com/paxosglobal/asdf-dep) | | depot | [asdf:depot/asdf-depot](https://github.com/depot/asdf-depot) | @@ -218,6 +220,7 @@ editLink: false | envcli | [asdf:zekker6/asdf-envcli](https://github.com/zekker6/asdf-envcli) | | envsubst | [asdf:dex4er/asdf-envsubst](https://github.com/dex4er/asdf-envsubst) | | ephemeral-postgres | [asdf:smashedtoatoms/asdf-ephemeral-postgres](https://github.com/smashedtoatoms/asdf-ephemeral-postgres) | +| erlang | [core:erlang](https://mise.jdx.dev/lang/erlang.html) [asdf:asdf-vm/asdf-erlang](https://github.com/asdf-vm/asdf-erlang) [vfox:version-fox/vfox-erlang](https://github.com/version-fox/vfox-erlang) | | erlang | [core:erlang](https://mise.jdx.dev/lang/erlang.html) | | esc | [asdf:fxsalazar/asdf-esc](https://github.com/fxsalazar/asdf-esc) | | esy | [asdf:asdf-community/asdf-esy](https://github.com/asdf-community/asdf-esy) | @@ -271,6 +274,7 @@ editLink: false | glen | [asdf:bradym/asdf-glen](https://github.com/bradym/asdf-glen) | | glooctl | [asdf:halilkaya/asdf-glooctl](https://github.com/halilkaya/asdf-glooctl) | | glow | [asdf:mise-plugins/asdf-glow](https://github.com/mise-plugins/asdf-glow) | +| go | [core:go](https://mise.jdx.dev/lang/go.html) [vfox:version-fox/vfox-golang](https://github.com/version-fox/vfox-golang) | | go | [core:go](https://mise.jdx.dev/lang/go.html) | | go-containerregistry | [asdf:dex4er/asdf-go-containerregistry](https://github.com/dex4er/asdf-go-containerregistry) | | go-getter | [asdf:ryodocx/asdf-go-getter](https://github.com/ryodocx/asdf-go-getter) | @@ -348,6 +352,7 @@ editLink: false | io | [asdf:mracos/asdf-io](https://github.com/mracos/asdf-io) | | istioctl | [asdf:virtualstaticvoid/asdf-istioctl](https://github.com/virtualstaticvoid/asdf-istioctl) | | janet | [asdf:Jakski/asdf-janet](https://github.com/Jakski/asdf-janet) | +| java | [core:java](https://mise.jdx.dev/lang/java.html) [vfox:version-fox/vfox-java](https://github.com/version-fox/vfox-java) | | java | [core:java](https://mise.jdx.dev/lang/java.html) | | jb | [asdf:beardix/asdf-jb](https://github.com/beardix/asdf-jb) | | jbang | [asdf:jbangdev/jbang-asdf](https://github.com/jbangdev/jbang-asdf) | @@ -511,6 +516,7 @@ editLink: false | nfpm | [ubi:goreleaser/nfpm](https://github.com/goreleaser/nfpm) [asdf:ORCID/asdf-nfpm](https://github.com/ORCID/asdf-nfpm) | | nim | [asdf:asdf-community/asdf-nim](https://github.com/asdf-community/asdf-nim) | | ninja | [asdf:asdf-community/asdf-ninja](https://github.com/asdf-community/asdf-ninja) | +| node | [core:node](https://mise.jdx.dev/lang/node.html) [vfox:version-fox/vfox-nodejs](https://github.com/version-fox/vfox-nodejs) | | node | [core:node](https://mise.jdx.dev/lang/node.html) | | nomad | [asdf:asdf-community/asdf-hashicorp](https://github.com/asdf-community/asdf-hashicorp) | | nomad-pack | [asdf:asdf-community/asdf-hashicorp](https://github.com/asdf-community/asdf-hashicorp) | @@ -586,6 +592,7 @@ editLink: false | purerl | [asdf:GoNZooo/asdf-purerl](https://github.com/GoNZooo/asdf-purerl) | | purescript | [asdf:jrrom/asdf-purescript](https://github.com/jrrom/asdf-purescript) | | purty | [asdf:nsaunders/asdf-purty](https://github.com/nsaunders/asdf-purty) | +| python | [core:python](https://mise.jdx.dev/lang/python.html) [vfox:version-fox/vfox-python](https://github.com/version-fox/vfox-python) | | python | [core:python](https://mise.jdx.dev/lang/python.html) | | qdns | [asdf:moritz-makandra/asdf-plugin-qdns](https://github.com/moritz-makandra/asdf-plugin-qdns) | | quarkus | [asdf:asdf-community/asdf-quarkus](https://github.com/asdf-community/asdf-quarkus) | @@ -616,6 +623,7 @@ editLink: false | rlwrap | [asdf:asdf-community/asdf-rlwrap](https://github.com/asdf-community/asdf-rlwrap) | | rome | [asdf:kichiemon/asdf-rome](https://github.com/kichiemon/asdf-rome) | | rstash | [asdf:carlduevel/asdf-rstash](https://github.com/carlduevel/asdf-rstash) | +| ruby | [core:ruby](https://mise.jdx.dev/lang/ruby.html) [vfox:yanecc/vfox-ruby](https://github.com/yanecc/vfox-ruby) | | ruby | [core:ruby](https://mise.jdx.dev/lang/ruby.html) | | ruff | [ubi:astral-sh/ruff](https://github.com/astral-sh/ruff) [asdf:simhem/asdf-ruff](https://github.com/simhem/asdf-ruff) | | rust | [asdf:code-lever/asdf-rust](https://github.com/code-lever/asdf-rust) | @@ -814,6 +822,7 @@ editLink: false | zbctl | [asdf:camunda-community-hub/asdf-zbctl](https://github.com/camunda-community-hub/asdf-zbctl) | | zellij | [ubi:zellij-org/zellij](https://github.com/zellij-org/zellij) [asdf:chessmango/asdf-zellij](https://github.com/chessmango/asdf-zellij) | | zephyr | [asdf:nsaunders/asdf-zephyr](https://github.com/nsaunders/asdf-zephyr) | +| zig | [core:zig](https://mise.jdx.dev/lang/zig.html) [asdf:cheetah/asdf-zig](https://github.com/cheetah/asdf-zig) [vfox:version-fox/vfox-zig](https://github.com/version-fox/vfox-zig) | | zig | [core:zig](https://mise.jdx.dev/lang/zig.html) | | zigmod | [asdf:mise-plugins/asdf-zigmod](https://github.com/mise-plugins/asdf-zigmod) | | zola | [ubi:getzola/zola](https://github.com/getzola/zola) [asdf:salasrod/asdf-zola](https://github.com/salasrod/asdf-zola) | diff --git a/e2e/cli/test_link b/e2e/cli/test_link index b0e4eba6ff..fe56426c39 100644 --- a/e2e/cli/test_link +++ b/e2e/cli/test_link @@ -1,14 +1,9 @@ #!/usr/bin/env bash -# Systematically installed by run_test -rm "$MISE_DATA_DIR/plugins/dummy" +mise install tiny@1.0.1 tiny@3.1.0 +mkdir -p tmp/tiny +mise link tiny@9.8.7 tmp/tiny -mise plugins ln "$ROOT"/test/data/plugins/dummy -assert_contains "mise plugins" "dummy" +assert_contains "mise ls tiny" "tiny 9.8.7 (symlink)" -assert_fail "mise plugins link $ROOT/test/data/plugins/dummy" -mise plugins link -f "$ROOT"/test/data/plugins/dummy -assert_contains "mise plugins" "dummy" - -mise plugins rm dummy -assert_not_contains "mise plugins" "dummy" +mise uninstall tiny@9.8.7 diff --git a/e2e/plugins/test_plugins_install b/e2e/plugins/test_plugin_install similarity index 100% rename from e2e/plugins/test_plugins_install rename to e2e/plugins/test_plugin_install diff --git a/e2e/plugins/test_plugin_update b/e2e/plugins/test_plugin_update new file mode 100644 index 0000000000..7db5437e7e --- /dev/null +++ b/e2e/plugins/test_plugin_update @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +mise plugin install tiny https://github.com/mise-plugins/rtx-tiny.git +mise plugins update tiny diff --git a/e2e/tools/test_runtime_symlinks b/e2e/tools/test_runtime_symlinks new file mode 100644 index 0000000000..37a955633a --- /dev/null +++ b/e2e/tools/test_runtime_symlinks @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +mise i dummy@1.0.1 dummy@1.0.2 dummy@1.10.0 dummy@2.0.0 dummy@2.1.0 +assert "ls -l $MISE_DATA_DIR/installs/dummy | grep '\->' | awk '{print \$(NF-2),\$(NF)}'" "1 ./1.10.0 +1.0 ./1.0.2 +1.10 ./1.10.0 +2 ./2.1.0 +2.0 ./2.0.0 +2.1 ./2.1.0 +latest ./2.1.0" diff --git a/registry.toml b/registry.toml index bd76b9ebe0..e9bc07ea5e 100644 --- a/registry.toml +++ b/registry.toml @@ -510,6 +510,7 @@ newrelic-cli = ["ubi:newrelic/newrelic-cli[exe=newrelic]", "asdf:NeoHsu/asdf-new nfpm = ["ubi:goreleaser/nfpm", "asdf:ORCID/asdf-nfpm"] nim = ["asdf:asdf-community/asdf-nim"] ninja = ["asdf:asdf-community/asdf-ninja"] +node = ["core:node", "vfox:version-fox/vfox-nodejs"] nomad = ["asdf:asdf-community/asdf-hashicorp"] nomad-pack = ["asdf:asdf-community/asdf-hashicorp"] notation = ["asdf:bodgit/asdf-notation"] diff --git a/src/backend/aqua.rs b/src/backend/aqua.rs index 934fa41d13..ecb90cfb24 100644 --- a/src/backend/aqua.rs +++ b/src/backend/aqua.rs @@ -1,5 +1,6 @@ use crate::aqua::aqua_registry::{AquaPackage, AquaPackageType, AQUA_REGISTRY}; -use crate::backend::{Backend, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::cli::version::{ARCH, OS}; @@ -27,7 +28,7 @@ impl Backend for AquaBackend { BackendType::Aqua } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } @@ -123,7 +124,7 @@ impl Backend for AquaBackend { impl AquaBackend { pub fn from_arg(ba: BackendArg) -> Self { - let mut id = ba.full.strip_prefix("aqua:").unwrap_or(&ba.full); + let mut id = ba.tool_name.as_str(); if !id.contains("/") { id = REGISTRY .get(id) diff --git a/src/backend/asdf.rs b/src/backend/asdf.rs index 48cb9236d1..953df7cd20 100644 --- a/src/backend/asdf.rs +++ b/src/backend/asdf.rs @@ -3,18 +3,17 @@ use std::fmt::{Debug, Formatter}; use std::fs; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; -use std::sync::Arc; use color_eyre::eyre::{eyre, Result, WrapErr}; use console::style; +use crate::backend::backend_type::BackendType; use crate::backend::external_plugin_cache::ExternalPluginCache; -use crate::backend::{ABackend, Backend, BackendList}; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::config::{Config, SETTINGS}; use crate::env_diff::{EnvDiff, EnvDiffOperation}; -use crate::git::Git; use crate::hash::hash_to_str; use crate::install_context::InstallContext; use crate::plugins::asdf_plugin::AsdfPlugin; @@ -23,7 +22,7 @@ use crate::plugins::Script::{Download, ExecEnv, Install, ParseLegacyFile}; use crate::plugins::{Plugin, PluginType, Script, ScriptManager}; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; -use crate::{dirs, env, file, plugins}; +use crate::{env, file}; /// This represents a plugin installed to ~/.local/share/mise/plugins pub struct AsdfBackend { @@ -42,12 +41,8 @@ pub struct AsdfBackend { impl AsdfBackend { pub fn from_arg(ba: BackendArg) -> Self { - let name = ba - .full - .strip_prefix("asdf:") - .unwrap_or(&ba.full) - .to_string(); - let plugin_path = dirs::PLUGINS.join(&name); + let name = ba.tool_name.clone(); + let plugin_path = ba.plugin_path.clone(); let mut toml_path = plugin_path.join("mise.plugin.toml"); if plugin_path.join("rtx.plugin.toml").exists() { toml_path = plugin_path.join("rtx.plugin.toml"); @@ -80,7 +75,13 @@ impl AsdfBackend { .with_fresh_file(plugin_path.join("bin/list-legacy-filenames")) .build(), plugin_path, - plugin: Box::new(AsdfPlugin::new(name.clone())), + plugin: Box::new(AsdfPlugin::new( + ba.plugin_path + .file_name() + .unwrap() + .to_string_lossy() + .to_string(), + )), repo_url: None, toml, name, @@ -91,19 +92,6 @@ impl AsdfBackend { &*self.plugin } - pub fn list() -> Result { - Ok(plugins::INSTALLED_PLUGINS - .iter() - // if metadata.lua exists it's a vfox plugin (hopefully) - .filter(|(_, pt)| matches!(pt, PluginType::Asdf)) - .map(|(d, _)| { - Arc::new(Self::from_arg( - d.file_name().unwrap().to_string_lossy().into(), - )) as ABackend - }) - .collect()) - } - fn fetch_cached_legacy_file(&self, legacy_file: &Path) -> Result> { let fp = self.legacy_cache_file_path(legacy_file); if !fp.exists() || fp.metadata()?.modified()? < legacy_file.metadata()?.modified()? { @@ -244,16 +232,20 @@ impl Hash for AsdfBackend { } impl Backend for AsdfBackend { - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } - fn get_plugin_type(&self) -> PluginType { - PluginType::Asdf + fn get_type(&self) -> BackendType { + BackendType::Asdf + } + + fn get_plugin_type(&self) -> Option { + Some(PluginType::Asdf) } fn get_dependencies(&self, tvr: &ToolRequest) -> Result> { - let out = match tvr.backend().name.as_str() { + let out = match tvr.ba().tool_name.as_str() { "poetry" | "pipenv" | "pipx" => vec!["python"], "elixir" => vec!["erlang"], _ => vec![], @@ -288,11 +280,6 @@ impl Backend for AsdfBackend { .cloned() } - fn get_remote_url(&self) -> Option { - let git = Git::new(&self.plugin_path); - git.get_remote_url().or_else(|| self.repo_url.clone()) - } - fn get_aliases(&self) -> Result> { if let Some(data) = &self.toml.list_aliases.data { return Ok(self.plugin.parse_aliases(data).into_iter().collect()); diff --git a/src/backend/backend_meta.rs b/src/backend/backend_meta.rs deleted file mode 100644 index b8e563cdbb..0000000000 --- a/src/backend/backend_meta.rs +++ /dev/null @@ -1,83 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::cli::args::BackendArg; -use crate::{dirs, file}; - -use super::BackendType; - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct BackendMeta { - pub short: String, - pub id: String, - pub name: String, - pub backend_type: String, -} - -pub const FORGE_META_FILENAME: &str = ".mise.backend.json"; - -impl BackendMeta { - pub fn read(dirname: &str) -> BackendMeta { - let meta_path = &dirs::INSTALLS.join(dirname).join(FORGE_META_FILENAME); - if meta_path.exists() { - let json = file::read_to_string(meta_path).unwrap_or_default(); - if let Ok(meta) = serde_json::from_str(&json) { - return meta; - } - } - Self::default_meta(dirname) - } - - pub fn write(fa: &BackendArg) -> eyre::Result<()> { - if fa.backend_type == BackendType::Asdf { - return Ok(()); - } - let meta = BackendMeta { - short: fa.short.clone(), - id: fa.full.clone(), - name: fa.name.clone(), - backend_type: fa.backend_type.as_ref().to_string(), - }; - - let json = serde_json::to_string(&meta).expect("Could not encode JSON value"); - let meta_path = fa.installs_path.join(FORGE_META_FILENAME); - file::write(meta_path, json)?; - Ok(()) - } - - // Returns a BackendMeta derived from the dirname for backends without a meta file - fn default_meta(dirname: &str) -> BackendMeta { - let id = dirname.replacen('-', ":", 1); - match id.split_once(':') { - Some((backend_type, name)) => { - let name = Self::name_for_type(name.to_string(), backend_type); - let id = format!("{}:{}", backend_type, name); - BackendMeta { - short: id.clone(), - id, - name, - backend_type: backend_type.to_string(), - } - } - None => BackendMeta { - short: id.clone(), - id: id.to_string(), - name: id.to_string(), - backend_type: BackendType::Asdf.as_ref().to_string(), - }, - } - } - - // TODO: remove this when backends come out of experimental - fn name_for_type(name: String, backend_type: &str) -> String { - match backend_type { - "go" => name.replace('-', "/"), - "npm" => { - if name.contains('@') { - return name.replacen('-', "/", 1).to_string(); - } - name.to_string() - } - _ => name.to_string(), - } - } -} diff --git a/src/backend/backend_type.rs b/src/backend/backend_type.rs new file mode 100644 index 0000000000..03e4f94466 --- /dev/null +++ b/src/backend/backend_type.rs @@ -0,0 +1,55 @@ +use std::fmt::{Display, Formatter}; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Copy, + strum::EnumString, + strum::EnumIter, + strum::AsRefStr, + Ord, + PartialOrd, +)] +#[strum(serialize_all = "snake_case")] +pub enum BackendType { + Aqua, + Asdf, + Cargo, + Core, + Go, + Npm, + Pipx, + Spm, + Ubi, + Vfox, + Unknown, +} + +impl Display for BackendType { + fn fmt(&self, formatter: &mut Formatter) -> std::fmt::Result { + write!(formatter, "{}", format!("{:?}", self).to_lowercase()) + } +} + +impl BackendType { + pub fn guess(s: &str) -> BackendType { + let s = s.split(':').next().unwrap_or(s); + let s = s.split('-').next().unwrap_or(s); + match s { + "aqua" => BackendType::Aqua, + "asdf" => BackendType::Asdf, + "cargo" => BackendType::Cargo, + "core" => BackendType::Core, + "go" => BackendType::Go, + "npm" => BackendType::Npm, + "pipx" => BackendType::Pipx, + "spm" => BackendType::Spm, + "ubi" => BackendType::Ubi, + "vfox" => BackendType::Vfox, + _ => BackendType::Unknown, + } + } +} diff --git a/src/backend/cargo.rs b/src/backend/cargo.rs index e4f6fc5bf7..bb69e31d8e 100644 --- a/src/backend/cargo.rs +++ b/src/backend/cargo.rs @@ -5,7 +5,8 @@ use eyre::eyre; use serde_json::Deserializer; use url::Url; -use crate::backend::{Backend, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::cmd::CmdLineRunner; @@ -27,13 +28,12 @@ impl Backend for CargoBackend { BackendType::Cargo } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } fn get_dependencies(&self, _tvr: &ToolRequest) -> eyre::Result> { Ok(vec![ - "cargo".into(), "rust".into(), "cargo-binstall".into(), "sccache".into(), diff --git a/src/backend/go.rs b/src/backend/go.rs index 9ea156c406..769ed81254 100644 --- a/src/backend/go.rs +++ b/src/backend/go.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; -use crate::backend::{Backend, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::cmd::CmdLineRunner; @@ -19,7 +20,7 @@ impl Backend for GoBackend { BackendType::Go } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 715475dc69..befeb3c982 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -5,34 +5,32 @@ use std::hash::Hash; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; -use console::style; -use contracts::requires; -use eyre::{bail, eyre, WrapErr}; -use indexmap::IndexSet; -use itertools::Itertools; -use once_cell::sync::Lazy; -use regex::Regex; -use strum::IntoEnumIterator; -use versions::Versioning; - -use self::backend_meta::BackendMeta; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::{BackendArg, ToolVersionType}; use crate::cmd::CmdLineRunner; use crate::config::{Config, CONFIG, SETTINGS}; use crate::file::{display_path, remove_all, remove_all_with_warning}; use crate::install_context::InstallContext; -use crate::plugins::core::{CorePlugin, CORE_PLUGINS}; +use crate::plugins::core::CORE_PLUGINS; use crate::plugins::{Plugin, PluginType, VERSION_REGEX}; use crate::registry::REGISTRY_BACKEND_MAP; use crate::runtime_symlinks::is_runtime_symlink; -use crate::toolset::{is_outdated_version, ToolRequest, ToolSource, ToolVersion, Toolset}; +use crate::toolset::{ + install_state, is_outdated_version, ToolRequest, ToolSource, ToolVersion, Toolset, +}; use crate::ui::progress_report::SingleReport; -use crate::{config, dirs, env, file, lock_file, versions_host}; +use crate::{dirs, env, file, lock_file, plugins, versions_host}; +use backend_type::BackendType; +use console::style; +use eyre::{bail, eyre, Result, WrapErr}; +use indexmap::IndexSet; +use itertools::Itertools; +use once_cell::sync::Lazy; +use regex::Regex; pub mod aqua; pub mod asdf; -pub mod backend_meta; +pub mod backend_type; pub mod cargo; mod external_plugin_cache; pub mod go; @@ -47,162 +45,121 @@ pub type BackendMap = BTreeMap; pub type BackendList = Vec; pub type VersionCacheManager = CacheManager>; -#[derive( - Debug, - PartialEq, - Eq, - Hash, - Clone, - Copy, - strum::EnumString, - strum::EnumIter, - strum::AsRefStr, - Ord, - PartialOrd, -)] -#[strum(serialize_all = "snake_case")] -pub enum BackendType { - Aqua, - Asdf, - Cargo, - Core, - Go, - Npm, - Pipx, - Spm, - Ubi, - Vfox, -} - -impl Display for BackendType { - fn fmt(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "{}", format!("{:?}", self).to_lowercase()) - } -} - static TOOLS: Mutex> = Mutex::new(None); -fn load_tools() -> BackendMap { +pub fn load_tools() { let mut memo_tools = TOOLS.lock().unwrap(); - if let Some(backends) = &*memo_tools { - return backends.clone(); + if memo_tools.is_some() { + return; } time!("load_tools start"); - let core_tools = CORE_PLUGINS - .iter() - .map(|(_, p)| p.clone()) - .collect::>(); - time!("load_tools core"); - let mut asdf_tools = Ok(vec![]); - let mut vfox_tools = Ok(vec![]); - let mut backend_tools = vec![]; - rayon::scope(|s| { - if !SETTINGS.disable_backends.contains(&"asdf".to_string()) { - s.spawn(|_| asdf_tools = asdf::AsdfBackend::list()); - } - if !SETTINGS.disable_backends.contains(&"vfox".to_string()) { - s.spawn(|_| vfox_tools = vfox::VfoxBackend::list()); - } - backend_tools = INSTALLED_BACKENDS.clone(); - }); - time!("load_tools backends"); + let core_tools = CORE_PLUGINS.values().cloned().collect::>(); let mut tools = core_tools; - tools.extend(asdf_tools.expect("asdf tools failed to load")); - tools.extend(vfox_tools.expect("vfox tools failed to load")); - tools.extend(backend_tools); + time!("load_tools core"); + let plugins = install_state::list_plugins() + .map_err(|err| { + warn!("{err:#}"); + }) + .unwrap_or_default(); + if !SETTINGS.disable_backends.contains(&"asdf".to_string()) { + tools.extend( + plugins + .iter() + .filter(|(_, p)| **p == PluginType::Asdf) + .map(|(p, _)| { + arg_to_backend(BackendArg::new(p.to_string(), Some(format!("asdf:{p}")))) + .unwrap() + }), + ); + } + time!("load_tools asdf"); + if !SETTINGS.disable_backends.contains(&"vfox".to_string()) { + tools.extend( + plugins + .iter() + .filter(|(_, p)| **p == PluginType::Vfox) + .map(|(p, _)| { + arg_to_backend(BackendArg::new(p.to_string(), Some(format!("vfox:{p}")))) + .unwrap() + }), + ); + } + time!("load_tools vfox"); + tools.extend( + install_state::list_tools() + .map_err(|err| { + warn!("{err:#}"); + }) + .unwrap_or_default() + .into_values() + .map(|ist| arg_to_backend(BackendArg::new(ist.short, Some(ist.full))).unwrap()), + ); + time!("load_tools install_state"); tools.retain(|backend| !SETTINGS.disable_tools.contains(backend.id())); let tools: BackendMap = tools .into_iter() - .map(|plugin| (plugin.id().to_string(), plugin)) + .map(|backend| (backend.id().to_string(), backend)) .collect(); *memo_tools = Some(tools.clone()); time!("load_tools done"); - tools } -pub static INSTALLED_BACKENDS: Lazy> = Lazy::new(|| { - file::dir_subdirs(&dirs::INSTALLS) +pub fn list() -> BackendList { + load_tools(); + TOOLS + .lock() .unwrap() - .into_iter() - .map(|dir| arg_to_backend(BackendMeta::read(&dir).into())) - .filter(|f| !matches!(f.fa().backend_type, BackendType::Asdf | BackendType::Vfox)) + .as_ref() + .unwrap() + .values() + .cloned() .collect() -}); - -pub fn list() -> BackendList { - load_tools().values().cloned().collect() -} - -pub fn list_backend_types() -> Vec { - BackendType::iter().collect() } -pub fn get(fa: &BackendArg) -> ABackend { - let mut name = fa.short.clone(); - if config::is_loaded() { - if let Some(full) = CONFIG - .get_all_aliases() - .get(&name) - .and_then(|a| a.full.clone()) - { - name = full; - } - } - if let Some(backend) = load_tools().get(&name) { - backend.clone() +pub fn get(ba: &BackendArg) -> Option { + load_tools(); + let mut m = TOOLS.lock().unwrap(); + let backends = m.as_mut().unwrap(); + if let Some(backend) = backends.get(&ba.short) { + Some(backend.clone()) + } else if let Some(backend) = arg_to_backend(ba.clone()) { + backends.insert(ba.short.clone(), backend.clone()); + Some(backend) } else { - let mut m = TOOLS.lock().unwrap(); - let backends = m.as_mut().unwrap(); - let mut fa = fa.clone(); - fa.full = name.clone(); - backends - .entry(name) - .or_insert_with(|| arg_to_backend(fa)) - .clone() - } -} - -pub fn arg_to_backend(ba: BackendArg) -> ABackend { - match ba.backend_type { - BackendType::Aqua => Arc::new(aqua::AquaBackend::from_arg(ba)), - BackendType::Asdf => Arc::new(asdf::AsdfBackend::from_arg(ba)), - BackendType::Cargo => Arc::new(cargo::CargoBackend::from_arg(ba)), - BackendType::Core => Arc::new(asdf::AsdfBackend::from_arg(ba)), - BackendType::Npm => Arc::new(npm::NPMBackend::from_arg(ba)), - BackendType::Go => Arc::new(go::GoBackend::from_arg(ba)), - BackendType::Pipx => Arc::new(pipx::PIPXBackend::from_arg(ba)), - BackendType::Spm => Arc::new(spm::SPMBackend::from_arg(ba)), - BackendType::Ubi => Arc::new(ubi::UbiBackend::from_arg(ba)), - BackendType::Vfox => Arc::new(vfox::VfoxBackend::from_arg(ba)), - } -} - -impl From for ABackend { - fn from(fa: BackendArg) -> Self { - get(&fa) + None } } -impl From<&BackendArg> for ABackend { - fn from(fa: &BackendArg) -> Self { - get(fa) +pub fn arg_to_backend(ba: BackendArg) -> Option { + match ba.backend_type() { + BackendType::Core => CORE_PLUGINS.get(&ba.short).cloned(), + BackendType::Aqua => Some(Arc::new(aqua::AquaBackend::from_arg(ba))), + BackendType::Asdf => Some(Arc::new(asdf::AsdfBackend::from_arg(ba))), + BackendType::Cargo => Some(Arc::new(cargo::CargoBackend::from_arg(ba))), + BackendType::Npm => Some(Arc::new(npm::NPMBackend::from_arg(ba))), + BackendType::Go => Some(Arc::new(go::GoBackend::from_arg(ba))), + BackendType::Pipx => Some(Arc::new(pipx::PIPXBackend::from_arg(ba))), + BackendType::Spm => Some(Arc::new(spm::SPMBackend::from_arg(ba))), + BackendType::Ubi => Some(Arc::new(ubi::UbiBackend::from_arg(ba))), + BackendType::Vfox => Some(Arc::new(vfox::VfoxBackend::from_arg(ba))), + BackendType::Unknown => None, } } pub trait Backend: Debug + Send + Sync { fn id(&self) -> &str { - &self.fa().short + &self.ba().short } fn name(&self) -> &str { - &self.fa().name + &self.ba().tool_name } fn get_type(&self) -> BackendType { - BackendType::Asdf + BackendType::Core } - fn fa(&self) -> &BackendArg; - fn get_plugin_type(&self) -> PluginType { - PluginType::Core + fn ba(&self) -> &BackendArg; + fn get_plugin_type(&self) -> Option { + None } /// If any of these tools are installing in parallel, we should wait for them to finish /// before installing this tool. @@ -213,9 +170,9 @@ pub trait Backend: Debug + Send + Sync { let mut deps: IndexSet<_> = self .get_dependencies(tvr)? .into_iter() - .flat_map(|fa| { - let short = fa.short.clone(); - [fa].into_iter().chain( + .flat_map(|ba| { + let short = ba.short.clone(); + [ba].into_iter().chain( REGISTRY_BACKEND_MAP .get(short.as_str()) .cloned() @@ -223,7 +180,10 @@ pub trait Backend: Debug + Send + Sync { ) }) .collect(); - let dep_backends = deps.iter().map(|fa| fa.into()).collect::>(); + let dep_backends = deps + .iter() + .map(|ba| ba.backend()) + .collect::>>()?; for dep in dep_backends { // TODO: pass the right tvr let tvr = ToolRequest::System(dep.id().into(), ToolSource::Unknown); @@ -236,8 +196,8 @@ pub trait Backend: Debug + Send + Sync { self.ensure_dependencies_installed()?; self.get_remote_version_cache() .get_or_try_init(|| { - trace!("Listing remote versions for {}", self.fa().to_string()); - match versions_host::list_versions(self.fa()) { + trace!("Listing remote versions for {}", self.ba().to_string()); + match versions_host::list_versions(self.ba()) { Ok(Some(versions)) => return Ok(versions), Ok(None) => {} Err(e) => { @@ -246,7 +206,7 @@ pub trait Backend: Debug + Send + Sync { }; trace!( "Calling backend to list remote versions for {}", - self.fa().to_string() + self.ba().to_string() ); let versions = self ._list_remote_versions()? @@ -271,17 +231,7 @@ pub trait Backend: Debug + Send + Sync { self.latest_version(Some("latest".into())) } fn list_installed_versions(&self) -> eyre::Result> { - let installs_path = &self.fa().installs_path; - Ok(match installs_path.exists() { - true => file::dir_subdirs(installs_path)? - .into_iter() - .filter(|v| !v.starts_with('.')) - .filter(|v| !is_runtime_symlink(&installs_path.join(v))) - .filter(|v| !installs_path.join(v).join("incomplete").exists()) - .sorted_by_cached_key(|v| (Versioning::new(v), v.to_string())) - .collect(), - false => vec![], - }) + install_state::list_versions(&self.ba().short) } fn is_version_installed(&self, tv: &ToolVersion, check_symlink: bool) -> bool { match tv.request { @@ -309,7 +259,7 @@ pub trait Backend: Debug + Send + Sync { Err(e) => { debug!( "Error getting latest version for {}: {:#}", - self.fa().to_string(), + self.ba().to_string(), e ); return false; @@ -328,7 +278,7 @@ pub trait Backend: Debug + Send + Sync { version: &str, target: &Path, ) -> eyre::Result> { - let link = self.fa().installs_path.join(version); + let link = self.ba().installs_path.join(version); if link.exists() { return Ok(None); } @@ -360,7 +310,7 @@ pub trait Backend: Debug + Send + Sync { Ok(find_match_in_list(&matches, &query)) } None => { - let installed_symlink = self.fa().installs_path.join("latest"); + let installed_symlink = self.ba().installs_path.join("latest"); if installed_symlink.exists() { if installed_symlink.is_dir() && !installed_symlink.is_symlink() { return Ok(Some("latest".to_string())); @@ -379,9 +329,6 @@ pub trait Backend: Debug + Send + Sync { } } - fn get_remote_url(&self) -> Option { - None - } fn ensure_dependencies_installed(&self) -> eyre::Result<()> { let deps = self .get_all_dependencies(&ToolRequest::System(self.id().into(), ToolSource::Unknown))? @@ -402,9 +349,9 @@ pub trait Backend: Debug + Send + Sync { Ok(()) } fn purge(&self, pr: &dyn SingleReport) -> eyre::Result<()> { - rmdir(&self.fa().installs_path, pr)?; - rmdir(&self.fa().cache_path, pr)?; - rmdir(&self.fa().downloads_path, pr)?; + rmdir(&self.ba().installs_path, pr)?; + rmdir(&self.ba().cache_path, pr)?; + rmdir(&self.ba().downloads_path, pr)?; Ok(()) } fn get_aliases(&self) -> eyre::Result> { @@ -421,7 +368,6 @@ pub trait Backend: Debug + Send + Sync { None } - #[requires(ctx.tv.backend().backend_type == self.get_type())] fn install_version(&self, ctx: InstallContext) -> eyre::Result<()> { if let Some(plugin) = self.plugin() { plugin.is_installed_err()?; @@ -447,7 +393,7 @@ pub trait Backend: Debug + Send + Sync { ))); } - BackendMeta::write(ctx.tv.backend())?; + self.write_backend_meta()?; self.cleanup_install_dirs(&ctx.tv); // attempt to touch all the .tool-version files to trigger updates in hook-env @@ -474,7 +420,10 @@ pub trait Backend: Debug + Send + Sync { fn run_postinstall_hook(&self, ctx: &InstallContext, script: &str) -> eyre::Result<()> { CmdLineRunner::new(&*env::SHELL) - .env(&*env::PATH_KEY, CorePlugin::path_env_with_tv_path(&ctx.tv)?) + .env( + &*env::PATH_KEY, + plugins::core::path_env_with_tv_path(&ctx.tv)?, + ) .with_pr(ctx.pr.as_ref()) .arg("-c") .arg(script) @@ -569,7 +518,7 @@ pub trait Backend: Debug + Send + Sync { } } fn incomplete_file_path(&self, tv: &ToolVersion) -> PathBuf { - tv.cache_path().join("incomplete") + install_state::incomplete_file_path(&tv.ba().short, &tv.tv_pathname()) } fn dependency_toolset(&self) -> eyre::Result { @@ -623,10 +572,10 @@ pub trait Backend: Debug + Send + Sync { REMOTE_VERSION_CACHE .lock() .unwrap() - .entry(self.fa().full.to_string()) + .entry(self.ba().full()) .or_insert_with(|| { let mut cm = CacheManagerBuilder::new( - self.fa().cache_path.join("remote_versions.msgpack.z"), + self.ba().cache_path.join("remote_versions.msgpack.z"), ) .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()); if let Some(plugin_path) = self.plugin().map(|p| p.path()) { @@ -639,6 +588,13 @@ pub trait Backend: Debug + Send + Sync { }) .clone() } + + fn write_backend_meta(&self) -> eyre::Result<()> { + file::write( + self.ba().installs_path.join(".mise.backend"), + self.ba().full(), + ) + } } fn find_match_in_list(list: &[String], query: &str) -> Option { diff --git a/src/backend/npm.rs b/src/backend/npm.rs index fb434b8beb..6ec2fdd7b3 100644 --- a/src/backend/npm.rs +++ b/src/backend/npm.rs @@ -1,7 +1,8 @@ use serde_json::Value; use std::fmt::Debug; -use crate::backend::{Backend, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::cmd::CmdLineRunner; @@ -23,7 +24,7 @@ impl Backend for NPMBackend { BackendType::Npm } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } diff --git a/src/backend/pipx.rs b/src/backend/pipx.rs index 22a797bfb6..6c0ca3e0ff 100644 --- a/src/backend/pipx.rs +++ b/src/backend/pipx.rs @@ -1,4 +1,5 @@ -use crate::backend::{Backend, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::cmd::CmdLineRunner; @@ -26,7 +27,7 @@ impl Backend for PIPXBackend { BackendType::Pipx } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } diff --git a/src/backend/spm.rs b/src/backend/spm.rs index 8d5217fff4..e8ad03881d 100644 --- a/src/backend/spm.rs +++ b/src/backend/spm.rs @@ -1,4 +1,5 @@ -use crate::backend::{Backend, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::cmd::CmdLineRunner; @@ -29,7 +30,7 @@ impl Backend for SPMBackend { BackendType::Spm } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } diff --git a/src/backend/ubi.rs b/src/backend/ubi.rs index 3fde100d26..8f769a5f64 100644 --- a/src/backend/ubi.rs +++ b/src/backend/ubi.rs @@ -1,4 +1,5 @@ -use crate::backend::{Backend, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::config::SETTINGS; @@ -26,7 +27,7 @@ impl Backend for UbiBackend { BackendType::Ubi } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } diff --git a/src/backend/vfox.rs b/src/backend/vfox.rs index 8be3340ff7..4a8a5ca3af 100644 --- a/src/backend/vfox.rs +++ b/src/backend/vfox.rs @@ -1,12 +1,13 @@ -use crate::{env, plugins}; +use crate::env; use heck::ToKebabCase; use std::collections::{BTreeMap, HashMap}; use std::fmt::Debug; use std::path::PathBuf; -use std::sync::{Arc, RwLock}; +use std::sync::RwLock; use std::thread; -use crate::backend::{ABackend, Backend, BackendList, BackendType}; +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::config::{Config, SETTINGS}; @@ -31,11 +32,11 @@ impl Backend for VfoxBackend { BackendType::Vfox } - fn get_plugin_type(&self) -> PluginType { - PluginType::Vfox + fn get_plugin_type(&self) -> Option { + Some(PluginType::Vfox) } - fn fa(&self) -> &BackendArg { + fn ba(&self) -> &BackendArg { &self.ba } @@ -98,7 +99,7 @@ impl Backend for VfoxBackend { } fn get_dependencies(&self, tvr: &ToolRequest) -> eyre::Result> { - let out = match tvr.backend().name.as_str() { + let out = match tvr.ba().tool_name.as_str() { "poetry" | "pipenv" | "pipx" => vec!["python"], "elixir" => vec!["erlang"], _ => vec![], @@ -108,23 +109,11 @@ impl Backend for VfoxBackend { } impl VfoxBackend { - pub fn list() -> eyre::Result { - Ok(plugins::INSTALLED_PLUGINS - .iter() - .filter(|(_, pt)| matches!(pt, PluginType::Vfox)) - .map(|(d, _)| { - Arc::new(Self::from_arg( - d.file_name().unwrap().to_string_lossy().into(), - )) as ABackend - }) - .collect()) - } - pub fn from_arg(ba: BackendArg) -> Self { let pathname = ba.short.to_kebab_case(); let plugin_path = dirs::PLUGINS.join(&pathname); let mut plugin = VfoxPlugin::new(pathname.clone()); - plugin.full = Some(ba.full.clone()); + plugin.full = Some(ba.full()); Self { remote_version_cache: CacheManagerBuilder::new( ba.cache_path.join("remote_versions.msgpack.z"), @@ -150,7 +139,7 @@ impl VfoxBackend { CacheManagerBuilder::new(tv.cache_path().join("exec_env.msgpack.z")) .with_fresh_file(dirs::DATA.to_path_buf()) .with_fresh_file(self.plugin.plugin_path.to_path_buf()) - .with_fresh_file(self.fa().installs_path.to_path_buf()) + .with_fresh_file(self.ba().installs_path.to_path_buf()) .build(), ); } diff --git a/src/cli/alias/get.rs b/src/cli/alias/get.rs index 99b3e969d6..acb7b8ea79 100644 --- a/src/cli/alias/get.rs +++ b/src/cli/alias/get.rs @@ -19,7 +19,7 @@ pub struct AliasGet { impl AliasGet { pub fn run(self) -> Result<()> { let config = Config::try_get()?; - match config.get_all_aliases().get(&self.plugin.short) { + match config.all_aliases.get(&self.plugin.short) { Some(alias) => match alias.versions.get(&self.alias) { Some(alias) => { miseprintln!("{alias}"); diff --git a/src/cli/alias/ls.rs b/src/cli/alias/ls.rs index 5709bb0301..a18e313ec4 100644 --- a/src/cli/alias/ls.rs +++ b/src/cli/alias/ls.rs @@ -30,7 +30,7 @@ impl AliasLs { pub fn run(self) -> Result<()> { let config = Config::try_get()?; let rows = config - .get_all_aliases() + .all_aliases .iter() .filter(|(short, _)| { self.tool.is_none() || self.tool.as_ref().is_some_and(|f| &f.short == *short) diff --git a/src/cli/args/backend_arg.rs b/src/cli/args/backend_arg.rs index 5a6ee6415d..1989030bb4 100644 --- a/src/cli/args/backend_arg.rs +++ b/src/cli/args/backend_arg.rs @@ -1,91 +1,146 @@ -use crate::backend::backend_meta::BackendMeta; -use crate::backend::{unalias_backend, BackendType}; -use crate::dirs; -use crate::plugins::{PluginType, PLUGIN_NAMES_TO_TYPE}; +use crate::backend::backend_type::BackendType; +use crate::backend::{unalias_backend, ABackend}; +use crate::config::CONFIG; use crate::registry::REGISTRY_BACKEND_MAP; -use crate::toolset::{parse_tool_options, ToolVersionOptions}; +use crate::toolset::{install_state, parse_tool_options, ToolVersionOptions}; +use crate::{backend, config, dirs}; +use contracts::requires; +use eyre::{bail, Result}; use heck::ToKebabCase; use std::fmt::{Debug, Display}; use std::hash::Hash; use std::path::PathBuf; use xx::regex; -#[derive(Clone, PartialOrd, Ord)] +#[derive(Clone)] pub struct BackendArg { /// short or full identifier (what the user specified), "node", "prettier", "npm:prettier", "cargo:eza" pub short: String, /// full identifier, "core:node", "npm:prettier", "cargo:eza", "vfox:version-fox/vfox-nodejs" - pub full: String, + full: Option, /// the name of the tool within the backend, e.g.: "node", "prettier", "eza", "vfox-nodejs" - pub name: String, - /// type of backend, "asdf", "cargo", "core", "npm", "vfox" - pub backend_type: BackendType, + pub tool_name: String, /// ~/.local/share/mise/cache/ pub cache_path: PathBuf, /// ~/.local/share/mise/installs/ pub installs_path: PathBuf, /// ~/.local/share/mise/downloads/ pub downloads_path: PathBuf, + /// ~/.local/share/mise/plugins/ + pub plugin_path: PathBuf, pub opts: Option, + // TODO: make this not a hash key anymore to use this + // backend: OnceCell, } impl> From for BackendArg { fn from(s: A) -> Self { let s = s.as_ref(); - if let Some(fa) = REGISTRY_BACKEND_MAP.get(s).and_then(|rbm| rbm.first()) { - fa.clone() + if let Some(ba) = REGISTRY_BACKEND_MAP + .get(unalias_backend(s)) + .and_then(|rbm| rbm.first()) + { + ba.clone() } else { - Self::new(s, s) + Self::new(s.to_string(), None) } } } -impl From for BackendArg { - fn from(meta: BackendMeta) -> Self { - meta.short.into() - } -} - impl BackendArg { - pub fn new(short: &str, full: &str) -> Self { - let short = unalias_backend(short).to_string(); + #[requires(!short.is_empty())] + pub fn new(short: String, full: Option) -> Self { + let short = unalias_backend(&short).to_string(); + let (_backend, mut tool_name) = full + .as_ref() + .unwrap_or(&short) + .split_once(':') + .unwrap_or(("", full.as_ref().unwrap_or(&short))); let short = regex!(r#"\[.+\]$"#).replace_all(&short, "").to_string(); - let (backend, mut name) = full.split_once(':').unwrap_or(("", full)); - let mut opts = None; - if let Some(c) = regex!(r"^(.+)\[(.+)\]$").captures(name) { - name = c.get(1).unwrap().as_str(); + if let Some(c) = regex!(r"^(.+)\[(.+)\]$").captures(tool_name) { + tool_name = c.get(1).unwrap().as_str(); opts = Some(parse_tool_options(c.get(2).unwrap().as_str())); } - let backend = unalias_backend(backend); + Self::new_raw(short.clone(), full.clone(), tool_name.to_string(), opts) + } - let backend_type = if let Some(pt) = PLUGIN_NAMES_TO_TYPE.get(&short) { - match pt { - PluginType::Asdf => BackendType::Asdf, - PluginType::Vfox => BackendType::Vfox, - PluginType::Core => BackendType::Core, - } - } else { - backend.parse().unwrap_or(BackendType::Asdf) - }; - let full = match backend_type { - BackendType::Asdf | BackendType::Core => short.clone(), - backend_type => format!("{backend_type}:{name}"), - }; + pub fn new_raw( + short: String, + full: Option, + tool_name: String, + opts: Option, + ) -> Self { let pathname = short.to_kebab_case(); Self { - name: name.to_string(), - backend_type, + tool_name, short, full, + plugin_path: dirs::PLUGINS.join(&pathname), cache_path: dirs::CACHE.join(&pathname), installs_path: dirs::INSTALLS.join(&pathname), downloads_path: dirs::DOWNLOADS.join(&pathname), opts, + // backend: Default::default(), + } + } + + pub fn backend(&self) -> Result { + // TODO: see above about hash key + // let backend = self.backend.get_or_try_init(|| { + // if let Some(backend) = backend::get(self) { + // Ok(backend) + // } else { + // bail!("{self} not found in mise tool registry"); + // } + // })?; + // Ok(backend.clone()) + if let Some(backend) = backend::get(self) { + Ok(backend) + } else { + bail!("{self} not found in mise tool registry"); } } + + pub fn backend_type(&self) -> BackendType { + if let Ok(Some(backend_type)) = install_state::backend_type(&self.short) { + return backend_type; + } + let full = self.full(); + let backend = full.split(':').next().unwrap(); + if let Ok(backend_type) = backend.parse() { + return backend_type; + } + if config::is_loaded() { + if let Some(repo_url) = CONFIG.get_repo_url(&self.short) { + return if repo_url.contains("vfox-") { + BackendType::Vfox + } else { + // TODO: maybe something more intelligent? + BackendType::Asdf + }; + } + } + BackendType::Unknown + } + + pub fn full(&self) -> String { + if let Some(full) = &self.full { + return full.clone(); + } + if config::is_loaded() { + if let Some(full) = CONFIG + .all_aliases + .get(&self.short) + .and_then(|a| a.full.clone()) + { + return full; + } + } + unalias_backend(&self.short).to_string() + } } impl Display for BackendArg { @@ -96,8 +151,8 @@ impl Display for BackendArg { impl Debug for BackendArg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.short != self.full { - write!(f, r#"BackendArg("{}" -> "{}")"#, self.short, self.full) + if let Some(full) = &self.full { + write!(f, r#"BackendArg("{}" -> "{}")"#, self.short, full) } else { write!(f, r#"BackendArg("{}")"#, self.short) } @@ -112,6 +167,18 @@ impl PartialEq for BackendArg { impl Eq for BackendArg {} +impl PartialOrd for BackendArg { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.short.cmp(&other.short)) + } +} + +impl Ord for BackendArg { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.short.cmp(&other.short) + } +} + impl Hash for BackendArg { fn hash(&self, state: &mut H) { self.short.hash(state); @@ -127,21 +194,24 @@ mod tests { #[test] fn test_backend_arg() { reset(); - let t = |s: &str, id, name, t| { + let t = |s: &str, full, tool_name, t| { let fa: BackendArg = s.into(); - assert_str_eq!(fa.full, id); - assert_str_eq!(fa.name, name); - assert_eq!(fa.backend_type, t); + assert_str_eq!(full, fa.full()); + assert_str_eq!(tool_name, fa.tool_name); + assert_eq!(t, fa.backend_type()); }; - let asdf = |s, id, name| t(s, id, name, BackendType::Asdf); - let cargo = |s, id, name| t(s, id, name, BackendType::Cargo); - // let core = |s, id, name| t(s, id, name, BackendType::Core); - let npm = |s, id, name| t(s, id, name, BackendType::Npm); - let vfox = |s, id, name| t(s, id, name, BackendType::Vfox); + let asdf = |s, full, name| t(s, full, name, BackendType::Asdf); + let cargo = |s, full, name| t(s, full, name, BackendType::Cargo); + // let core = |s, full, name| t(s, full, name, BackendType::Core); + let npm = |s, full, name| t(s, full, name, BackendType::Npm); + let vfox = |s, full, name| t(s, full, name, BackendType::Vfox); asdf("asdf:poetry", "asdf:poetry", "poetry"); - asdf("poetry", "poetry", "mise-plugins/mise-poetry"); - asdf("", "", ""); + asdf( + "poetry", + "asdf:mise-plugins/mise-poetry", + "mise-plugins/mise-poetry", + ); cargo("cargo:eza", "cargo:eza", "eza"); // core("node", "node", "node"); npm("npm:@antfu/ni", "npm:@antfu/ni", "@antfu/ni"); @@ -164,7 +234,6 @@ mod tests { }; t("asdf:node", "asdf-node"); t("node", "node"); - t("", ""); t("cargo:eza", "cargo-eza"); t("npm:@antfu/ni", "npm-antfu-ni"); t("npm:prettier", "npm-prettier"); diff --git a/src/cli/args/tool_arg.rs b/src/cli/args/tool_arg.rs index c633c63d8a..60b7073916 100644 --- a/src/cli/args/tool_arg.rs +++ b/src/cli/args/tool_arg.rs @@ -12,7 +12,7 @@ use xx::regex; #[derive(Debug, Clone, Eq, PartialEq)] pub struct ToolArg { pub short: String, - pub backend: BackendArg, + pub ba: BackendArg, pub version: Option, pub version_type: ToolVersionType, pub tvr: Option, @@ -57,7 +57,7 @@ impl FromStr for ToolArg { tvr, version: version.map(|v| v.to_string()), version_type, - backend, + ba: backend, opts, }) } @@ -111,16 +111,16 @@ impl ToolArg { let re = regex!(r"^\d+(\.\d+)*$"); let a = tools[0].clone(); let b = tools[1].clone(); - if a.tvr.is_none() && b.tvr.is_none() && re.is_match(&b.backend.name) { + if a.tvr.is_none() && b.tvr.is_none() && re.is_match(&b.ba.tool_name) { tools[1].short = a.short.clone(); tools[1].tvr = Some(ToolRequest::new( - a.backend.clone(), - &b.backend.name, + a.ba.clone(), + &b.ba.tool_name, ToolSource::Argument, )?); - tools[1].backend = a.backend; - tools[1].version_type = b.backend.name.parse()?; - tools[1].version = Some(b.backend.name); + tools[1].ba = a.ba; + tools[1].version_type = b.ba.tool_name.parse()?; + tools[1].version = Some(b.ba.tool_name); tools.remove(0); } } @@ -129,9 +129,7 @@ impl ToolArg { pub fn with_version(self, version: &str) -> Self { Self { - tvr: Some( - ToolRequest::new(self.backend.clone(), version, ToolSource::Argument).unwrap(), - ), + tvr: Some(ToolRequest::new(self.ba.clone(), version, ToolSource::Argument).unwrap()), version: Some(version.into()), version_type: version.parse().unwrap(), ..self @@ -156,7 +154,7 @@ impl Display for ToolArg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.tvr { Some(tvr) => write!(f, "{}", tvr), - _ => write!(f, "{}", self.backend.name), + _ => write!(f, "{}", self.ba.tool_name), } } } @@ -196,7 +194,7 @@ mod tests { tool, ToolArg { short: "node".into(), - backend: "node".into(), + ba: "node".into(), version: None, version_type: ToolVersionType::Version("latest".into()), tvr: None, @@ -213,7 +211,7 @@ mod tests { tool, ToolArg { short: "node".into(), - backend: "node".into(), + ba: "node".into(), version: Some("20".into()), version_type: ToolVersionType::Version("20".into()), tvr: Some(ToolRequest::new("node".into(), "20", ToolSource::Argument).unwrap()), @@ -230,7 +228,7 @@ mod tests { tool, ToolArg { short: "node".into(), - backend: "node".into(), + ba: "node".into(), version: Some("lts".into()), version_type: ToolVersionType::Version("lts".into()), tvr: Some(ToolRequest::new("node".into(), "lts", ToolSource::Argument).unwrap()), diff --git a/src/cli/asdf.rs b/src/cli/asdf.rs index 1f157ae5ac..be9c063d44 100644 --- a/src/cli/asdf.rs +++ b/src/cli/asdf.rs @@ -53,12 +53,12 @@ fn list_versions(config: &Config, args: &[String]) -> Result<()> { _ => None, }; if let Some(plugin) = plugin { - versions.retain(|(_, v)| &v.backend().to_string() == plugin); + versions.retain(|(_, v)| &v.ba().to_string() == plugin); for (_, version) in versions { miseprintln!("{}", version.version); } } else { - for (plugin, versions) in &versions.into_iter().chunk_by(|(_, v)| v.backend().clone()) { + for (plugin, versions) in &versions.into_iter().chunk_by(|(_, v)| v.ba().clone()) { miseprintln!("{}", plugin); for (_, tv) in versions { miseprintln!(" {}", tv.version); diff --git a/src/cli/backends/ls.rs b/src/cli/backends/ls.rs index a8067f6076..320a34ca65 100644 --- a/src/cli/backends/ls.rs +++ b/src/cli/backends/ls.rs @@ -1,6 +1,6 @@ +use crate::backend::backend_type::BackendType; use eyre::Result; - -use crate::backend::{self, BackendType}; +use strum::IntoEnumIterator; /// List built-in backends #[derive(Debug, clap::Args)] @@ -9,8 +9,8 @@ pub struct BackendsLs {} impl BackendsLs { pub fn run(self) -> Result<()> { - let mut backends = backend::list_backend_types(); - backends.retain(|f| *f != BackendType::Asdf); + let mut backends = BackendType::iter().collect::>(); + backends.retain(|f| !matches!(f, BackendType::Unknown)); for backend in backends { miseprintln!("{}", backend); diff --git a/src/cli/backends/snapshots/mise__cli__backends__ls__tests__backends_list.snap b/src/cli/backends/snapshots/mise__cli__backends__ls__tests__backends_list.snap index 16c4c0d65d..a81ad0bb7a 100644 --- a/src/cli/backends/snapshots/mise__cli__backends__ls__tests__backends_list.snap +++ b/src/cli/backends/snapshots/mise__cli__backends__ls__tests__backends_list.snap @@ -4,6 +4,7 @@ expression: output snapshot_kind: text --- aqua +asdf cargo core go diff --git a/src/cli/current.rs b/src/cli/current.rs index 9ccef55f7d..f4b2906562 100644 --- a/src/cli/current.rs +++ b/src/cli/current.rs @@ -1,7 +1,6 @@ use console::style; use eyre::{bail, Result}; -use crate::backend; use crate::backend::Backend; use crate::cli::args::BackendArg; use crate::config::Config; @@ -25,14 +24,13 @@ impl Current { let config = Config::try_get()?; let ts = ToolsetBuilder::new().build(&config)?; match &self.plugin { - Some(fa) => { - let backend = backend::get(fa); - if let Some(plugin) = backend.plugin() { + Some(ba) => { + if let Some(plugin) = ba.backend()?.plugin() { if !plugin.is_installed() { - bail!("Plugin {fa} is not installed"); + bail!("Plugin {ba} is not installed"); } } - self.one(ts, backend.as_ref()) + self.one(ts, ba.backend()?.as_ref()) } None => self.all(ts), } @@ -77,10 +75,10 @@ impl Current { } for tv in versions { if !plugin.is_version_installed(tv, true) { - let source = ts.versions.get(tv.backend()).unwrap().source.clone(); + let source = ts.versions.get(tv.ba()).unwrap().source.clone(); warn!( "{}@{} is specified in {}, but not installed", - &tv.backend(), + &tv.ba(), &tv.version, &source ); diff --git a/src/cli/doctor.rs b/src/cli/doctor.rs index 33cafc03b8..321fccf513 100644 --- a/src/cli/doctor.rs +++ b/src/cli/doctor.rs @@ -1,11 +1,6 @@ use crate::exit; -use console::{pad_str, style, Alignment}; -use indoc::formatdoc; -use itertools::Itertools; -use rayon::prelude::*; - -use crate::backend::BackendType; +use crate::backend::backend_type::BackendType; use crate::build_time::built_info; use crate::cli::version; use crate::cli::version::VERSION; @@ -18,6 +13,11 @@ use crate::shell::ShellType; use crate::toolset::{Toolset, ToolsetBuilder}; use crate::ui::{info, style}; use crate::{backend, cmd, dirs, duration, env, file, shims}; +use console::{pad_str, style, Alignment}; +use indoc::formatdoc; +use itertools::Itertools; +use rayon::prelude::*; +use strum::IntoEnumIterator; /// Check mise installation for possible problems #[derive(Debug, clap::Args)] @@ -197,7 +197,7 @@ impl Doctor { let is_core = CORE_PLUGINS.contains_key(plugin.id()); let plugin_type = plugin.get_plugin_type(); - if is_core && matches!(plugin_type, PluginType::Asdf | PluginType::Vfox) { + if is_core && matches!(plugin_type, Some(PluginType::Asdf | PluginType::Vfox)) { self.warnings .push(format!("plugin {} overrides a core plugin", &plugin.id())); } @@ -251,10 +251,7 @@ fn render_config_files(config: &Config) -> String { fn render_backends() -> String { let mut s = vec![]; - let backends = backend::list_backend_types() - .into_iter() - .filter(|f| *f != BackendType::Asdf); - for b in backends { + for b in BackendType::iter() { s.push(format!("{}", b)); } s.join("\n") @@ -276,11 +273,13 @@ fn render_plugins() -> String { .min(40); plugins .into_par_iter() + .filter(|b| b.plugin().is_some()) .map(|p| { - let padded_name = pad_str(p.id(), max_plugin_name_len, Alignment::Left, None); + let p = p.plugin().unwrap(); + let padded_name = pad_str(p.name(), max_plugin_name_len, Alignment::Left, None); let extra = match p.get_plugin_type() { PluginType::Asdf | PluginType::Vfox => { - let git = Git::new(dirs::PLUGINS.join(p.id())); + let git = Git::new(dirs::PLUGINS.join(p.name())); match git.get_remote_url() { Some(url) => { let sha = git @@ -290,8 +289,7 @@ fn render_plugins() -> String { } None => "".to_string(), } - } - PluginType::Core => "(core)".to_string(), + } // TODO: PluginType::Core => "(core)".to_string(), }; format!("{padded_name} {}", style::ndim(extra)) }) diff --git a/src/cli/external.rs b/src/cli/external.rs index c51776e4ba..6a03edba4f 100644 --- a/src/cli/external.rs +++ b/src/cli/external.rs @@ -21,13 +21,13 @@ pub fn commands() -> Vec { .collect() } -pub fn execute(fa: &BackendArg, args: &ArgMatches) -> Result<()> { +pub fn execute(ba: &BackendArg, args: &ArgMatches) -> Result<()> { if let Some(mut cmd) = commands() .into_iter() - .find(|c| c.get_name() == fa.to_string()) + .find(|c| c.get_name() == ba.to_string()) { if let Some((subcommand, matches)) = args.subcommand() { - let backend = backend::get(fa); + let backend = ba.backend()?; let args: Vec = matches .get_raw("args") .unwrap_or_default() diff --git a/src/cli/install.rs b/src/cli/install.rs index 58aa94c0e3..7db0db1e60 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -59,7 +59,7 @@ impl Install { fn install_runtimes(&self, config: &Config, runtimes: &[ToolArg]) -> Result> { let mpr = MultiProgressReport::get(); - let tools: HashSet = runtimes.iter().map(|ta| ta.backend.clone()).collect(); + let tools: HashSet = runtimes.iter().map(|ta| ta.ba.clone()).collect(); let mut ts = config.get_tool_request_set()?.filter_by_tool(&tools).into(); let tool_versions = self.get_requested_tool_versions(&ts, runtimes)?; if tool_versions.is_empty() { @@ -93,11 +93,10 @@ impl Install { for ta in ToolArg::double_tool_condition(runtimes)? { match ta.tvr { // user provided an explicit version - // TODO: this should install using options from config if the version matches Some(tv) => requests.push(tv), None => { if ta.tvr.is_none() { - match ts.versions.get(&ta.backend) { + match ts.versions.get(&ta.ba) { // the tool is in config so fetch the params from config // this may match multiple versions of one tool (e.g.: python) Some(tvl) => { @@ -109,7 +108,7 @@ impl Install { // so we default to @latest with no options None => { let tvr = ToolRequest::Version { - backend: ta.backend.clone(), + backend: ta.ba.clone(), version: "latest".into(), options: ta.opts.clone().unwrap_or(Default::default()), source: ToolSource::Argument, diff --git a/src/cli/latest.rs b/src/cli/latest.rs index ed1d8ccca5..62900634d6 100644 --- a/src/cli/latest.rs +++ b/src/cli/latest.rs @@ -1,6 +1,5 @@ use color_eyre::eyre::{bail, Result}; -use crate::backend::ABackend; use crate::cli::args::ToolArg; use crate::config::Config; use crate::toolset::ToolRequest; @@ -36,7 +35,7 @@ impl Latest { _ => bail!("invalid version: {}", self.tool.style()), }; - let backend: ABackend = self.tool.backend.into(); + let backend = self.tool.ba.backend()?; let mpr = MultiProgressReport::get(); if let Some(plugin) = backend.plugin() { plugin.ensure_installed(&mpr, false)?; diff --git a/src/cli/link.rs b/src/cli/link.rs index 06f6f38812..c165ff901d 100644 --- a/src/cli/link.rs +++ b/src/cli/link.rs @@ -45,7 +45,7 @@ impl Link { style(path.to_string_lossy()).cyan().for_stderr() ); } - let target = self.tool.backend.installs_path.join(version); + let target = self.tool.ba.installs_path.join(version); if target.exists() { if self.force { remove_all(&target)?; diff --git a/src/cli/ls.rs b/src/cli/ls.rs index 665d32d324..c7c2611e8c 100644 --- a/src/cli/ls.rs +++ b/src/cli/ls.rs @@ -14,10 +14,10 @@ use versions::Versioning; use crate::backend::Backend; use crate::cli::args::BackendArg; +use crate::config; use crate::config::Config; use crate::toolset::{ToolSource, ToolVersion, Toolset}; use crate::ui::table; -use crate::{backend, config}; /// List installed and active tool versions /// @@ -108,10 +108,9 @@ impl Ls { fn verify_plugin(&self) -> Result<()> { if let Some(plugins) = &self.plugin { - for fa in plugins { - let backend = backend::get(fa); - if let Some(plugin) = backend.plugin() { - ensure!(plugin.is_installed(), "{fa} is not installed"); + for ba in plugins { + if let Some(plugin) = ba.backend()?.plugin() { + ensure!(plugin.is_installed(), "{ba} is not installed"); } } } @@ -123,7 +122,7 @@ impl Ls { // only runtimes for 1 plugin let runtimes: Vec = runtimes .into_iter() - .filter(|(_, p, _, _)| plugins.contains(p.fa())) + .filter(|(_, p, _, _)| plugins.contains(p.ba())) .map(|row| row.into()) .collect(); miseprintln!("{}", serde_json::to_string_pretty(&runtimes)?); @@ -155,7 +154,7 @@ impl Ls { // only displaying 1 plugin so only show the version miseprintln!("{}", tv.version); } else { - miseprintln!("{} {}", tv.backend(), tv.version); + miseprintln!("{} {}", tv.ba(), tv.version); } } Ok(()) @@ -216,7 +215,7 @@ impl Ls { let rvs: Vec = versions .into_iter() .filter(|(_, (f, _))| match &self.plugin { - Some(p) => p.contains(f.fa()), + Some(p) => p.contains(f.ba()), None => true, }) .sorted_by_cached_key(|((plugin_name, version), _)| { @@ -228,7 +227,7 @@ impl Ls { }) .map(|(k, (p, tv))| { let source = match &active.get(&k) { - Some((_, tv)) => ts.versions.get(tv.backend()).map(|tv| tv.source.clone()), + Some((_, tv)) => ts.versions.get(tv.ba()).map(|tv| tv.source.clone()), None => None, }; (self, p, tv, source) @@ -236,7 +235,7 @@ impl Ls { // if it isn't installed and it's not specified, don't show it .filter(|(_ls, p, tv, source)| source.is_some() || p.is_version_installed(tv, true)) .filter(|(_ls, p, _, _)| match &self.plugin { - Some(backend) => backend.contains(p.fa()), + Some(backend) => backend.contains(p.ba()), None => true, }) .collect(); @@ -494,7 +493,10 @@ mod tests { fn test_ls_missing_plugin() { reset(); let err = assert_cli_err!("ls", "missing-plugin"); - assert_str_eq!(err.to_string(), r#"missing-plugin is not installed"#); + assert_str_eq!( + err.to_string(), + r#"missing-plugin not found in mise tool registry"# + ); } #[test] diff --git a/src/cli/ls_remote.rs b/src/cli/ls_remote.rs index 89215049ca..2087f40053 100644 --- a/src/cli/ls_remote.rs +++ b/src/cli/ls_remote.rs @@ -90,7 +90,7 @@ impl LsRemote { fn get_plugin(&self) -> Result>> { match &self.plugin { Some(tool_arg) => { - let backend = backend::get(&tool_arg.backend); + let backend = tool_arg.ba.backend()?; let mpr = MultiProgressReport::get(); if let Some(plugin) = backend.plugin() { plugin.ensure_installed(&mpr, false)?; diff --git a/src/cli/outdated.rs b/src/cli/outdated.rs index f8ac0ea8df..bc611102fb 100644 --- a/src/cli/outdated.rs +++ b/src/cli/outdated.rs @@ -45,7 +45,7 @@ impl Outdated { let tool_set = self .tool .iter() - .map(|t| t.backend.clone()) + .map(|t| t.ba.clone()) .collect::>(); ts.versions .retain(|_, tvl| tool_set.is_empty() || tool_set.contains(&tvl.backend)); diff --git a/src/cli/plugins/ls.rs b/src/cli/plugins/ls.rs index d15f1334f7..a9dfe8c252 100644 --- a/src/cli/plugins/ls.rs +++ b/src/cli/plugins/ls.rs @@ -1,12 +1,12 @@ use eyre::Result; use rayon::prelude::*; +use std::collections::BTreeMap; use tabled::{Table, Tabled}; use crate::config::Config; -use crate::plugins; -use crate::plugins::asdf_plugin::AsdfPlugin; use crate::plugins::PluginType; use crate::registry::full_to_url; +use crate::toolset::install_state; use crate::ui::table; /// List installed plugins @@ -45,25 +45,33 @@ pub struct PluginsLs { impl PluginsLs { pub fn run(self, config: &Config) -> Result<()> { - let mut tools = plugins::list2()?; + let mut plugins: BTreeMap<_, _> = install_state::list_plugins()? + .into_iter() + .map(|(k, p)| (k, (p, None))) + .collect(); if self.all { - for (plugin, backends) in config.get_shorthands() { + for (name, backends) in config.get_shorthands() { for full in backends { - let mut ep = AsdfPlugin::new(plugin.to_string()); - ep.repo_url = Some(full_to_url(full)); - tools.insert(ep.name.clone(), Box::new(ep)); + let plugin_type = PluginType::from_full(full)?; + plugins.insert(name.clone(), (plugin_type, Some(full_to_url(full)))); } } - } else if self.user && self.core { - } else if self.core { - tools.retain(|_, p| matches!(p.get_plugin_type(), PluginType::Core)); - } else { - tools.retain(|_, p| matches!(p.get_plugin_type(), PluginType::Asdf | PluginType::Vfox)); } + let plugins = plugins + .into_iter() + .map(|(short, (pt, url))| { + let mut plugin = pt.plugin(short.clone()); + if let Some(url) = url { + plugin.set_remote_url(url); + } + (short, plugin) + }) + .collect::>(); + if self.urls || self.refs { - let data = tools + let data = plugins .into_par_iter() .map(|(name, p)| { let remote_url = p.get_remote_url().unwrap_or_else(|e| { @@ -96,7 +104,7 @@ impl PluginsLs { miseprintln!("{table}"); } else { hint!("registry", "see available plugins with", "mise registry"); - for tool in tools.values() { + for tool in plugins.values() { miseprintln!("{tool}"); } } diff --git a/src/cli/plugins/ls_remote.rs b/src/cli/plugins/ls_remote.rs index 798f899afa..d5c49f86cd 100644 --- a/src/cli/plugins/ls_remote.rs +++ b/src/cli/plugins/ls_remote.rs @@ -1,11 +1,9 @@ -use std::collections::HashSet; - use console::{measure_text_width, pad_str, Alignment}; use eyre::Result; use itertools::Itertools; use crate::config::Config; -use crate::plugins; +use crate::toolset::install_state; /// List all available remote plugins #[derive(Debug, clap::Args)] @@ -24,11 +22,7 @@ pub struct PluginsLsRemote { impl PluginsLsRemote { pub fn run(self, config: &Config) -> Result<()> { - let installed_plugins = plugins::list() - .into_iter() - .filter(|b| b.plugin().is_some_and(|p| p.is_installed())) - .map(|p| p.id().to_string()) - .collect::>(); + let installed_plugins = install_state::list_plugins()?; let shorthands = config.get_shorthands().iter().sorted().collect_vec(); let max_plugin_len = shorthands @@ -43,11 +37,12 @@ impl PluginsLsRemote { for (plugin, backends) in shorthands { for repo in backends { - let installed = if !self.only_names && installed_plugins.contains(plugin.as_str()) { - "*" - } else { - " " - }; + let installed = + if !self.only_names && installed_plugins.contains_key(plugin.as_str()) { + "*" + } else { + " " + }; let url = if self.urls { repo } else { "" }; let plugin = pad_str(plugin, max_plugin_len, Alignment::Left, None); miseprintln!("{} {}{}", plugin, installed, url); diff --git a/src/cli/plugins/uninstall.rs b/src/cli/plugins/uninstall.rs index 24b3cbf27d..6cef2199d8 100644 --- a/src/cli/plugins/uninstall.rs +++ b/src/cli/plugins/uninstall.rs @@ -1,9 +1,10 @@ use eyre::Result; use crate::backend::unalias_backend; -use crate::plugins; +use crate::toolset::install_state; use crate::ui::multi_progress_report::MultiProgressReport; use crate::ui::style; +use crate::{backend, plugins}; /// Removes a plugin #[derive(Debug, clap::Args)] @@ -27,10 +28,7 @@ impl PluginsUninstall { let mpr = MultiProgressReport::get(); let plugins = match self.all { - true => plugins::list() - .into_iter() - .map(|p| p.id().to_string()) - .collect(), + true => install_state::list_plugins()?.keys().cloned().collect(), false => self.plugin.clone(), }; @@ -42,15 +40,19 @@ impl PluginsUninstall { } fn uninstall_one(&self, plugin_name: &str, mpr: &MultiProgressReport) -> Result<()> { - let backend = plugins::get(plugin_name); - if let Some(plugin) = backend.plugin() { - let prefix = format!("plugin:{}", style::eblue(&backend.name())); - let pr = mpr.add(&prefix); - plugin.uninstall(pr.as_ref())?; - if self.purge { - backend.purge(pr.as_ref())?; + if let Ok(plugin) = plugins::get(plugin_name) { + if plugin.is_installed() { + let prefix = format!("plugin:{}", style::eblue(&plugin.name())); + let pr = mpr.add(&prefix); + plugin.uninstall(pr.as_ref())?; + if self.purge { + let backend = backend::get(&plugin_name.into()).unwrap(); + backend.purge(pr.as_ref())?; + } + pr.finish_with_message("uninstalled".into()); + } else { + warn!("{} is not installed", style::eblue(plugin_name)); } - pr.finish_with_message("uninstalled".into()); } else { warn!("{} is not installed", style::eblue(plugin_name)); } diff --git a/src/cli/plugins/update.rs b/src/cli/plugins/update.rs index 0980b30036..04649e8f3c 100644 --- a/src/cli/plugins/update.rs +++ b/src/cli/plugins/update.rs @@ -4,6 +4,7 @@ use rayon::prelude::*; use crate::config::Settings; use crate::plugins; +use crate::toolset::install_state; use crate::ui::multi_progress_report::MultiProgressReport; /// Updates a plugin to the latest version @@ -27,17 +28,13 @@ impl Update { let plugins: Vec<_> = match self.plugin { Some(plugins) => plugins .into_iter() - .map(|p| { - let (p, ref_) = match p.split_once('#') { - Some((p, ref_)) => (p, Some(ref_.to_string())), - None => (p.as_str(), None), - }; - let plugin = plugins::get(p); - Ok((plugin.clone(), ref_)) + .map(|p| match p.split_once('#') { + Some((p, ref_)) => (p.to_string(), Some(ref_.to_string())), + None => (p, None), }) - .collect::>()?, - None => plugins::list_external() - .into_iter() + .collect(), + None => install_state::list_plugins()? + .into_keys() .map(|p| (p, None)) .collect::>(), }; @@ -50,14 +47,13 @@ impl Update { .install(|| { plugins .into_par_iter() - .map(|(backend, ref_)| { - let prefix = format!("plugin:{}", style(backend.id()).blue().for_stderr()); + .map(|(short, ref_)| { + let plugin = plugins::get(&short)?; + let prefix = format!("plugin:{}", style(plugin.name()).blue().for_stderr()); let pr = mpr.add(&prefix); - if let Some(plugin) = backend.plugin() { - plugin - .update(pr.as_ref(), ref_) - .wrap_err_with(|| format!("[{backend}] plugin update"))?; - } + plugin + .update(pr.as_ref(), ref_) + .wrap_err_with(|| format!("[{plugin}] plugin update"))?; Ok(()) }) .filter_map(|r| r.err()) @@ -84,23 +80,3 @@ static AFTER_LONG_HELP: &str = color_print::cstr!( $ mise plugins update node#beta # specify a ref "# ); - -#[cfg(test)] -mod tests { - use test_log::test; - - use crate::test::reset; - - #[test] - fn test_plugin_update() { - reset(); - assert_cli!( - "plugin", - "install", - "tiny", - "https://github.com/mise-plugins/rtx-tiny.git" - ); - // assert_cli!("p", "update"); tested in e2e - assert_cli!("plugins", "update", "tiny"); - } -} diff --git a/src/cli/prune.rs b/src/cli/prune.rs index a1c21220f6..795a0127d0 100644 --- a/src/cli/prune.rs +++ b/src/cli/prune.rs @@ -72,7 +72,7 @@ impl Prune { .collect::, ToolVersion)>>(); if let Some(backends) = &self.plugin { - to_delete.retain(|_, (_, tv)| backends.contains(tv.backend())); + to_delete.retain(|_, (_, tv)| backends.contains(tv.ba())); } for cf in config.get_tracked_config_files()?.values() { diff --git a/src/cli/registry.rs b/src/cli/registry.rs index c2fc3b7eeb..257a851e3e 100644 --- a/src/cli/registry.rs +++ b/src/cli/registry.rs @@ -1,4 +1,4 @@ -use crate::backend::BackendType; +use crate::backend::backend_type::BackendType; use crate::plugins::core::CORE_PLUGINS; use crate::registry::REGISTRY; use crate::ui::table; diff --git a/src/cli/shell.rs b/src/cli/shell.rs index 642eee946b..d18b4e2be7 100644 --- a/src/cli/shell.rs +++ b/src/cli/shell.rs @@ -56,7 +56,7 @@ impl Shell { let shell = get_shell(None).expect("no shell detected"); for (p, tv) in ts.list_current_installed_versions() { - let source = &ts.versions.get(p.fa()).unwrap().source; + let source = &ts.versions.get(p.ba()).unwrap().source; if matches!(source, ToolSource::Argument) { let k = format!("MISE_{}_VERSION", p.id().to_uppercase()); let op = if self.unset { diff --git a/src/cli/snapshots/mise__cli__latest__tests__latest_missing_plugin.snap b/src/cli/snapshots/mise__cli__latest__tests__latest_missing_plugin.snap index bb6ee06c81..a604de5eb4 100644 --- a/src/cli/snapshots/mise__cli__latest__tests__latest_missing_plugin.snap +++ b/src/cli/snapshots/mise__cli__latest__tests__latest_missing_plugin.snap @@ -3,4 +3,4 @@ source: src/cli/latest.rs expression: stdout snapshot_kind: text --- -No repository found for plugin invalid_plugin +invalid_plugin not found in mise tool registry diff --git a/src/cli/sync/node.rs b/src/cli/sync/node.rs index 52fb017dae..a97985159b 100644 --- a/src/cli/sync/node.rs +++ b/src/cli/sync/node.rs @@ -5,7 +5,7 @@ use itertools::sorted; use crate::config::Config; use crate::env::{NODENV_ROOT, NVM_DIR}; -use crate::{cmd, dirs, file, plugins}; +use crate::{backend, cmd, dirs, file}; /// Symlinks all tool versions from an external tool into mise /// @@ -47,7 +47,7 @@ impl SyncNode { } fn run_brew(self, config: &Config) -> Result<()> { - let tool = plugins::get("node"); + let node = backend::get(&"node".into()).unwrap(); let brew_prefix = PathBuf::from(cmd!("brew", "--prefix").read()?).join("opt"); let installed_versions_path = dirs::INSTALLS.join("node"); @@ -60,7 +60,7 @@ impl SyncNode { continue; } let v = entry.trim_start_matches("node@"); - tool.create_symlink(v, &brew_prefix.join(&entry))?; + node.create_symlink(v, &brew_prefix.join(&entry))?; miseprintln!("Synced node@{} from Homebrew", v); } @@ -68,7 +68,7 @@ impl SyncNode { } fn run_nvm(self, config: &Config) -> Result<()> { - let tool = plugins::get("node"); + let node = backend::get(&"node".into()).unwrap(); let nvm_versions_path = NVM_DIR.join("versions").join("node"); let installed_versions_path = dirs::INSTALLS.join("node"); @@ -83,7 +83,7 @@ impl SyncNode { let subdirs = file::dir_subdirs(&nvm_versions_path)?; for entry in sorted(subdirs) { let v = entry.trim_start_matches('v'); - let symlink = tool.create_symlink(v, &nvm_versions_path.join(&entry))?; + let symlink = node.create_symlink(v, &nvm_versions_path.join(&entry))?; if let Some(symlink) = symlink { created.push(symlink); miseprintln!("Synced node@{} from nvm", v); @@ -99,7 +99,7 @@ impl SyncNode { } fn run_nodenv(self, config: &Config) -> Result<()> { - let tool = plugins::get("node"); + let node = backend::get(&"node".into()).unwrap(); let nodenv_versions_path = NODENV_ROOT.join("versions"); let installed_versions_path = dirs::INSTALLS.join("node"); @@ -108,7 +108,7 @@ impl SyncNode { let subdirs = file::dir_subdirs(&nodenv_versions_path)?; for v in sorted(subdirs) { - tool.create_symlink(&v, &nodenv_versions_path.join(&v))?; + node.create_symlink(&v, &nodenv_versions_path.join(&v))?; miseprintln!("Synced node@{} from nodenv", v); } diff --git a/src/cli/sync/python.rs b/src/cli/sync/python.rs index 62989b240b..d7dea270f3 100644 --- a/src/cli/sync/python.rs +++ b/src/cli/sync/python.rs @@ -3,7 +3,7 @@ use itertools::sorted; use crate::config::Config; use crate::env::PYENV_ROOT; -use crate::{dirs, file, plugins}; +use crate::{backend, dirs, file}; /// Symlinks all tool versions from an external tool into mise /// @@ -19,7 +19,7 @@ pub struct SyncPython { impl SyncPython { pub fn run(self) -> Result<()> { let config = Config::try_get()?; - let python = plugins::get("python"); + let python = backend::get(&"python".into()).unwrap(); let pyenv_versions_path = PYENV_ROOT.join("versions"); let installed_python_versions_path = dirs::INSTALLS.join("python"); diff --git a/src/cli/uninstall.rs b/src/cli/uninstall.rs index 5e9c5049d4..83c4d9cc7e 100644 --- a/src/cli/uninstall.rs +++ b/src/cli/uninstall.rs @@ -10,7 +10,7 @@ use crate::cli::args::ToolArg; use crate::config::Config; use crate::toolset::{ToolRequest, ToolSource, ToolVersion, ToolsetBuilder}; use crate::ui::multi_progress_report::MultiProgressReport; -use crate::{backend, dirs, file, lockfile, runtime_symlinks, shims}; +use crate::{dirs, file, lockfile, runtime_symlinks, shims}; /// Removes installed tool versions /// @@ -41,8 +41,7 @@ impl Uninstall { }; let tool_versions = tool_versions .into_iter() - .unique() - .sorted() + .unique_by(|(_, tv)| (tv.request.ba().short.clone(), tv.version.clone())) .collect::>(); if !self.all && tool_versions.len() > 1 { bail!("multiple tools specified, use --all to uninstall all versions"); @@ -91,10 +90,10 @@ impl Uninstall { let runtimes = ToolArg::double_tool_condition(&self.installed_tool)?; let tool_versions = runtimes .into_par_iter() - .map(|a| { - let tool = backend::get(&a.backend); - let query = a.tvr.as_ref().map(|tvr| tvr.version()).unwrap_or_default(); - let installed_versions = tool.list_installed_versions()?; + .map(|ta| { + let backend = ta.ba.backend()?; + let query = ta.tvr.as_ref().map(|tvr| tvr.version()).unwrap_or_default(); + let installed_versions = backend.list_installed_versions()?; let exact_match = installed_versions.iter().find(|v| v == &&query); let matches = match exact_match { Some(m) => vec![m], @@ -106,16 +105,19 @@ impl Uninstall { let mut tvs = matches .into_iter() .map(|v| { - let tvr = ToolRequest::new(tool.fa().clone(), v, ToolSource::Unknown)?; + let tvr = ToolRequest::new(backend.ba().clone(), v, ToolSource::Unknown)?; let tv = ToolVersion::new(tvr, v.into()); - Ok((tool.clone(), tv)) + Ok((backend.clone(), tv)) }) .collect::>>()?; - if let Some(tvr) = &a.tvr { - tvs.push((tool.clone(), tvr.resolve(&Default::default())?)); + if let Some(tvr) = &ta.tvr { + tvs.push((backend.clone(), tvr.resolve(&Default::default())?)); } if tvs.is_empty() { - warn!("no versions found for {}", style(&tool).blue().for_stderr()); + warn!( + "no versions found for {}", + style(&backend).blue().for_stderr() + ); } Ok(tvs) }) diff --git a/src/cli/upgrade.rs b/src/cli/upgrade.rs index e39361cfaf..91fb026673 100644 --- a/src/cli/upgrade.rs +++ b/src/cli/upgrade.rs @@ -61,11 +61,7 @@ impl Upgrade { if self.interactive && !outdated.is_empty() { outdated = self.get_interactive_tool_set(&outdated)?; } else if !self.tool.is_empty() { - outdated.retain(|o| { - self.tool - .iter() - .any(|t| &t.backend == o.tool_version.backend()) - }); + outdated.retain(|o| self.tool.iter().any(|t| &t.ba == o.tool_version.ba())); } if outdated.is_empty() { info!("All tools are up to date"); @@ -104,7 +100,7 @@ impl Upgrade { }) .filter(|(o, _bump, cf)| { if let Ok(trs) = cf.to_tool_request_set() { - if let Some(versions) = trs.tools.get(o.tool_request.backend()) { + if let Some(versions) = trs.tools.get(o.tool_request.ba()) { if versions.len() != 1 { warn!("upgrading multiple versions with --bump is not yet supported"); return false; @@ -150,10 +146,7 @@ impl Upgrade { let versions = ts.install_versions(config, new_versions, &mpr, &opts)?; for (o, bump, mut cf) in config_file_updates { - cf.replace_versions( - o.tool_request.backend(), - &[(bump, o.tool_request.options())], - )?; + cf.replace_versions(o.tool_request.ba(), &[(bump, o.tool_request.options())])?; cf.save()?; } @@ -170,7 +163,7 @@ impl Upgrade { } fn uninstall_old_version(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> { - tv.get_backend() + tv.backend()? .uninstall_version(tv, pr, self.dry_run) .wrap_err_with(|| format!("failed to uninstall {tv}"))?; pr.finish(); diff --git a/src/cli/use.rs b/src/cli/use.rs index a400bc9301..4f448041bc 100644 --- a/src/cli/use.rs +++ b/src/cli/use.rs @@ -98,7 +98,7 @@ impl Use { .cloned() .map(|t| match t.tvr { Some(tvr) => Ok(tvr), - None => ToolRequest::new(t.backend, "latest", ToolSource::Argument), + None => ToolRequest::new(t.ba, "latest", ToolSource::Argument), }) .collect::>()?; let mut versions = ts.install_versions( @@ -116,7 +116,7 @@ impl Use { let mut cf = self.get_config_file()?; let pin = self.pin || !self.fuzzy && (SETTINGS.pin || SETTINGS.asdf_compat); - for (fa, tvl) in &versions.iter().chunk_by(|tv| tv.backend()) { + for (fa, tvl) in &versions.iter().chunk_by(|tv| tv.ba()) { let versions: Vec<_> = tvl .into_iter() .map(|tv| { @@ -178,13 +178,13 @@ impl Use { fn warn_if_hidden(&self, config: &Config, global: &Path) { let ts = ToolsetBuilder::new().build(config).unwrap_or_default(); let warn = |targ: &ToolArg, p| { - let plugin = &targ.backend; + let plugin = &targ.ba; let p = display_path(p); let global = display_path(global); warn!("{plugin} is defined in {p} which overrides the global config ({global})"); }; for targ in &self.tool { - if let Some(tv) = ts.versions.get(&targ.backend) { + if let Some(tv) = ts.versions.get(&targ.ba) { if let ToolSource::MiseToml(p) | ToolSource::ToolVersions(p) = &tv.source { if !file::same_file(p, global) { warn(targ, p); diff --git a/src/cli/where.rs b/src/cli/where.rs index 01a22dbc38..785e67d55f 100644 --- a/src/cli/where.rs +++ b/src/cli/where.rs @@ -1,6 +1,5 @@ use eyre::Result; -use crate::backend; use crate::cli::args::ToolArg; use crate::config::Config; use crate::errors::Error::VersionNotInstalled; @@ -37,15 +36,15 @@ impl Where { None => { let ts = ToolsetBuilder::new().build(&config)?; ts.versions - .get(&self.tool.backend) + .get(&self.tool.ba) .and_then(|tvr| tvr.requests.first().cloned()) .unwrap_or_else(|| self.tool.with_version("latest").tvr.unwrap()) } }, }; - let ba = tvr.backend(); - let backend = backend::get(ba); + let ba = tvr.ba(); + let backend = ba.backend()?; let tv = tvr.resolve(&Default::default())?; if backend.is_version_installed(&tv, true) { diff --git a/src/config/config_file/legacy_version.rs b/src/config/config_file/legacy_version.rs index 0465e7d1ad..8f79722bab 100644 --- a/src/config/config_file/legacy_version.rs +++ b/src/config/config_file/legacy_version.rs @@ -28,7 +28,7 @@ impl LegacyVersionFile { for plugin in plugins { let version = plugin.parse_legacy_file(&path)?; for version in version.split_whitespace() { - let tr = ToolRequest::new(plugin.fa().clone(), version, source.clone())?; + let tr = ToolRequest::new(plugin.ba().clone(), version, source.clone())?; tools.add_version(tr, &source); } } @@ -82,8 +82,4 @@ impl ConfigFile for LegacyVersionFile { fn to_tool_request_set(&self) -> Result { Ok(self.tools.clone()) } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } } diff --git a/src/config/config_file/mise_toml.rs b/src/config/config_file/mise_toml.rs index 3f5f2a8225..124f0be3c6 100644 --- a/src/config/config_file/mise_toml.rs +++ b/src/config/config_file/mise_toml.rs @@ -293,13 +293,13 @@ impl ConfigFile for MiseToml { fn replace_versions( &mut self, - fa: &BackendArg, + ba: &BackendArg, versions: &[(String, ToolVersionOptions)], ) -> eyre::Result<()> { - let existing = self.tools.entry(fa.clone()).or_default(); + let existing = self.tools.entry(ba.clone()).or_default(); let output_empty_opts = |opts: &ToolVersionOptions| { if let Some(reg_ba) = REGISTRY_BACKEND_MAP - .get(fa.short.as_str()) + .get(ba.short.as_str()) .and_then(|b| b.first()) { if reg_ba.opts.as_ref().is_some_and(|o| o == opts) { @@ -328,11 +328,11 @@ impl ConfigFile for MiseToml { .unwrap(); // create a key from the short name preserving any decorations like prefix/suffix if the key already exists - let key = get_key_with_decor(tools, fa.short.as_str()); + let key = get_key_with_decor(tools, ba.short.as_str()); // if a short name is used like "node", make sure we remove any long names like "core:node" - if fa.short != fa.full { - tools.remove(&fa.full.to_string()); + if ba.short != ba.full() { + tools.remove(&ba.full()); } if versions.len() == 1 { @@ -434,10 +434,6 @@ impl ConfigFile for MiseToml { fn task_config(&self) -> &TaskConfig { &self.task_config } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } } /// Returns a [`toml_edit::Key`] from the given `key`. diff --git a/src/config/config_file/mod.rs b/src/config/config_file/mod.rs index b8e20c5848..cce34be41b 100644 --- a/src/config/config_file/mod.rs +++ b/src/config/config_file/mod.rs @@ -90,7 +90,6 @@ pub trait ConfigFile: Debug + Send + Sync { static DEFAULT_TASK_CONFIG: Lazy = Lazy::new(TaskConfig::default); &DEFAULT_TASK_CONFIG } - fn clone_box(&self) -> Box; } impl dyn ConfigFile { @@ -102,7 +101,7 @@ impl dyn ConfigFile { for ta in tools { if let Some(tv) = &ta.tvr { plugins_to_update - .entry(ta.backend.clone()) + .entry(ta.ba.clone()) .or_insert_with(Vec::new) .push(tv); } @@ -142,7 +141,7 @@ impl dyn ConfigFile { pub fn display_runtime(&self, runtimes: &[ToolArg]) -> eyre::Result { // in this situation we just print the current version in the config file if runtimes.len() == 1 && runtimes[0].tvr.is_none() { - let fa = &runtimes[0].backend; + let fa = &runtimes[0].ba; let tvl = self .to_toolset()? .versions diff --git a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-3.snap b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-3.snap index 3c7f93d94c..19a5c126dd 100644 --- a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-3.snap +++ b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__fixture-3.snap @@ -5,9 +5,9 @@ snapshot_kind: text --- ToolRequestSet { tools: { - BackendArg("terraform"): [ + BackendArg("terraform" -> "asdf:asdf-community/asdf-hashicorp"): [ Version { - backend: BackendArg("terraform"), + backend: BackendArg("terraform" -> "asdf:asdf-community/asdf-hashicorp"), version: "1.0.0", options: {}, source: MiseToml( @@ -15,9 +15,9 @@ ToolRequestSet { ), }, ], - BackendArg("node"): [ + BackendArg("node" -> "core:node"): [ Version { - backend: BackendArg("node"), + backend: BackendArg("node" -> "core:node"), version: "18", options: {}, source: MiseToml( @@ -25,7 +25,7 @@ ToolRequestSet { ), }, Prefix { - backend: BackendArg("node"), + backend: BackendArg("node" -> "core:node"), prefix: "20", options: {}, source: MiseToml( @@ -33,7 +33,7 @@ ToolRequestSet { ), }, Ref { - backend: BackendArg("node"), + backend: BackendArg("node" -> "core:node"), ref_: "master", ref_type: "ref", options: {}, @@ -42,16 +42,16 @@ ToolRequestSet { ), }, Path( - BackendArg("node"), + BackendArg("node" -> "core:node"), "~/.nodes/18", MiseToml( "~/fixtures/.mise.toml", ), ), ], - BackendArg("jq"): [ + BackendArg("jq" -> "asdf:mise-plugins/asdf-jq"): [ Prefix { - backend: BackendArg("jq"), + backend: BackendArg("jq" -> "asdf:mise-plugins/asdf-jq"), prefix: "1.6", options: {}, source: MiseToml( @@ -69,9 +69,9 @@ ToolRequestSet { ), }, ], - BackendArg("python"): [ + BackendArg("python" -> "core:python"): [ Version { - backend: BackendArg("python"), + backend: BackendArg("python" -> "core:python"), version: "3.10.0", options: { "venv": ".venv", @@ -81,7 +81,7 @@ ToolRequestSet { ), }, Version { - backend: BackendArg("python"), + backend: BackendArg("python" -> "core:python"), version: "3.9.0", options: {}, source: MiseToml( @@ -91,19 +91,19 @@ ToolRequestSet { ], }, sources: { - BackendArg("jq"): MiseToml( + BackendArg("jq" -> "asdf:mise-plugins/asdf-jq"): MiseToml( "~/fixtures/.mise.toml", ), - BackendArg("node"): MiseToml( + BackendArg("node" -> "core:node"): MiseToml( "~/fixtures/.mise.toml", ), - BackendArg("python"): MiseToml( + BackendArg("python" -> "core:python"): MiseToml( "~/fixtures/.mise.toml", ), BackendArg("shellcheck" -> "ubi:koalaman/shellcheck"): MiseToml( "~/fixtures/.mise.toml", ), - BackendArg("terraform"): MiseToml( + BackendArg("terraform" -> "asdf:asdf-community/asdf-hashicorp"): MiseToml( "~/fixtures/.mise.toml", ), }, diff --git a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap index 0400df6f43..99ee109f18 100644 --- a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap +++ b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap @@ -5,12 +5,12 @@ snapshot_kind: text --- Toolset { versions: { - BackendArg("node"): ToolVersionList { - backend: BackendArg("node"), + BackendArg("node" -> "core:node"): ToolVersionList { + backend: BackendArg("node" -> "core:node"), versions: [], requests: [ Version { - backend: BackendArg("node"), + backend: BackendArg("node" -> "core:node"), version: "16.0.1", options: {}, source: MiseToml( @@ -18,7 +18,7 @@ Toolset { ), }, Version { - backend: BackendArg("node"), + backend: BackendArg("node" -> "core:node"), version: "18.0.1", options: {}, source: MiseToml( diff --git a/src/config/config_file/tool_versions.rs b/src/config/config_file/tool_versions.rs index 458986c13f..935b8b460a 100644 --- a/src/config/config_file/tool_versions.rs +++ b/src/config/config_file/tool_versions.rs @@ -102,7 +102,7 @@ impl ToolVersions { // note that this method will cause the colons to be removed // permanently if saving the file again, but I think that's fine let orig_plugin = plugin.trim_end_matches(':'); - let fa = orig_plugin.into(); + let ba = orig_plugin.into(); let tvp = ToolVersionPlugin { orig_name: orig_plugin.to_string(), @@ -112,7 +112,7 @@ impl ToolVersions { _ => [" #", post, "\n"].join(""), }, }; - plugins.insert(fa, tvp); + plugins.insert(ba, tvp); } } plugins @@ -126,9 +126,9 @@ impl ToolVersions { fn populate_toolset(&mut self) -> eyre::Result<()> { let source = ToolSource::ToolVersions(self.path.clone()); - for (plugin, tvp) in &self.plugins { + for (ba, tvp) in &self.plugins { for version in &tvp.versions { - let tvr = ToolRequest::new(plugin.clone(), version, source.clone())?; + let tvr = ToolRequest::new(ba.clone(), version, source.clone())?; self.tools.add_version(tvr, &source) } } @@ -212,10 +212,6 @@ impl ConfigFile for ToolVersions { fn to_tool_request_set(&self) -> eyre::Result { Ok(self.tools.clone()) } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } } #[cfg(test)] diff --git a/src/config/env_directive.rs b/src/config/env_directive.rs index 427f64fdd6..ce8a700949 100644 --- a/src/config/env_directive.rs +++ b/src/config/env_directive.rs @@ -262,7 +262,7 @@ impl EnvResults { if ts .list_missing_versions() .iter() - .any(|tv| tv.backend().name == "python") + .any(|tv| tv.ba().tool_name == "python") { debug!("python not installed, skipping venv creation"); } else { diff --git a/src/config/mod.rs b/src/config/mod.rs index 573d377189..125d6e3763 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -22,7 +22,7 @@ use crate::config::tracking::Tracker; use crate::file::display_path; use crate::shorthands::{get_shorthands, Shorthands}; use crate::task::Task; -use crate::toolset::{ToolRequestSet, ToolRequestSetBuilder}; +use crate::toolset::{install_state, ToolRequestSet, ToolRequestSetBuilder}; use crate::ui::style; use crate::{backend, dirs, env, file, registry}; @@ -31,6 +31,7 @@ mod env_directive; pub mod settings; pub mod tracking; +use crate::plugins::PluginType; pub use settings::SETTINGS; type AliasMap = IndexMap; @@ -41,10 +42,10 @@ type EnvWithSources = IndexMap; pub struct Config { pub config_files: ConfigMap, pub project_root: Option, + pub all_aliases: AliasMap, aliases: AliasMap, env: OnceCell, env_with_sources: OnceCell, - all_aliases: OnceLock, repo_urls: HashMap, shorthands: OnceLock, tasks: OnceCell>, @@ -92,7 +93,7 @@ impl Config { let config_files = load_all_config_files(&config_paths, &legacy_files)?; time!("load config_files"); - let config = Self { + let mut config = Self { aliases: load_aliases(&config_files)?, project_root: get_project_root(&config_files), repo_urls: load_plugins(&config_files)?, @@ -104,6 +105,9 @@ impl Config { config.validate()?; time!("load validate"); + config.all_aliases = config.load_all_aliases(); + time!("load all aliases"); + if log::log_enabled!(log::Level::Trace) { trace!("config: {config:#?}"); } else if log::log_enabled!(log::Level::Debug) { @@ -113,6 +117,14 @@ impl Config { } time!("load done"); + for (plugin, url) in &config.repo_urls { + let plugin_type = match url.contains("vfox-") { + true => PluginType::Vfox, + false => PluginType::Asdf, + }; + install_state::add_plugin(plugin, plugin_type)?; + } + Ok(config) } pub fn env_maybe(&self) -> Option> { @@ -163,20 +175,30 @@ impl Config { .get_or_try_init(|| ToolRequestSetBuilder::new().build()) } - pub fn get_repo_url(&self, plugin_name: &String) -> Option { + pub fn get_repo_url(&self, plugin_name: &str) -> Option { + let plugin_name = self + .all_aliases + .get(plugin_name) + .and_then(|a| a.full.clone()) + .unwrap_or(plugin_name.to_string()); + let plugin_name = plugin_name.strip_prefix("asdf:").unwrap_or(&plugin_name); + let plugin_name = plugin_name.strip_prefix("vfox:").unwrap_or(plugin_name); match self.repo_urls.get(plugin_name) { Some(url) => Some(url.to_string()), None => self .get_shorthands() .get(plugin_name) - .map(|full| registry::full_to_url(&full[0])), + .map(|full| registry::full_to_url(&full[0])) + .or_else(|| { + if plugin_name.starts_with("https://") || plugin_name.split('/').count() == 2 { + Some(registry::full_to_url(plugin_name)) + } else { + None + } + }), } } - pub fn get_all_aliases(&self) -> &AliasMap { - self.all_aliases.get_or_init(|| self.load_all_aliases()) - } - pub fn tasks(&self) -> Result<&BTreeMap> { self.tasks.get_or_try_init(|| self.load_all_tasks()) } @@ -196,7 +218,7 @@ impl Config { } pub fn resolve_alias(&self, backend: &ABackend, v: &str) -> Result { - if let Some(plugin_aliases) = self.get_all_aliases().get(&backend.fa().short) { + if let Some(plugin_aliases) = self.all_aliases.get(&backend.ba().short) { if let Some(alias) = plugin_aliases.versions.get(v) { return Ok(alias.clone()); } @@ -216,13 +238,13 @@ impl Config { warn!("get_aliases: {err}"); BTreeMap::new() }); - (backend.fa().clone(), aliases) + (backend.ba().clone(), aliases) }) .collect(); - for (fa, plugin_aliases) in plugin_aliases { + for (ba, plugin_aliases) in plugin_aliases { for (from, to) in plugin_aliases { aliases - .entry(fa.short.to_string()) + .entry(ba.short.to_string()) .or_default() .versions .insert(from, to); diff --git a/src/eager.rs b/src/eager.rs index 24cee753f6..e997bc8927 100644 --- a/src/eager.rs +++ b/src/eager.rs @@ -1,6 +1,5 @@ -use crate::backend::INSTALLED_BACKENDS; use crate::cli::version::VERSION; -use crate::plugins::{INSTALLED_PLUGINS, VERSION_REGEX}; +use crate::plugins::VERSION_REGEX; use once_cell::sync::Lazy; use std::path::PathBuf; use std::sync::Mutex; @@ -14,9 +13,9 @@ pub fn early_init() { rayon::spawn(|| { let _ = &*VERSION; }); - rayon::spawn(|| { - let _ = &*INSTALLED_PLUGINS; - }); + // rayon::spawn(|| { + // let _ = install_state::list_backends(); + // }); } pub static CONFIG_FILES: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); @@ -24,7 +23,7 @@ pub static CONFIG_FILES: Lazy>> = Lazy::new(|| Mutex::new(Vec /// run after SETTING has been loaded pub fn post_settings() { time!("post_settings"); - rayon::spawn(|| { - let _ = &*INSTALLED_BACKENDS; - }); + // rayon::spawn(|| { + // let _ = load_tools(); + // }); } diff --git a/src/file.rs b/src/file.rs index 2437b3a45c..7dcb7cbcd3 100644 --- a/src/file.rs +++ b/src/file.rs @@ -77,10 +77,7 @@ pub fn remove_dir>(path: P) -> Result<()> { .wrap_err_with(|| format!("failed to remove_dir: {}", display_path(path))) } -pub fn remove_dir_ignore>( - path: P, - is_empty_ignore_files: Vec, -) -> Result<()> { +pub fn remove_dir_ignore>(path: P, is_empty_ignore_files: Vec<&str>) -> Result<()> { let path = path.as_ref(); (|| -> Result<()> { if path.exists() && is_empty_dir_ignore(path, is_empty_ignore_files)? { @@ -412,7 +409,7 @@ fn is_empty_dir(path: &Path) -> Result { .wrap_err_with(|| format!("failed to read_dir: {}", display_path(path))) } -fn is_empty_dir_ignore(path: &Path, ignore_files: Vec) -> Result { +fn is_empty_dir_ignore(path: &Path, ignore_files: Vec<&str>) -> Result { path.read_dir() .map(|mut i| { i.all(|entry| match entry { diff --git a/src/lockfile.rs b/src/lockfile.rs index a9072b52b7..37f4b23812 100644 --- a/src/lockfile.rs +++ b/src/lockfile.rs @@ -59,7 +59,7 @@ pub fn update_lockfiles(new_versions: &[ToolVersion]) -> Result<()> { } // add versions added within this session such as from `mise use` or `mise up` - for (backend, group) in &new_versions.iter().chunk_by(|tv| tv.backend()) { + for (backend, group) in &new_versions.iter().chunk_by(|tv| tv.ba()) { let tvs = group.cloned().collect_vec(); let source = tvs[0].request.source().clone(); let mut tvl = ToolVersionList::new(backend.clone(), source.clone()); diff --git a/src/plugins/asdf_plugin.rs b/src/plugins/asdf_plugin.rs index b5b4fb2534..a620809a38 100644 --- a/src/plugins/asdf_plugin.rs +++ b/src/plugins/asdf_plugin.rs @@ -2,20 +2,19 @@ use crate::config::{Config, Settings, SETTINGS}; use crate::errors::Error::PluginNotInstalled; use crate::file::{display_path, remove_all}; use crate::git::Git; -use crate::plugins::{Plugin, PluginList, PluginType, Script, ScriptManager}; +use crate::plugins::{Plugin, PluginType, Script, ScriptManager}; use crate::result::Result; use crate::timeout::run_with_timeout; use crate::ui::multi_progress_report::MultiProgressReport; use crate::ui::progress_report::SingleReport; use crate::ui::prompt; -use crate::{dirs, env, exit, lock_file, plugins, registry}; +use crate::{dirs, env, exit, lock_file, registry}; use clap::Command; use console::style; use contracts::requires; use eyre::{bail, eyre, Context}; use itertools::Itertools; -use once_cell::sync::Lazy; -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::sync::{Mutex, MutexGuard}; @@ -30,14 +29,6 @@ pub struct AsdfPlugin { pub script_man: ScriptManager, } -pub static ASDF_PLUGIN_NAMES: Lazy> = Lazy::new(|| match AsdfPlugin::list() { - Ok(plugins) => plugins.into_iter().map(|p| p.name().to_string()).collect(), - Err(err) => { - warn!("Failed to list vfox plugins: {err}"); - BTreeSet::new() - } -}); - impl AsdfPlugin { #[requires(!name.is_empty())] pub fn new(name: String) -> Self { @@ -52,21 +43,6 @@ impl AsdfPlugin { } } - pub fn list() -> eyre::Result { - let settings = Settings::get(); - let plugins = plugins::INSTALLED_PLUGINS - .iter() - .filter(|(_, pt)| matches!(pt, PluginType::Asdf)) - .inspect(|(dir, _)| trace!("vfox_plugin: {:?}", dir)) - .map(|(dir, _)| { - let name = dir.file_name().unwrap().to_string_lossy().to_string(); - Box::new(AsdfPlugin::new(name)) as Box - }) - .filter(|p| !settings.disable_tools.contains(p.name())) - .collect(); - Ok(plugins) - } - fn repo(&self) -> MutexGuard { self.repo.lock().unwrap() } @@ -221,6 +197,10 @@ impl Plugin for AsdfPlugin { Ok(url.or(self.repo_url.clone())) } + fn set_remote_url(&mut self, url: String) { + self.repo_url = Some(url); + } + fn current_abbrev_ref(&self) -> eyre::Result> { if !self.is_installed() { return Ok(None); diff --git a/src/plugins/core/bun.rs b/src/plugins/core/bun.rs index 80df1c1c1b..cf778bb737 100644 --- a/src/plugins/core/bun.rs +++ b/src/plugins/core/bun.rs @@ -9,23 +9,23 @@ use crate::backend::Backend; use crate::cli::args::BackendArg; use crate::cli::version::{ARCH, OS}; use crate::cmd::CmdLineRunner; -use crate::file; use crate::github::GithubRelease; use crate::http::{HTTP, HTTP_FETCH}; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::toolset::{ToolRequest, ToolVersion}; use crate::ui::progress_report::SingleReport; +use crate::{file, plugins}; #[derive(Debug)] pub struct BunPlugin { - core: CorePlugin, + ba: BackendArg, } impl BunPlugin { pub fn new() -> Self { - let core = CorePlugin::new(BackendArg::new("bun", "bun")); - Self { core } + Self { + ba: plugins::core::new_backend_arg("bun"), + } } fn bun_bin(&self, tv: &ToolVersion) -> PathBuf { @@ -80,8 +80,8 @@ impl BunPlugin { } impl Backend for BunPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> Result> { diff --git a/src/plugins/core/deno.rs b/src/plugins/core/deno.rs index 91fd7f0c03..0f985b6682 100644 --- a/src/plugins/core/deno.rs +++ b/src/plugins/core/deno.rs @@ -12,22 +12,22 @@ use crate::cli::args::BackendArg; use crate::cli::version::{ARCH, OS}; use crate::cmd::CmdLineRunner; use crate::config::Config; -use crate::file; use crate::http::{HTTP, HTTP_FETCH}; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; +use crate::{file, plugins}; #[derive(Debug)] pub struct DenoPlugin { - core: CorePlugin, + ba: BackendArg, } impl DenoPlugin { pub fn new() -> Self { - let core = CorePlugin::new(BackendArg::new("deno", "deno")); - Self { core } + Self { + ba: plugins::core::new_backend_arg("deno"), + } } fn deno_bin(&self, tv: &ToolVersion) -> PathBuf { @@ -88,8 +88,8 @@ impl DenoPlugin { } impl Backend for DenoPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> Result> { diff --git a/src/plugins/core/erlang.rs b/src/plugins/core/erlang.rs index 45a70081d6..371ea4ed9a 100644 --- a/src/plugins/core/erlang.rs +++ b/src/plugins/core/erlang.rs @@ -6,15 +6,14 @@ use crate::file::display_path; use crate::http::HTTP_FETCH; use crate::install_context::InstallContext; use crate::lock_file::LockFile; -use crate::plugins::core::CorePlugin; use crate::toolset::ToolRequest; -use crate::{cmd, file}; +use crate::{cmd, file, plugins}; use eyre::Result; use xx::regex; #[derive(Debug)] pub struct ErlangPlugin { - core: CorePlugin, + ba: BackendArg, } const KERL_VERSION: &str = "4.1.1"; @@ -22,19 +21,16 @@ const KERL_VERSION: &str = "4.1.1"; impl ErlangPlugin { pub fn new() -> Self { Self { - core: CorePlugin::new(BackendArg::new("erlang", "erlang")), + ba: plugins::core::new_backend_arg("erlang"), } } fn kerl_path(&self) -> PathBuf { - self.core - .fa - .cache_path - .join(format!("kerl-{}", KERL_VERSION)) + self.ba.cache_path.join(format!("kerl-{}", KERL_VERSION)) } fn kerl_base_dir(&self) -> PathBuf { - self.core.fa.cache_path.join("kerl") + self.ba.cache_path.join("kerl") } fn lock_build_tool(&self) -> Result { @@ -72,14 +68,14 @@ impl ErlangPlugin { } impl Backend for ErlangPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> Result> { self.update_kerl()?; - let versions = CorePlugin::run_fetch_task_with_timeout(move || { + let versions = crate::plugins::core::run_fetch_task_with_timeout(move || { let output = cmd!(self.kerl_path(), "list", "releases", "all") - .env("KERL_BASE_DIR", self.core.fa.cache_path.join("kerl")) + .env("KERL_BASE_DIR", self.ba.cache_path.join("kerl")) .read()?; let versions = output .split('\n') @@ -107,7 +103,7 @@ impl Backend for ErlangPlugin { &ctx.tv.version, ctx.tv.install_path() ) - .env("KERL_BASE_DIR", self.core.fa.cache_path.join("kerl")) + .env("KERL_BASE_DIR", self.ba.cache_path.join("kerl")) .run()?; } } diff --git a/src/plugins/core/go.rs b/src/plugins/core/go.rs index 610dfeda5c..210a0ddc84 100644 --- a/src/plugins/core/go.rs +++ b/src/plugins/core/go.rs @@ -9,10 +9,9 @@ use crate::cmd::CmdLineRunner; use crate::config::{Config, Settings, SETTINGS}; use crate::http::HTTP; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; -use crate::{cmd, env, file, hash}; +use crate::{cmd, env, file, hash, plugins}; use itertools::Itertools; use tempfile::tempdir_in; use versions::Versioning; @@ -20,13 +19,13 @@ use xx::regex; #[derive(Debug)] pub struct GoPlugin { - core: CorePlugin, + ba: BackendArg, } impl GoPlugin { pub fn new() -> Self { Self { - core: CorePlugin::new(BackendArg::new("go", "go")), + ba: plugins::core::new_backend_arg("go"), } } @@ -170,11 +169,11 @@ impl GoPlugin { } impl Backend for GoPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> eyre::Result> { - CorePlugin::run_fetch_task_with_timeout(move || { + plugins::core::run_fetch_task_with_timeout(move || { let output = cmd!("git", "ls-remote", "--tags", &SETTINGS.go_repo, "go*").read()?; let lines = output.split('\n'); let versions = lines.map(|s| s.split("/go").last().unwrap_or_default().to_string()) diff --git a/src/plugins/core/java.rs b/src/plugins/core/java.rs index c36032444e..800029a812 100644 --- a/src/plugins/core/java.rs +++ b/src/plugins/core/java.rs @@ -11,11 +11,10 @@ use crate::cmd::CmdLineRunner; use crate::config::{Config, SETTINGS}; use crate::http::{HTTP, HTTP_FETCH}; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::plugins::VERSION_REGEX; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; -use crate::{file, hash}; +use crate::{file, hash, plugins}; use color_eyre::eyre::{eyre, Result}; use contracts::requires; use indoc::formatdoc; @@ -28,30 +27,30 @@ use xx::regex; #[derive(Debug)] pub struct JavaPlugin { - core: CorePlugin, + ba: BackendArg, java_metadata_ea_cache: CacheManager>, java_metadata_ga_cache: CacheManager>, } impl JavaPlugin { pub fn new() -> Self { - let core = CorePlugin::new(BackendArg::new("java", "java")); + let ba = plugins::core::new_backend_arg("java"); let java_metadata_ga_cache_filename = format!("java_metadata_ga_{}_{}.msgpack.z", os(), arch()); let java_metadata_ea_cache_filename = format!("java_metadata_ea_{}_{}.msgpack.z", os(), arch()); Self { java_metadata_ea_cache: CacheManagerBuilder::new( - core.fa.cache_path.join(java_metadata_ea_cache_filename), + ba.cache_path.join(java_metadata_ea_cache_filename), ) .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()) .build(), java_metadata_ga_cache: CacheManagerBuilder::new( - core.fa.cache_path.join(java_metadata_ga_cache_filename), + ba.cache_path.join(java_metadata_ga_cache_filename), ) .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()) .build(), - core, + ba, } } @@ -265,8 +264,8 @@ impl JavaPlugin { } impl Backend for JavaPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> Result> { diff --git a/src/plugins/core/mod.rs b/src/plugins/core/mod.rs index 7bc868809b..d6c2132aa4 100644 --- a/src/plugins/core/mod.rs +++ b/src/plugins/core/mod.rs @@ -1,14 +1,14 @@ use eyre::Result; use once_cell::sync::Lazy; use std::ffi::OsString; -use std::path::PathBuf; use std::sync::Arc; pub use python::PythonPlugin; use crate::backend::{Backend, BackendMap}; use crate::cli::args::BackendArg; -use crate::config::{Settings, SETTINGS}; +use crate::config::SETTINGS; +use crate::env; use crate::env::PATH_KEY; #[cfg(unix)] use crate::plugins::core::bun::BunPlugin; @@ -21,10 +21,8 @@ use crate::plugins::core::node::NodePlugin; use crate::plugins::core::ruby::RubyPlugin; #[cfg(unix)] use crate::plugins::core::zig::ZigPlugin; -use crate::plugins::{Plugin, PluginList, PluginType}; use crate::timeout::run_with_timeout; use crate::toolset::ToolVersion; -use crate::{dirs, env}; #[cfg(unix)] mod bun; @@ -71,64 +69,25 @@ pub static CORE_PLUGINS: Lazy = Lazy::new(|| { .collect() }); -// TODO: remove this struct -#[derive(Debug)] -pub struct CorePlugin { - pub fa: BackendArg, +pub fn path_env_with_tv_path(tv: &ToolVersion) -> Result { + let mut path = env::split_paths(&env::var_os(&*PATH_KEY).unwrap()).collect::>(); + path.insert(0, tv.install_path().join("bin")); + Ok(env::join_paths(path)?) } -impl CorePlugin { - pub fn list() -> PluginList { - let settings = Settings::get(); - CORE_PLUGINS - .iter() - .map(|(id, _)| Box::new(CorePlugin::new(id.to_string().into())) as Box) - .filter(|p| !settings.disable_tools.contains(p.name())) - .collect() - } - - pub fn new(fa: BackendArg) -> Self { - Self { fa } - } - - pub fn path_env_with_tv_path(tv: &ToolVersion) -> Result { - let mut path = env::split_paths(&env::var_os(&*PATH_KEY).unwrap()).collect::>(); - path.insert(0, tv.install_path().join("bin")); - Ok(env::join_paths(path)?) - } - - pub fn run_fetch_task_with_timeout(f: F) -> Result - where - F: FnOnce() -> Result + Send, - T: Send, - { - run_with_timeout(f, SETTINGS.fetch_remote_versions_timeout()) - } +pub fn run_fetch_task_with_timeout(f: F) -> Result +where + F: FnOnce() -> Result + Send, + T: Send, +{ + run_with_timeout(f, SETTINGS.fetch_remote_versions_timeout()) } -// TODO: remove this since core "plugins" are not plugins, this is legacy from when it was just asdf/core -impl Plugin for CorePlugin { - fn name(&self) -> &str { - &self.fa.name - } - - fn path(&self) -> PathBuf { - dirs::PLUGINS.join(self.name()) - } - - fn get_plugin_type(&self) -> PluginType { - PluginType::Core - } - - fn get_remote_url(&self) -> Result> { - Ok(None) - } - - fn current_abbrev_ref(&self) -> Result> { - Ok(None) - } - - fn current_sha_short(&self) -> Result> { - Ok(None) - } +pub fn new_backend_arg(tool_name: &str) -> BackendArg { + BackendArg::new_raw( + tool_name.to_string(), + Some(format!("core:{}", tool_name)), + tool_name.to_string(), + None, + ) } diff --git a/src/plugins/core/node.rs b/src/plugins/core/node.rs index 9fd5e7e02e..8878363257 100644 --- a/src/plugins/core/node.rs +++ b/src/plugins/core/node.rs @@ -7,10 +7,9 @@ use crate::config::settings::SETTINGS; use crate::config::{Config, Settings}; use crate::http::{HTTP, HTTP_FETCH}; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::toolset::ToolVersion; use crate::ui::progress_report::SingleReport; -use crate::{env, file, hash, http}; +use crate::{env, file, hash, http, plugins}; use eyre::{bail, ensure, Result}; use serde_derive::Deserialize; use std::collections::BTreeMap; @@ -22,13 +21,13 @@ use xx::regex; #[derive(Debug)] pub struct NodePlugin { - core: CorePlugin, + ba: BackendArg, } impl NodePlugin { pub fn new() -> Self { Self { - core: CorePlugin::new(BackendArg::new("node", "node")), + ba: plugins::core::new_backend_arg("node"), } } @@ -198,7 +197,7 @@ impl NodePlugin { .arg("--global") .arg(package) .envs(config.env()?) - .env(&*env::PATH_KEY, CorePlugin::path_env_with_tv_path(tv)?) + .env(&*env::PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .execute()?; } Ok(()) @@ -217,7 +216,7 @@ impl NodePlugin { CmdLineRunner::new(corepack) .with_pr(pr) .arg("enable") - .env(&*env::PATH_KEY, CorePlugin::path_env_with_tv_path(tv)?) + .env(&*env::PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .execute()?; Ok(()) } @@ -234,7 +233,7 @@ impl NodePlugin { fn test_npm(&self, config: &Config, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> { pr.set_message("npm -v".into()); CmdLineRunner::new(self.npm_path(tv)) - .env(&*env::PATH_KEY, CorePlugin::path_env_with_tv_path(tv)?) + .env(&*env::PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .with_pr(pr) .arg("-v") .envs(config.env()?) @@ -253,15 +252,15 @@ impl NodePlugin { } impl Backend for NodePlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn get_remote_version_cache(&self) -> Arc { static CACHE: OnceLock> = OnceLock::new(); CACHE .get_or_init(|| { - CacheManagerBuilder::new(self.fa().cache_path.join("remote_versions.msgpack.z")) + CacheManagerBuilder::new(self.ba().cache_path.join("remote_versions.msgpack.z")) .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()) .with_cache_key(SETTINGS.node.mirror_url.clone().unwrap_or_default()) .with_cache_key(SETTINGS.node.flavor.clone().unwrap_or_default()) diff --git a/src/plugins/core/python.rs b/src/plugins/core/python.rs index ebb9329e76..7eb94930b7 100644 --- a/src/plugins/core/python.rs +++ b/src/plugins/core/python.rs @@ -8,10 +8,9 @@ use crate::file::display_path; use crate::git::Git; use crate::http::{HTTP, HTTP_FETCH}; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; -use crate::{cmd, file}; +use crate::{cmd, file, plugins}; use eyre::{bail, eyre}; use itertools::Itertools; use std::collections::BTreeMap; @@ -22,25 +21,25 @@ use xx::regex; #[derive(Debug)] pub struct PythonPlugin { - core: CorePlugin, + ba: BackendArg, precompiled_cache: CacheManager>, } impl PythonPlugin { pub fn new() -> Self { - let core = CorePlugin::new(BackendArg::new("python", "python")); + let ba = plugins::core::new_backend_arg("python"); Self { precompiled_cache: CacheManagerBuilder::new( - core.fa.cache_path.join("precompiled.msgpack.z"), + ba.cache_path.join("precompiled.msgpack.z"), ) .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()) .build(), - core, + ba, } } fn python_build_path(&self) -> PathBuf { - self.core.fa.cache_path.join("pyenv") + self.ba.cache_path.join("pyenv") } fn python_build_bin(&self) -> PathBuf { self.python_build_path() @@ -73,7 +72,7 @@ impl PythonPlugin { self.python_build_path().display() ); let git = Git::new(self.python_build_path()); - CorePlugin::run_fetch_task_with_timeout(move || git.update(None))?; + plugins::core::run_fetch_task_with_timeout(move || git.update(None))?; Ok(()) } @@ -333,8 +332,8 @@ impl PythonPlugin { } impl Backend for PythonPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> eyre::Result> { @@ -347,7 +346,7 @@ impl Backend for PythonPlugin { } else { self.install_or_update_python_build()?; let python_build_bin = self.python_build_bin(); - CorePlugin::run_fetch_task_with_timeout(move || { + plugins::core::run_fetch_task_with_timeout(move || { let output = cmd!(python_build_bin, "--definitions").read()?; let versions = output .split('\n') @@ -365,7 +364,7 @@ impl Backend for PythonPlugin { static CACHE: OnceLock> = OnceLock::new(); CACHE .get_or_init(|| { - CacheManagerBuilder::new(self.fa().cache_path.join("remote_versions.msgpack.z")) + CacheManagerBuilder::new(self.ba().cache_path.join("remote_versions.msgpack.z")) .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()) .with_cache_key((SETTINGS.python.compile == Some(false)).to_string()) .build() diff --git a/src/plugins/core/ruby.rs b/src/plugins/core/ruby.rs index ac2c77ac37..7479e301da 100644 --- a/src/plugins/core/ruby.rs +++ b/src/plugins/core/ruby.rs @@ -13,31 +13,30 @@ use crate::github::GithubRelease; use crate::http::{HTTP, HTTP_FETCH}; use crate::install_context::InstallContext; use crate::lock_file::LockFile; -use crate::plugins::core::CorePlugin; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; -use crate::{cmd, env, file}; +use crate::{cmd, env, file, plugins}; use contracts::requires; use eyre::{Result, WrapErr}; use xx::regex; #[derive(Debug)] pub struct RubyPlugin { - core: CorePlugin, + ba: BackendArg, } impl RubyPlugin { pub fn new() -> Self { Self { - core: CorePlugin::new(BackendArg::new("ruby", "ruby")), + ba: plugins::core::new_backend_arg("ruby"), } } fn ruby_build_path(&self) -> PathBuf { - self.core.fa.cache_path.join("ruby-build") + self.ba.cache_path.join("ruby-build") } fn ruby_install_path(&self) -> PathBuf { - self.core.fa.cache_path.join("ruby-install") + self.ba.cache_path.join("ruby-install") } fn ruby_build_bin(&self) -> PathBuf { @@ -144,7 +143,7 @@ impl RubyPlugin { } debug!("Updating ruby-install in {}", ruby_install_path.display()); - CorePlugin::run_fetch_task_with_timeout(move || { + plugins::core::run_fetch_task_with_timeout(move || { cmd!(&ruby_install_path, "--update").run()?; file::touch_dir(&ruby_install_path)?; Ok(()) @@ -189,7 +188,7 @@ impl RubyPlugin { Some((name, version)) => cmd = cmd.arg(name).arg("--version").arg(version), None => cmd = cmd.arg(package), }; - cmd.env(&*PATH_KEY, CorePlugin::path_env_with_tv_path(tv)?) + cmd.env(&*PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .execute()?; } Ok(()) @@ -210,7 +209,7 @@ impl RubyPlugin { .with_pr(pr) .arg("-v") .envs(config.env()?) - .env(&*PATH_KEY, CorePlugin::path_env_with_tv_path(tv)?) + .env(&*PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .execute() } @@ -325,15 +324,15 @@ impl RubyPlugin { } impl Backend for RubyPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> Result> { if let Err(err) = self.update_build_tool() { warn!("{err}"); } let ruby_build_bin = self.ruby_build_bin(); - let versions = CorePlugin::run_fetch_task_with_timeout(move || { + let versions = plugins::core::run_fetch_task_with_timeout(move || { let output = cmd!(ruby_build_bin, "--definitions").read()?; let versions = output.split('\n').map(|s| s.to_string()).collect(); Ok(versions) diff --git a/src/plugins/core/ruby_windows.rs b/src/plugins/core/ruby_windows.rs index d502661264..f169a66987 100644 --- a/src/plugins/core/ruby_windows.rs +++ b/src/plugins/core/ruby_windows.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; +use crate::backend::backend_type::BackendType; use crate::backend::Backend; use crate::cli::args::BackendArg; use crate::cmd::CmdLineRunner; @@ -9,10 +10,9 @@ use crate::env::PATH_KEY; use crate::github::GithubRelease; use crate::http::HTTP; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; -use crate::{env, file, github}; +use crate::{env, file, github, plugins}; use contracts::requires; use eyre::Result; use itertools::Itertools; @@ -21,13 +21,13 @@ use xx::regex; #[derive(Debug)] pub struct RubyPlugin { - core: CorePlugin, + ba: BackendArg, } impl RubyPlugin { pub fn new() -> Self { Self { - core: CorePlugin::new(BackendArg::new("ruby", "ruby")), + ba: plugins::core::new_backend_arg("ruby"), } } @@ -64,7 +64,7 @@ impl RubyPlugin { Some((name, version)) => cmd = cmd.arg(name).arg("--version").arg(version), None => cmd = cmd.arg(package), }; - cmd.env(&*PATH_KEY, CorePlugin::path_env_with_tv_path(tv)?) + cmd.env(&*PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .execute()?; } Ok(()) @@ -85,7 +85,7 @@ impl RubyPlugin { .with_pr(pr) .arg("-v") .envs(config.env()?) - .env(&*PATH_KEY, CorePlugin::path_env_with_tv_path(tv)?) + .env(&*PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .execute() } @@ -105,7 +105,7 @@ impl RubyPlugin { let arch = arch(); let url = format!( "https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-{version}-1/rubyinstaller-{version}-1-{arch}.7z", - version=tv.version, + version = tv.version, ); let filename = url.split('/').last().unwrap(); let tarball_path = tv.download_path().join(filename); @@ -137,8 +137,8 @@ impl RubyPlugin { } impl Backend for RubyPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> Result> { // TODO: use windows set of versions @@ -182,7 +182,8 @@ impl Backend for RubyPlugin { Ok(v) } - #[requires(matches!(ctx.tv.request, ToolRequest::Version { .. } | ToolRequest::Prefix { .. }), "unsupported tool version request type")] + #[requires(matches!(ctx.tv.request, ToolRequest::Version { .. } | ToolRequest::Prefix { .. }), "unsupported tool version request type" + )] fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> { let config = Config::get(); let tarball = self.download(&ctx.tv, ctx.pr.as_ref())?; diff --git a/src/plugins/core/zig.rs b/src/plugins/core/zig.rs index 2c7eff3214..608b8a127e 100644 --- a/src/plugins/core/zig.rs +++ b/src/plugins/core/zig.rs @@ -4,13 +4,12 @@ use crate::backend::Backend; use crate::cli::args::BackendArg; use crate::cli::version::{ARCH, OS}; use crate::cmd::CmdLineRunner; -use crate::file; use crate::github::GithubRelease; use crate::http::{HTTP, HTTP_FETCH}; use crate::install_context::InstallContext; -use crate::plugins::core::CorePlugin; use crate::toolset::{ToolRequest, ToolVersion}; use crate::ui::progress_report::SingleReport; +use crate::{file, plugins}; use contracts::requires; use eyre::Result; use itertools::Itertools; @@ -19,13 +18,14 @@ use xx::regex; #[derive(Debug)] pub struct ZigPlugin { - core: CorePlugin, + ba: BackendArg, } impl ZigPlugin { pub fn new() -> Self { - let core = CorePlugin::new(BackendArg::new("zig", "zig")); - Self { core } + Self { + ba: plugins::core::new_backend_arg("zig"), + } } fn zig_bin(&self, tv: &ToolVersion) -> PathBuf { @@ -117,8 +117,8 @@ impl ZigPlugin { } impl Backend for ZigPlugin { - fn fa(&self) -> &BackendArg { - &self.core.fa + fn ba(&self) -> &BackendArg { + &self.ba } fn _list_remote_versions(&self) -> Result> { diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index c1707ea0a8..1f92cddead 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,18 +1,14 @@ -use crate::backend::{ABackend, BackendList, BackendType}; -use crate::cli::args::BackendArg; use crate::errors::Error::PluginNotInstalled; -use crate::plugins::asdf_plugin::{AsdfPlugin, ASDF_PLUGIN_NAMES}; -use crate::plugins::core::CorePlugin; -use crate::plugins::vfox_plugin::{VfoxPlugin, VFOX_PLUGIN_NAMES}; +use crate::plugins::asdf_plugin::AsdfPlugin; +use crate::plugins::vfox_plugin::VfoxPlugin; +use crate::toolset::install_state; use crate::ui::multi_progress_report::MultiProgressReport; use crate::ui::progress_report::SingleReport; -use crate::{backend, dirs, file}; use clap::Command; -use itertools::Itertools; +use eyre::{eyre, Result}; use once_cell::sync::Lazy; use regex::Regex; pub use script_manager::{Script, ScriptManager}; -use std::collections::BTreeMap; use std::fmt::{Debug, Display}; use std::path::PathBuf; use std::vec; @@ -25,11 +21,27 @@ pub mod vfox_plugin; #[derive(Debug, Clone, Copy, PartialEq, strum::EnumString, strum::Display)] pub enum PluginType { - Core, Asdf, Vfox, } +impl PluginType { + pub fn from_full(full: &str) -> eyre::Result { + match full.split(':').next() { + Some("asdf") => Ok(Self::Asdf), + Some("vfox") => Ok(Self::Vfox), + _ => Err(eyre!("unknown plugin type: {full}")), + } + } + + pub fn plugin(&self, short: String) -> APlugin { + match self { + PluginType::Asdf => Box::new(AsdfPlugin::new(short)), + PluginType::Vfox => Box::new(VfoxPlugin::new(short)), + } + } +} + pub static VERSION_REGEX: Lazy = Lazy::new(|| { Regex::new( r"(^Available versions:|-src|-dev|-latest|-stm|[-\\.]rc|-milestone|-alpha|-beta|[-\\.]pre|-next|([abc])[0-9]+|snapshot|SNAPSHOT|master)" @@ -37,79 +49,25 @@ pub static VERSION_REGEX: Lazy = Lazy::new(|| { .unwrap() }); -pub fn get(name: &str) -> ABackend { - BackendArg::new(name, name).into() -} - -pub static PLUGIN_NAMES_TO_TYPE: Lazy> = Lazy::new(|| { - let vfox = VFOX_PLUGIN_NAMES - .iter() - .map(|name| (name.clone(), PluginType::Vfox)) - .collect_vec(); - let asdf = ASDF_PLUGIN_NAMES - .iter() - .map(|name| (name.clone(), PluginType::Asdf)) - .collect_vec(); - asdf.into_iter().chain(vfox).collect() -}); - -pub static INSTALLED_PLUGINS: Lazy> = - Lazy::new(|| match file::dir_subdirs(&dirs::PLUGINS) { - Ok(dirs) => dirs - .into_iter() - .map(|d| { - let path = dirs::PLUGINS.join(&d); - let plugin_type = if path.join("metadata.lua").exists() { - PluginType::Vfox - } else { - PluginType::Asdf - }; - (path, plugin_type) - }) - .collect(), - Err(e) => { - warn!("error reading plugin dirs: {e}"); - vec![] - } - }); - -pub fn list() -> BackendList { - // TODO: replace with list2 - backend::list() - .into_iter() - .filter(|f| matches!(f.get_type(), BackendType::Asdf | BackendType::Vfox)) - .collect() -} - -pub fn list2() -> eyre::Result { - let core = CorePlugin::list() - .into_iter() - .map(|p| (p.name().to_string(), p)); - let asdf = AsdfPlugin::list()? - .into_iter() - .map(|p| (p.name().to_string(), p)); - let vfox = VfoxPlugin::list()? - .into_iter() - .map(|p| (p.name().to_string(), p)); - Ok(core.chain(asdf).chain(vfox).collect()) -} - -pub fn list_external() -> BackendList { - list() - .into_iter() - .filter(|tool| matches!(tool.get_plugin_type(), PluginType::Asdf | PluginType::Vfox)) - .collect() +pub fn get(short: &str) -> Result { + let (name, full) = short.split_once(':').unwrap_or((short, short)); + let plugin_type = if let Some(plugin_type) = install_state::list_plugins()?.remove(short) { + plugin_type + } else { + PluginType::from_full(full)? + }; + Ok(plugin_type.plugin(name.to_string())) } pub type APlugin = Box; -pub type PluginMap = BTreeMap; -pub type PluginList = Vec; +#[allow(unused_variables)] pub trait Plugin: Debug + Send { fn name(&self) -> &str; fn path(&self) -> PathBuf; fn get_plugin_type(&self) -> PluginType; fn get_remote_url(&self) -> eyre::Result>; + fn set_remote_url(&mut self, url: String) {} fn current_abbrev_ref(&self) -> eyre::Result>; fn current_sha_short(&self) -> eyre::Result>; fn is_installed(&self) -> bool { @@ -178,27 +136,32 @@ mod tests { use crate::backend::asdf::AsdfBackend; use crate::backend::Backend; + use crate::cli::args::BackendArg; use crate::test::reset; #[test] fn test_exact_match() { reset(); assert_cli!("plugin", "add", "tiny"); - let plugin = AsdfBackend::from_arg("tiny".into()); - let version = plugin + let ba = BackendArg::from("tiny"); + assert_str_eq!(ba.short, "tiny"); + assert_str_eq!(ba.tool_name, "mise-plugins/mise-tiny"); + assert_str_eq!(ba.full(), "asdf:mise-plugins/mise-tiny"); + let backend = AsdfBackend::from_arg("tiny".into()); + let version = backend .latest_version(Some("1.0.0".into())) .unwrap() .unwrap(); assert_str_eq!(version, "1.0.0"); - let version = plugin.latest_version(None).unwrap().unwrap(); + let version = backend.latest_version(None).unwrap().unwrap(); assert_str_eq!(version, "3.1.0"); } #[test] fn test_latest_stable() { reset(); - let plugin = AsdfBackend::from_arg("dummy".into()); - let version = plugin.latest_version(None).unwrap().unwrap(); + let backend = AsdfBackend::from_arg("dummy".into()); + let version = backend.latest_version(None).unwrap().unwrap(); assert_str_eq!(version, "2.0.0"); } } diff --git a/src/plugins/vfox_plugin.rs b/src/plugins/vfox_plugin.rs index d9f84c8eae..b7dd12e7bd 100644 --- a/src/plugins/vfox_plugin.rs +++ b/src/plugins/vfox_plugin.rs @@ -1,17 +1,14 @@ -use crate::config::Settings; use crate::file::{display_path, remove_all}; use crate::git::Git; -use crate::plugins::{Plugin, PluginList, PluginType}; +use crate::plugins::{Plugin, PluginType}; use crate::result::Result; use crate::ui::multi_progress_report::MultiProgressReport; use crate::ui::progress_report::SingleReport; -use crate::{dirs, plugins, registry}; +use crate::{dirs, registry}; use console::style; use contracts::requires; use eyre::{eyre, Context, Report}; use indexmap::{indexmap, IndexMap}; -use once_cell::sync::Lazy; -use std::collections::BTreeSet; use std::path::{Path, PathBuf}; use std::sync::{mpsc, Mutex, MutexGuard}; use tokio::runtime::Runtime; @@ -28,14 +25,6 @@ pub struct VfoxPlugin { pub repo_url: Option, } -pub static VFOX_PLUGIN_NAMES: Lazy> = Lazy::new(|| match VfoxPlugin::list() { - Ok(plugins) => plugins.into_iter().map(|p| p.name().to_string()).collect(), - Err(err) => { - warn!("Failed to list vfox plugins: {err}"); - BTreeSet::new() - } -}); - impl VfoxPlugin { #[requires(!name.is_empty())] pub fn new(name: String) -> Self { @@ -50,21 +39,6 @@ impl VfoxPlugin { } } - pub fn list() -> eyre::Result { - let settings = Settings::get(); - let plugins = plugins::INSTALLED_PLUGINS - .iter() - .filter(|(_, t)| matches!(t, PluginType::Vfox)) - .inspect(|(dir, _)| trace!("vfox_plugin: {:?}", dir)) - .map(|(dir, _)| { - let name = dir.file_name().unwrap().to_string_lossy().to_string(); - Box::new(VfoxPlugin::new(name)) as Box - }) - .filter(|p| !settings.disable_tools.contains(p.name())) - .collect(); - Ok(plugins) - } - fn repo(&self) -> MutexGuard { self.repo.lock().unwrap() } @@ -133,6 +107,10 @@ impl Plugin for VfoxPlugin { Ok(url.or(self.repo_url.clone())) } + fn set_remote_url(&mut self, url: String) { + self.repo_url = Some(url); + } + fn current_abbrev_ref(&self) -> eyre::Result> { if !self.is_installed() { return Ok(None); diff --git a/src/registry.rs b/src/registry.rs index fc30fdbdbe..c448e30523 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,10 +1,11 @@ +use crate::backend::backend_type::BackendType; use crate::cli::args::BackendArg; use crate::config::SETTINGS; -use crate::plugins::core::CORE_PLUGINS; use itertools::Itertools; use once_cell::sync::Lazy; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::iter::Iterator; +use strum::IntoEnumIterator; use url::Url; // the registry is generated from registry.toml in the project root @@ -12,20 +13,23 @@ include!(concat!(env!("OUT_DIR"), "/registry.rs")); // a rust representation of registry.toml pub static REGISTRY: Lazy>> = Lazy::new(|| { - let backend_types = vec![ - "core", "ubi", "vfox", "asdf", "aqua", "cargo", "go", "npm", "pipx", "spm", - ] - .into_iter() - .filter(|b| !(*b == "asdf" && cfg!(windows))) - .filter(|b| !(*b == "aqua" && cfg!(unix) && !SETTINGS.experimental)) - .filter(|b| !SETTINGS.disable_backends.contains(&b.to_string())) - .collect::>(); + let mut backend_types = BackendType::iter() + .map(|b| b.to_string()) + .collect::>(); + for backend in &SETTINGS.disable_backends { + backend_types.remove(backend); + } + if cfg!(windows) { + backend_types.remove("asdf"); + } + if cfg!(unix) && !SETTINGS.experimental { + backend_types.remove("aqua"); + } _REGISTRY .iter() - .filter(|(id, _)| !CORE_PLUGINS.contains_key(*id)) - .map(|(id, fulls)| { - let fulls = fulls + .map(|(short, backends)| { + let backends = backends .iter() .filter(|full| { full.split(':') @@ -34,9 +38,9 @@ pub static REGISTRY: Lazy>> = Lazy::new(|| { }) .map(|full| full.to_string()) .collect::>(); - (*id, fulls) + (*short, backends) }) - .filter(|(_, fulls)| !fulls.is_empty()) + .filter(|(_, backends)| !backends.is_empty()) .collect() }); @@ -46,7 +50,9 @@ pub static REGISTRY_BACKEND_MAP: Lazy>> = .map(|(short, full)| { ( *short, - full.iter().map(|f| BackendArg::new(short, f)).collect(), + full.iter() + .map(|f| BackendArg::new(short.to_string(), Some(f.to_string()))) + .collect(), ) }) .collect() diff --git a/src/runtime_symlinks.rs b/src/runtime_symlinks.rs index 9d93b4d9d5..67a6568790 100644 --- a/src/runtime_symlinks.rs +++ b/src/runtime_symlinks.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; -use crate::backend::{backend_meta, Backend}; +use crate::backend::Backend; use crate::config::{Alias, Config}; use crate::file::make_symlink_or_file; use crate::plugins::VERSION_REGEX; @@ -15,7 +15,7 @@ use xx::regex; pub fn rebuild(config: &Config) -> Result<()> { for backend in backend::list() { let symlinks = list_symlinks(config, backend.clone())?; - let installs_dir = &backend.fa().installs_path; + let installs_dir = &backend.ba().installs_path; for (from, to) in symlinks { let from = installs_dir.join(from); if from.exists() { @@ -30,10 +30,7 @@ pub fn rebuild(config: &Config) -> Result<()> { } remove_missing_symlinks(backend.clone())?; // remove install dir if empty (ignore metadata) - file::remove_dir_ignore( - installs_dir, - vec![backend_meta::FORGE_META_FILENAME.to_string()], - )?; + file::remove_dir_ignore(installs_dir, vec![".mise.backend.json", ".mise.backend"])?; } Ok(()) } @@ -59,8 +56,8 @@ fn list_symlinks(config: &Config, backend: Arc) -> Result) -> Result> { } fn remove_missing_symlinks(backend: Arc) -> Result<()> { - let installs_dir = &backend.fa().installs_path; + let installs_dir = &backend.ba().installs_path; if !installs_dir.exists() { return Ok(()); } @@ -111,24 +108,3 @@ pub fn is_runtime_symlink(path: &Path) -> bool { } false } - -#[cfg(test)] -mod tests { - use crate::backend::asdf::AsdfBackend; - use insta::assert_debug_snapshot; - - use crate::test::reset; - - use super::*; - - #[test] - fn test_list_symlinks() { - reset(); - assert_cli!("install", "tiny@2"); - let config = Config::load().unwrap(); - let plugin = AsdfBackend::from_arg("tiny".into()); - let plugin = Arc::new(plugin); - let symlinks = list_symlinks(&config, plugin).unwrap(); - assert_debug_snapshot!(symlinks); - } -} diff --git a/src/shims.rs b/src/shims.rs index d340270588..de198aa997 100644 --- a/src/shims.rs +++ b/src/shims.rs @@ -55,7 +55,7 @@ fn which_shim(bin_name: &str) -> Result { } if SETTINGS.not_found_auto_install && console::user_attended() { for tv in ts.install_missing_bin(bin_name)?.unwrap_or_default() { - let p = tv.get_backend(); + let p = tv.backend()?; if let Some(bin) = p.which(&tv, bin_name)? { trace!( "shim[{bin_name}] NOT_FOUND ToolVersion: {tv} bin: {bin}", @@ -276,17 +276,17 @@ fn err_no_version_set(ts: Toolset, bin_name: &str, tvs: Vec) -> Res if tvs.is_empty() { bail!("{} is not a valid shim", bin_name); } - let missing_plugins = tvs.iter().map(|tv| tv.backend()).collect::>(); + let missing_plugins = tvs.iter().map(|tv| tv.ba()).collect::>(); let mut missing_tools = ts .list_missing_versions() .into_iter() - .filter(|t| missing_plugins.contains(t.backend())) + .filter(|t| missing_plugins.contains(t.ba())) .collect_vec(); if missing_tools.is_empty() { let mut msg = format!("No version is set for shim: {}\n", bin_name); msg.push_str("Set a global default version with one of the following:\n"); for tv in tvs { - msg.push_str(&format!("mise use -g {}@{}\n", tv.backend(), tv.version)); + msg.push_str(&format!("mise use -g {}@{}\n", tv.ba(), tv.version)); } Err(eyre!(msg.trim().to_string())) } else { diff --git a/src/snapshots/mise__runtime_symlinks__tests__list_symlinks.snap b/src/snapshots/mise__runtime_symlinks__tests__list_symlinks.snap deleted file mode 100644 index 006547266b..0000000000 --- a/src/snapshots/mise__runtime_symlinks__tests__list_symlinks.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/runtime_symlinks.rs -expression: symlinks -snapshot_kind: text ---- -{ - "latest": "./3.1.0", - "lts": "./3.1.0", - "2": "./2.1.0", - "2.1": "./2.1.0", - "3": "./3.1.0", - "3.1": "./3.1.0", -} diff --git a/src/test.rs b/src/test.rs index e64c5b9df3..227ad82677 100644 --- a/src/test.rs +++ b/src/test.rs @@ -7,6 +7,7 @@ use indoc::indoc; use crate::cli::Cli; use crate::config::{config_file, Config}; use crate::output::tests::{STDERR, STDOUT}; +use crate::toolset::install_state; use crate::{backend, cmd, dirs, env, file}; #[macro_export] @@ -74,6 +75,7 @@ fn init() { } pub fn reset() { + install_state::reset(); Config::reset(); backend::reset(); config_file::reset(); @@ -220,6 +222,7 @@ pub fn replace_path(input: &str) -> String { } pub fn cli_run(args: &Vec) -> eyre::Result<(String, String)> { + install_state::reset(); Config::reset(); backend::reset(); config_file::reset(); diff --git a/src/toolset/builder.rs b/src/toolset/builder.rs index 2a41d26e1f..af2c872b7d 100644 --- a/src/toolset/builder.rs +++ b/src/toolset/builder.rs @@ -41,7 +41,7 @@ impl ToolsetBuilder { if Error::is_argument_err(&err) { return Err(err); } - warn!("failed to resolve toolset: {err:?}"); + warn!("failed to resolve toolset: {err}"); } time!("toolset::builder::build"); @@ -84,11 +84,7 @@ impl ToolsetBuilder { } fn load_runtime_args(&self, ts: &mut Toolset) -> eyre::Result<()> { - for (_, args) in self - .args - .iter() - .into_group_map_by(|arg| arg.backend.clone()) - { + for (_, args) in self.args.iter().into_group_map_by(|arg| arg.ba.clone()) { let mut arg_ts = Toolset::new(ToolSource::Argument); for arg in args { if let Some(tvr) = &arg.tvr { @@ -102,19 +98,19 @@ impl ToolsetBuilder { let current_active = ts .list_current_requests() .into_iter() - .find(|tvr| tvr.backend() == &arg.backend); + .find(|tvr| tvr.ba() == &arg.ba); if let Some(current_active) = current_active { // active version, so don't set "latest" arg_ts.add_version(ToolRequest::new( - arg.backend.clone(), + arg.ba.clone(), ¤t_active.version(), ToolSource::Argument, )?); } else { // no active version, so use "latest" arg_ts.add_version(ToolRequest::new( - arg.backend.clone(), + arg.ba.clone(), "latest", ToolSource::Argument, )?); diff --git a/src/toolset/install_state.rs b/src/toolset/install_state.rs new file mode 100644 index 0000000000..6d780118ed --- /dev/null +++ b/src/toolset/install_state.rs @@ -0,0 +1,221 @@ +use crate::backend::backend_type::BackendType; +use crate::file::display_path; +use crate::plugins::PluginType; +use crate::registry::REGISTRY; +use crate::{dirs, file, runtime_symlinks}; +use eyre::{Ok, Result}; +use heck::ToKebabCase; +use itertools::Itertools; +use std::collections::BTreeMap; +use std::path::PathBuf; +use std::sync::{Mutex, MutexGuard}; +use versions::Versioning; + +type InstallStatePlugins = BTreeMap; +type InstallStateTools = BTreeMap; + +#[derive(Debug, Clone)] +pub struct InstallStateTool { + pub short: String, + pub full: String, + pub backend_type: BackendType, + pub versions: Vec, +} + +static INSTALL_STATE_PLUGINS: Mutex> = Mutex::new(None); +static INSTALL_STATE_TOOLS: Mutex> = Mutex::new(None); + +fn init_plugins() -> Result>>> { + let mut mu = INSTALL_STATE_PLUGINS.lock().unwrap(); + if mu.is_some() { + return Ok(mu); + } + let dirs = file::dir_subdirs(&dirs::PLUGINS)?; + let plugins = dirs + .into_iter() + .filter_map(|d| { + let path = dirs::PLUGINS.join(&d); + if path.join("metadata.lua").exists() { + Some((d, PluginType::Vfox)) + } else if path.join("bin").join("list-all").exists() { + Some((d, PluginType::Asdf)) + } else { + None + } + }) + .collect(); + time!("init_install_state plugins"); + *mu = Some(plugins); + Ok(mu) +} + +fn init_tools() -> Result>>> { + let mut mu = INSTALL_STATE_TOOLS.lock().unwrap(); + if mu.is_some() { + return Ok(mu); + } + let mut tools: InstallStateTools = init_plugins()? + .as_ref() + .unwrap() + .iter() + .map(|(short, pt)| { + let tool = InstallStateTool { + short: short.clone(), + backend_type: match pt { + PluginType::Asdf => BackendType::Asdf, + PluginType::Vfox => BackendType::Vfox, + }, + full: match pt { + PluginType::Asdf => format!("asdf:{short}"), + PluginType::Vfox => format!("vfox:{short}"), + }, + versions: Default::default(), + }; + (short.clone(), tool) + }) + .collect(); + let dirs = file::dir_subdirs(&dirs::INSTALLS)?; + tools.extend( + dirs.into_iter() + .map(|short| { + let dir = dirs::INSTALLS.join(&short); + let full = if let Some(full) = short_to_full(&short)? { + full + } else { + return Ok(None); + }; + let backend_type = BackendType::guess(&full); + let versions = file::dir_subdirs(&dir) + .unwrap_or_else(|err| { + warn!("reading versions in {} failed: {err:?}", display_path(&dir)); + Default::default() + }) + .into_iter() + .filter(|v| !v.starts_with('.')) + .filter(|v| !runtime_symlinks::is_runtime_symlink(&dir.join(v))) + .filter(|v| !dir.join(v).join("incomplete").exists()) + .sorted_by_cached_key(|v| (Versioning::new(v), v.to_string())) + .collect(); + let tool = InstallStateTool { + short: short.clone(), + full, + backend_type, + versions, + }; + Ok(Some((short, tool))) + }) + .collect::>>()? + .into_iter() + .flatten(), + ); + time!("init_install_state tools"); + *mu = Some(tools); + Ok(mu) +} + +pub fn short_to_full(short: &str) -> Result> { + let plugins = init_plugins()?; + let plugins = plugins.as_ref().unwrap(); + if let Some(plugin) = plugins.get(short) { + match plugin { + PluginType::Asdf => Ok(Some(format!("asdf:{short}"))), + PluginType::Vfox => Ok(Some(format!("vfox:{short}"))), + } + } else if let Some(full) = read_backend_meta(short) { + Ok(Some(full)) + } else if let Some(full) = REGISTRY.get(short).unwrap_or(&EMPTY_VEC).first() { + Ok(Some(full.clone())) + } else { + Ok(None) + } +} + +pub fn list_plugins() -> Result> { + let plugins = init_plugins()?; + Ok(plugins.as_ref().unwrap().clone()) +} + +pub fn list_tools() -> Result> { + let tools = init_tools()?; + Ok(tools.as_ref().unwrap().clone()) +} + +pub fn backend_type(short: &str) -> Result> { + let tools = init_tools()?; + Ok(tools + .as_ref() + .unwrap() + .get(short) + .map(|tool| tool.backend_type)) +} + +pub fn list_versions(short: &str) -> Result> { + let tools = init_tools()?; + Ok(tools + .as_ref() + .unwrap() + .get(short) + .map(|tool| tool.versions.clone()) + .unwrap_or_default()) +} + +pub fn add_plugin(short: &str, plugin_type: PluginType) -> Result<()> { + let mut plugins = init_plugins()?; + plugins + .as_mut() + .unwrap() + .insert(short.to_string(), plugin_type); + Ok(()) +} + +fn backend_meta_path(short: &str) -> PathBuf { + dirs::INSTALLS.join(short).join(".mise.backend") +} + +fn migrate_backend_meta_json(short: &str) { + let old = dirs::INSTALLS.join(short).join(".mise.backend.json"); + let migrate = || { + let json: serde_json::Value = serde_json::from_reader(file::open(&old)?)?; + if let Some(full) = json.get("id").and_then(|id| id.as_str()) { + file::write(backend_meta_path(short), full.trim())?; + } + Ok(()) + }; + if old.exists() { + if let Err(err) = migrate() { + debug!("{err:#}"); + } + if let Err(err) = file::remove_file(&old) { + debug!("{err:#}"); + } + } +} + +fn read_backend_meta(short: &str) -> Option { + migrate_backend_meta_json(short); + let path = backend_meta_path(short); + if path.exists() { + let body = file::read_to_string(&path) + .map_err(|err| { + warn!("{err:?}"); + }) + .unwrap_or_default(); + Some(body) + } else { + None + } +} + +pub fn incomplete_file_path(short: &str, v: &str) -> PathBuf { + dirs::CACHE + .join(short.to_kebab_case()) + .join(v) + .join("incomplete") +} + +static EMPTY_VEC: Vec = vec![]; + +pub fn reset() { + *INSTALL_STATE_PLUGINS.lock().unwrap() = None; + *INSTALL_STATE_TOOLS.lock().unwrap() = None; +} diff --git a/src/toolset/mod.rs b/src/toolset/mod.rs index ca61a36598..eeb417f849 100644 --- a/src/toolset/mod.rs +++ b/src/toolset/mod.rs @@ -33,6 +33,7 @@ use versions::{Version, Versioning}; use xx::regex; mod builder; +pub(crate) mod install_state; pub(crate) mod tool_request; mod tool_request_set; mod tool_source; @@ -90,13 +91,13 @@ impl Toolset { } } pub fn add_version(&mut self, tvr: ToolRequest) { - let fa = tvr.backend(); + let fa = tvr.ba(); if self.is_disabled(fa) { return; } let tvl = self .versions - .entry(tvr.backend().clone()) + .entry(tvr.ba().clone()) .or_insert_with(|| ToolVersionList::new(fa.clone(), self.source.clone().unwrap())); tvl.requests.push(tvr); } @@ -141,7 +142,7 @@ impl Toolset { .into_iter() .filter(|(p, tv)| opts.force || !p.is_version_installed(tv, true)) .map(|(_, tv)| tv) - .filter(|tv| matches!(self.versions[tv.backend()].source, ToolSource::Argument)) + .filter(|tv| matches!(self.versions[tv.ba()].source, ToolSource::Argument)) .map(|tv| tv.request) .collect_vec(); let versions = self.install_versions(config, versions, &mpr, opts)?; @@ -152,7 +153,7 @@ impl Toolset { pub fn list_missing_plugins(&self) -> Vec { self.versions .keys() - .map(backend::get) + .flat_map(|ba| ba.backend()) .filter(|b| b.plugin().is_some_and(|p| !p.is_installed())) .map(|p| p.id().into()) .collect() @@ -178,10 +179,10 @@ impl Toolset { let queue: Vec<_> = versions .into_iter() .rev() - .chunk_by(|v| v.backend().clone()) + .chunk_by(|v| v.ba().clone()) .into_iter() - .map(|(fa, v)| (backend::get(&fa), v.collect_vec())) - .collect(); + .map(|(ba, v)| Ok((ba.backend()?, v.collect_vec()))) + .collect::>()?; for (backend, _) in &queue { if let Some(plugin) = backend.plugin() { if !plugin.is_installed() { @@ -249,6 +250,9 @@ impl Toolset { .collect::>>>() .map(|x| x.into_iter().flatten().rev().collect()) })?; + + install_state::reset(); + trace!("install: resolving"); if let Err(err) = self.resolve() { debug!("error resolving versions after install: {err:#}"); @@ -259,7 +263,7 @@ impl Toolset { trace!("install: done"); if log::log_enabled!(log::Level::Debug) { for tv in installed.iter() { - let backend = backend::get(tv.backend()); + let backend = tv.backend()?; let bin_paths = backend .list_bin_paths(tv) .map_err(|e| { @@ -304,7 +308,7 @@ impl Toolset { |v| match current_versions.get(&(p.id().into(), v.clone())) { Some((p, tv)) => Ok((p.clone(), tv.clone())), None => { - let tv = ToolRequest::new(p.fa().clone(), &v, ToolSource::Unknown)? + let tv = ToolRequest::new(p.ba().clone(), &v, ToolSource::Unknown)? .resolve(&Default::default()) .unwrap(); Ok((p.clone(), tv)) @@ -320,12 +324,6 @@ impl Toolset { Ok(versions) } - pub fn list_plugins(&self) -> Vec> { - self.list_versions_by_plugin() - .into_iter() - .map(|(p, _)| p) - .collect() - } pub fn list_current_requests(&self) -> Vec<&ToolRequest> { self.versions .values() @@ -335,7 +333,7 @@ impl Toolset { pub fn list_versions_by_plugin(&self) -> Vec<(Arc, &Vec)> { self.versions .iter() - .map(|(p, v)| (backend::get(p), &v.versions)) + .flat_map(|(ba, v)| eyre::Ok((ba.backend()?, &v.versions))) .collect() } pub fn list_current_versions(&self) -> Vec<(Arc, ToolVersion)> { @@ -347,7 +345,7 @@ impl Toolset { let tv = match v.version.split_once(':') { Some((ref_type @ ("tag" | "branch" | "rev"), r)) => { let request = ToolRequest::Ref { - backend: p.fa().clone(), + backend: p.ba().clone(), ref_: r.to_string(), ref_type: ref_type.to_string(), options: v.request.options().clone(), @@ -545,7 +543,7 @@ impl Toolset { let versions = self .list_missing_versions() .into_iter() - .filter(|tv| tv.backend() == plugin.fa()) + .filter(|tv| tv.ba() == plugin.ba()) .map(|tv| tv.request) .collect_vec(); if !versions.is_empty() { @@ -584,9 +582,8 @@ impl Toolset { SettingsStatusMissingTools::Never => false, SettingsStatusMissingTools::Always => true, SettingsStatusMissingTools::IfOtherVersionsInstalled => tv - .get_backend() - .list_installed_versions() - .is_ok_and(|f| !f.is_empty()), + .backend() + .is_ok_and(|b| b.list_installed_versions().is_ok_and(|f| !f.is_empty())), }) .collect_vec(); if missing.is_empty() || *env::__MISE_SHIM { @@ -612,7 +609,7 @@ impl Toolset { fn show_python_install_hint(versions: &[ToolRequest]) { let num_python = versions .iter() - .filter(|tr| tr.backend().name == "python") + .filter(|tr| tr.ba().tool_name == "python") .count(); if num_python != 1 { return; @@ -651,10 +648,7 @@ impl From for Toolset { } fn get_leaf_dependencies(requests: &[ToolRequest]) -> eyre::Result> { - let versions_hash = requests - .iter() - .map(|tr| tr.backend()) - .collect::>(); + let versions_hash = requests.iter().map(|tr| tr.ba()).collect::>(); let leaves = requests .iter() .map(|tr| { @@ -753,7 +747,7 @@ pub struct OutdatedInfo { impl OutdatedInfo { fn new(tv: ToolVersion, source: ToolSource) -> Self { Self { - name: tv.backend().short.to_string(), + name: tv.ba().short.to_string(), current: None, requested: tv.request.version(), tool_request: tv.request.clone(), diff --git a/src/toolset/tool_request.rs b/src/toolset/tool_request.rs index e17e459c95..134096eff0 100644 --- a/src/toolset/tool_request.rs +++ b/src/toolset/tool_request.rs @@ -5,6 +5,7 @@ use eyre::{bail, Result}; use versions::{Chunk, Version}; use xx::file; +use crate::backend::ABackend; use crate::cli::args::BackendArg; use crate::runtime_symlinks::is_runtime_symlink; use crate::toolset::tool_version::ResolveOptions; @@ -110,7 +111,7 @@ impl ToolRequest { } self.clone() } - pub fn backend(&self) -> &BackendArg { + pub fn ba(&self) -> &BackendArg { match self { Self::Version { backend: f, .. } | Self::Prefix { backend: f, .. } @@ -120,57 +121,8 @@ impl ToolRequest { | Self::System(f, ..) => f, } } - pub fn with_backend(self, backend: BackendArg) -> Self { - match self { - Self::Version { - version, - options, - source, - backend: _, - } => Self::Version { - backend, - version, - options, - source, - }, - Self::Prefix { - prefix, - options, - source, - backend: _, - } => Self::Prefix { - backend, - prefix, - options, - source, - }, - Self::Ref { - ref_, - ref_type, - options, - source, - backend: _, - } => Self::Ref { - backend, - ref_, - ref_type, - options, - source, - }, - Self::Path(_, path, source) => Self::Path(backend, path, source), - Self::Sub { - sub, - orig_version, - source, - backend: _, - } => Self::Sub { - backend, - sub, - orig_version, - source, - }, - Self::System(_, source) => Self::System(backend, source), - } + pub fn backend(&self) -> Result { + self.ba().backend() } pub fn source(&self) -> &ToolSource { match self { @@ -183,7 +135,7 @@ impl ToolRequest { } } pub fn dependencies(&self) -> eyre::Result> { - let backend = backend::get(self.backend()); + let backend = self.ba().backend()?; backend.get_all_dependencies(self) } pub fn version(&self) -> String { @@ -211,10 +163,12 @@ impl ToolRequest { } pub fn is_installed(&self) -> bool { - let backend = backend::get(self.backend()); - let tv = ToolVersion::new(self.clone(), self.version()); - - backend.is_version_installed(&tv, false) + if let Some(backend) = backend::get(self.ba()) { + let tv = ToolVersion::new(self.clone(), self.version()); + backend.is_version_installed(&tv, false) + } else { + false + } } pub fn install_path(&self) -> Option { @@ -257,7 +211,7 @@ impl ToolRequest { pub fn lockfile_resolve(&self) -> Result> { if let Some(path) = self.source().path() { - return lockfile::get_locked_version(path, &self.backend().short, &self.version()); + return lockfile::get_locked_version(path, &self.ba().short, &self.version()); } Ok(None) } @@ -266,13 +220,14 @@ impl ToolRequest { if let Some(v) = self.lockfile_resolve()? { return Ok(Some(v)); } - let backend = backend::get(self.backend()); - let matches = backend.list_installed_versions_matching(v)?; - if matches.iter().any(|m| m == v) { - return Ok(Some(v.to_string())); - } - if let Some(v) = matches.last() { - return Ok(Some(v.to_string())); + if let Some(backend) = backend::get(self.ba()) { + let matches = backend.list_installed_versions_matching(v)?; + if matches.iter().any(|m| m == v) { + return Ok(Some(v.to_string())); + } + if let Some(v) = matches.last() { + return Ok(Some(v.to_string())); + } } Ok(None) } @@ -300,7 +255,7 @@ pub fn version_sub(orig: &str, sub: &str) -> String { impl Display for ToolRequest { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "{}@{}", &self.backend(), self.version()) + write!(f, "{}@{}", &self.ba(), self.version()) } } diff --git a/src/toolset/tool_request_set.rs b/src/toolset/tool_request_set.rs index 7beaed63c0..c63c03dcac 100644 --- a/src/toolset/tool_request_set.rs +++ b/src/toolset/tool_request_set.rs @@ -52,11 +52,11 @@ impl ToolRequestSet { } pub fn add_version(&mut self, tr: ToolRequest, source: &ToolSource) { - let fa = tr.backend(); + let fa = tr.ba(); if !self.tools.contains_key(fa) { self.sources.insert(fa.clone(), source.clone()); } - let list = self.tools.entry(tr.backend().clone()).or_default(); + let list = self.tools.entry(tr.ba().clone()).or_default(); list.push(tr); } @@ -189,11 +189,7 @@ impl ToolRequestSetBuilder { } fn load_runtime_args(&self, trs: &mut ToolRequestSet) -> eyre::Result<()> { - for (_, args) in self - .args - .iter() - .into_group_map_by(|arg| arg.backend.clone()) - { + for (_, args) in self.args.iter().into_group_map_by(|arg| arg.ba.clone()) { let mut arg_ts = ToolRequestSet::new(); for arg in args { if let Some(tvr) = &arg.tvr { @@ -203,10 +199,9 @@ impl ToolRequestSetBuilder { // should default to installing the "latest" version if no version is specified // in mise.toml - if !trs.tools.contains_key(&arg.backend) { + if !trs.tools.contains_key(&arg.ba) { // no active version, so use "latest" - let tr = - ToolRequest::new(arg.backend.clone(), "latest", ToolSource::Argument)?; + let tr = ToolRequest::new(arg.ba.clone(), "latest", ToolSource::Argument)?; arg_ts.add_version(tr, &ToolSource::Argument); } } diff --git a/src/toolset/tool_version.rs b/src/toolset/tool_version.rs index 60ff606148..9b9f02aee5 100644 --- a/src/toolset/tool_version.rs +++ b/src/toolset/tool_version.rs @@ -4,10 +4,9 @@ use std::fs; use std::hash::{Hash, Hasher}; use std::path::PathBuf; -use crate::backend; use crate::backend::ABackend; use crate::cli::args::BackendArg; -use crate::config::{Config, CONFIG}; +use crate::config::Config; #[cfg(windows)] use crate::file; use crate::hash::hash_to_str; @@ -29,23 +28,14 @@ impl ToolVersion { ToolVersion { request, version } } - pub fn resolve(mut request: ToolRequest, opts: &ResolveOptions) -> Result { - if let Some(full) = CONFIG - .get_all_aliases() - .get(&request.backend().short) - .and_then(|a| a.full.clone()) - { - let mut backend = request.backend().clone(); - backend.full = full; - request = request.with_backend(backend); - } + pub fn resolve(request: ToolRequest, opts: &ResolveOptions) -> Result { if opts.use_locked_version { if let Some(v) = request.lockfile_resolve()? { let tv = Self::new(request.clone(), v); return Ok(tv); } } - let backend = backend::get(request.backend()); + let backend = request.ba().backend()?; if let Some(plugin) = backend.plugin() { if !plugin.is_installed() { let tv = Self::new(request.clone(), request.version()); @@ -66,12 +56,12 @@ impl ToolVersion { Ok(tv) } - pub fn backend(&self) -> &BackendArg { - self.request.backend() + pub fn ba(&self) -> &BackendArg { + self.request.ba() } - pub fn get_backend(&self) -> ABackend { - backend::get(self.backend()) + pub fn backend(&self) -> Result { + self.ba().backend() } pub fn install_path(&self) -> PathBuf { @@ -79,14 +69,14 @@ impl ToolVersion { ToolRequest::Path(_, p, ..) => p.to_string_lossy().to_string(), _ => self.tv_pathname(), }; - let path = self.backend().installs_path.join(pathname); + let path = self.ba().installs_path.join(pathname); // handle non-symlinks on windows // TODO: make this a utility function in xx #[cfg(windows)] if path.is_file() { if let Ok(p) = file::read_to_string(&path).map(PathBuf::from) { - let path = self.backend().installs_path.join(p); + let path = self.ba().installs_path.join(p); if path.exists() { return path .absolutize() @@ -98,13 +88,10 @@ impl ToolVersion { path } pub fn cache_path(&self) -> PathBuf { - self.backend().cache_path.join(self.tv_pathname()) + self.ba().cache_path.join(self.tv_pathname()) } pub fn download_path(&self) -> PathBuf { - self.request - .backend() - .downloads_path - .join(self.tv_pathname()) + self.request.ba().downloads_path.join(self.tv_pathname()) } pub fn latest_version(&self) -> Result { let opts = ResolveOptions { @@ -124,11 +111,11 @@ impl ToolVersion { pub fn style(&self) -> String { format!( "{}{}", - style(&self.backend().short).blue().for_stderr(), + style(&self.ba().short).blue().for_stderr(), style(&format!("@{}", &self.version)).for_stderr() ) } - fn tv_pathname(&self) -> String { + pub fn tv_pathname(&self) -> String { match &self.request { ToolRequest::Version { .. } => self.version.to_string(), ToolRequest::Prefix { .. } => self.version.to_string(), @@ -145,7 +132,7 @@ impl ToolVersion { opts: &ResolveOptions, ) -> Result { let config = Config::get(); - let backend = backend::get(request.backend()); + let backend = request.backend()?; let v = config.resolve_alias(&backend, v)?; match v.split_once(':') { Some((ref_type @ ("ref" | "tag" | "branch" | "rev"), r)) => { @@ -210,7 +197,7 @@ impl ToolVersion { v: &str, opts: &ResolveOptions, ) -> Result { - let backend = backend::get(request.backend()); + let backend = request.backend()?; let v = match v { "latest" => backend.latest_version(None)?.unwrap(), _ => Config::get().resolve_alias(&backend, v)?, @@ -220,8 +207,7 @@ impl ToolVersion { } fn resolve_prefix(request: ToolRequest, prefix: &str) -> Result { - let backend = backend::get(request.backend()); - let matches = backend.list_versions_matching(prefix)?; + let matches = request.backend()?.list_versions_matching(prefix)?; let v = match matches.last() { Some(v) => v, None => prefix, @@ -237,7 +223,7 @@ impl ToolVersion { tr: &ToolRequest, ) -> Self { let request = ToolRequest::Ref { - backend: tr.backend().clone(), + backend: tr.ba().clone(), ref_, ref_type, options: opts.clone(), @@ -249,7 +235,7 @@ impl ToolVersion { fn resolve_path(path: PathBuf, tr: &ToolRequest) -> Result { let path = fs::canonicalize(path)?; - let request = ToolRequest::Path(tr.backend().clone(), path, tr.source().clone()); + let request = ToolRequest::Path(tr.ba().clone(), path, tr.source().clone()); let version = request.version(); Ok(Self::new(request, version)) } @@ -257,13 +243,13 @@ impl ToolVersion { impl Display for ToolVersion { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "{}@{}", &self.backend().full, &self.version) + write!(f, "{}@{}", &self.ba().full(), &self.version) } } impl PartialEq for ToolVersion { fn eq(&self, other: &Self) -> bool { - self.backend().full == other.backend().full && self.version == other.version + self.ba() == other.ba() && self.version == other.version } } @@ -277,7 +263,7 @@ impl PartialOrd for ToolVersion { impl Ord for ToolVersion { fn cmp(&self, other: &Self) -> Ordering { - match self.request.backend().full.cmp(&other.backend().full) { + match self.request.ba().cmp(other.ba()) { Ordering::Equal => self.version.cmp(&other.version), o => o, } @@ -286,7 +272,7 @@ impl Ord for ToolVersion { impl Hash for ToolVersion { fn hash(&self, state: &mut H) { - self.backend().full.hash(state); + self.ba().hash(state); self.version.hash(state); } } diff --git a/src/versions_host.rs b/src/versions_host.rs index ecbdc24b62..5dfc492332 100644 --- a/src/versions_host.rs +++ b/src/versions_host.rs @@ -3,7 +3,7 @@ use crate::config::SETTINGS; use crate::http::HTTP_FETCH; use crate::plugins::core::CORE_PLUGINS; use crate::registry::REGISTRY; -use crate::{backend, http, registry}; +use crate::{http, registry}; use once_cell::sync::Lazy; use std::collections::HashSet; use url::Url; @@ -25,7 +25,7 @@ pub fn list_versions(ba: &BackendArg) -> eyre::Result>> { return Ok(None); } // ensure that we're using a default shorthand plugin - if let Some(plugin) = backend::get(ba).plugin() { + if let Some(plugin) = ba.backend()?.plugin() { if let Ok(Some(remote_url)) = plugin.get_remote_url() { let normalized_remote = normalize_remote(&remote_url).unwrap_or("INVALID_URL".into()); let shorthand_remote = REGISTRY