From c46ccc349a27c725f028a3c3112cdeea90837b27 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:57:38 -0500 Subject: [PATCH] feat: add ruby backend (#1657) --- .github/workflows/test.yml | 4 +- .prettierignore | 2 +- .python-versions | 2 + docs/cache-behavior.md | 2 +- docs/cli/direnv.md | 4 +- docs/cli/direnv/activate.md | 4 +- docs/cli/settings/get.md | 2 +- docs/cli/settings/ls.md | 2 +- docs/cli/settings/set.md | 2 +- docs/cli/settings/unset.md | 2 +- docs/configuration.md | 22 +-- docs/dev-tools/backends/gem.md | 52 +++++++ docs/dev-tools/backends/pipx.md | 15 ++ docs/lang/java.md | 4 +- docs/mise.usage.kdl | 2 +- e2e/backend/test_gem_slow | 4 + e2e/plugins/core/test_ruby_build_slow | 7 +- mise.usage.kdl | 16 +- schema/mise.json | 15 ++ schema/mise.plugin.json | 2 +- settings.toml | 35 +++-- src/backend/asdf.rs | 59 ++++---- src/backend/backend_type.rs | 2 + src/backend/gem.rs | 139 ++++++++++++++++++ src/backend/go.rs | 62 +++----- src/backend/mod.rs | 6 +- src/backend/vfox.rs | 4 + ...i__backends__ls__tests__backends_list.snap | 1 + ...se__cli__config__get__tests__toml_get.snap | 2 +- src/cli/direnv/activate.rs | 4 +- src/cli/direnv/mod.rs | 4 +- src/cli/settings/get.rs | 2 +- src/cli/settings/ls.rs | 2 +- src/cli/settings/set.rs | 2 +- src/cli/settings/unset.rs | 2 +- ...legacy_version.rs => idiomatic_version.rs} | 16 +- src/config/config_file/mod.rs | 22 +-- src/config/mod.rs | 40 ++--- src/config/settings.rs | 8 + src/plugins/asdf_plugin.rs | 13 +- src/plugins/core/assets/rubygems_plugin.rb | 2 +- src/plugins/core/bun.rs | 2 +- src/plugins/core/deno.rs | 2 +- src/plugins/core/go.rs | 2 +- src/plugins/core/java.rs | 4 +- src/plugins/core/node.rs | 4 +- src/plugins/core/python.rs | 7 +- src/plugins/core/ruby.rs | 8 +- src/plugins/core/ruby_windows.rs | 4 +- src/plugins/core/zig.rs | 2 +- src/plugins/mise_plugin_toml.rs | 12 +- src/plugins/script_manager.rs | 10 +- src/test.rs | 2 +- src/toolset/tool_source.rs | 20 +-- tasks/test/coverage | 2 +- test/config/config.toml | 2 +- 56 files changed, 468 insertions(+), 207 deletions(-) create mode 100644 .python-versions create mode 100644 docs/dev-tools/backends/gem.md create mode 100644 e2e/backend/test_gem_slow create mode 100644 src/backend/gem.rs rename src/config/config_file/{legacy_version.rs => idiomatic_version.rs} (83%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6ba475f80..41eb443c72 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -353,5 +353,5 @@ jobs: retry_wait_seconds: 30 max_attempts: 2 command: mise test-tool --all - env: - MISE_USE_VERSIONS_HOST: 0 +# env: +# MISE_USE_VERSIONS_HOST: 0 diff --git a/.prettierignore b/.prettierignore index 7a5fbb6bd4..d0744a8360 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,6 @@ CHANGELOG.md .venv -registry/ +aqua-registry/ docs/cli docs/environments.md docs/registry.md diff --git a/.python-versions b/.python-versions new file mode 100644 index 0000000000..ccceb06f27 --- /dev/null +++ b/.python-versions @@ -0,0 +1,2 @@ +3.12 +3.13 diff --git a/docs/cache-behavior.md b/docs/cache-behavior.md index 3a14883ab0..bccaa315fc 100644 --- a/docs/cache-behavior.md +++ b/docs/cache-behavior.md @@ -10,7 +10,7 @@ to be updating, this is a good place to start. ## Plugin/Runtime Cache Each plugin has a cache that's stored in `~/$MISE_CACHE_DIR/`. It stores -the list of versions available for that plugin (`mise ls-remote `), the legacy filenames (see below), +the list of versions available for that plugin (`mise ls-remote `), the idiomatic filenames (see below), the list of aliases, the bin directories within each runtime installation, and the result of running `exec-env` after the runtime was installed. diff --git a/docs/cli/direnv.md b/docs/cli/direnv.md index 288b05d7d8..26c958d380 100644 --- a/docs/cli/direnv.md +++ b/docs/cli/direnv.md @@ -7,9 +7,9 @@ Output direnv function to use mise inside direnv See for more information -Because this generates the legacy files based on currently installed plugins, +Because this generates the idiomatic files based on currently installed plugins, you should run this command after installing new plugins. Otherwise -direnv may not know to update environment variables when legacy file versions change. +direnv may not know to update environment variables when idiomatic file versions change. ## Subcommands diff --git a/docs/cli/direnv/activate.md b/docs/cli/direnv/activate.md index ccdbe5f882..593c321156 100644 --- a/docs/cli/direnv/activate.md +++ b/docs/cli/direnv/activate.md @@ -7,9 +7,9 @@ Output direnv function to use mise inside direnv See for more information -Because this generates the legacy files based on currently installed plugins, +Because this generates the idiomatic files based on currently installed plugins, you should run this command after installing new plugins. Otherwise -direnv may not know to update environment variables when legacy file versions change. +direnv may not know to update environment variables when idiomatic file versions change. Examples: diff --git a/docs/cli/settings/get.md b/docs/cli/settings/get.md index f64a6be74d..91cc149f19 100644 --- a/docs/cli/settings/get.md +++ b/docs/cli/settings/get.md @@ -24,5 +24,5 @@ Use the local config file instead of the global one Examples: - $ mise settings get legacy_version_file + $ mise settings get idiomatic_version_file true diff --git a/docs/cli/settings/ls.md b/docs/cli/settings/ls.md index 95a9401feb..d100c61603 100644 --- a/docs/cli/settings/ls.md +++ b/docs/cli/settings/ls.md @@ -30,7 +30,7 @@ Only display key names for each setting Examples: $ mise settings ls - legacy_version_file = false + idiomatic_version_file = false ... $ mise settings ls python diff --git a/docs/cli/settings/set.md b/docs/cli/settings/set.md index f8221c4593..fa265f4b37 100644 --- a/docs/cli/settings/set.md +++ b/docs/cli/settings/set.md @@ -26,4 +26,4 @@ Use the local config file instead of the global one Examples: - mise settings legacy_version_file=true + mise settings idiomatic_version_file=true diff --git a/docs/cli/settings/unset.md b/docs/cli/settings/unset.md index 9ad17812e1..7b6bf4bcb0 100644 --- a/docs/cli/settings/unset.md +++ b/docs/cli/settings/unset.md @@ -22,4 +22,4 @@ Use the local config file instead of the global one Examples: - mise settings unset legacy_version_file + mise settings unset idiomatic_version_file diff --git a/docs/configuration.md b/docs/configuration.md index a9e101fc68..cba2e5da36 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -157,8 +157,8 @@ python = ['3.10', '3.11'] [settings] # plugins can read the versions files used by other version managers (if enabled by the plugin) # for example, .nvmrc in the case of node's nvm -legacy_version_file = true # enabled by default (unlike asdf) -legacy_version_file_disable_tools = ['python'] # disable for specific tools +idiomatic_version_file = true # enabled by default (unlike asdf) +idiomatic_version_file_disable_tools = ['python'] # disable for specific tools # configure `mise install` to always keep the downloaded archive always_keep_download = false # deleted after install by default @@ -240,38 +240,38 @@ Both `.mise.toml` and `.tool-versions` support "scopes" which modify the behavio be used to express something like "2 versions behind lts" such as `sub-2:lts`. Or 1 minor version behind the latest version: `sub-0.1:latest`. -## Legacy version files +## Idiomatic version files -mise supports "legacy version files" just like asdf. They're language-specific files +mise supports "idiomatic version files" just like asdf. They're language-specific files like `.node-version` and `.python-version`. These are ideal for setting the runtime version of a project without forcing other developers to use a specific tool like mise/asdf. They support aliases, which means you can have an `.nvmrc` file with `lts/hydrogen` and it will work -in mise and nvm. Here are some of the supported legacy version files: +in mise and nvm. Here are some of the supported idiomatic version files: -| Plugin | "Legacy" (Idiomatic) Files | +| Plugin | Idiomatic Files | | --------- | -------------------------------------------------- | | crystal | `.crystal-version` | | elixir | `.exenv-version` | | go | `.go-version`, `go.mod` | | java | `.java-version`, `.sdkmanrc` | | node | `.nvmrc`, `.node-version` | -| python | `.python-version` | +| python | `.python-version`, `.python-versions` | | ruby | `.ruby-version`, `Gemfile` | | terraform | `.terraform-version`, `.packer-version`, `main.tf` | | yarn | `.yarnrc` | In mise these are enabled by default. You can disable them -with `mise settings legacy_version_file=false`. +with `mise settings idiomatic_version_file=false`. There is a performance cost to having these when they're parsed as it's performed by the plugin in -`bin/parse-version-file`. However these are [cached](/cache-behavior) so it's not a huge deal. +`bin/parse-version-file`. However, these are [cached](/cache-behavior) so it's not a huge deal. You may not even notice. ::: info -asdf calls these "legacy version files" so we do too. I think this is a bad name since it implies +asdf called these "legacy version files". I think this was a bad name since it implies that they shouldn't be used—which is definitely not the case IMO. I prefer the term "idiomatic" -version files since they're version files not specific to asdf/mise and can be used by other tools. +version files since they are version files not specific to asdf/mise and can be used by other tools. (`.nvmrc` being a notable exception, which is tied to a specific tool.) ::: diff --git a/docs/dev-tools/backends/gem.md b/docs/dev-tools/backends/gem.md new file mode 100644 index 0000000000..9b586f9a26 --- /dev/null +++ b/docs/dev-tools/backends/gem.md @@ -0,0 +1,52 @@ +# gem Backend + +mise can be used to install CLIs from RubyGems. The code for this is inside of the mise repository at [`./src/backend/gem.rs`](https://github.com/jdx/mise/blob/main/src/backend/pipx.rs). + +## Dependencies + +This relies on having `gem` (provided with ruby) installed. You can install it with or without mise. +Here is how to install `ruby` with mise: + +```sh +mise use -g ruby +``` + +## Usage + +The following installs the latest version of [rubocop](https://rubygems.org/gems/rubocop) and sets it as the active version on PATH: + +```sh +mise use -g gem:rubocop +rubocop --version +``` + +The version will be set in `~/.config/mise/config.toml` with the following format: + +```toml +[tools] +"gem:rubocop" = "latest" +``` + +## Ruby upgrades + +If the ruby version used by a gem package changes, (by mise or system ruby), you may need to +reinstall the gem. This can be done with: + +```sh +mise install -f gem:rubocop +``` + +Or you can reinstall all gems with: + +```sh +mise install -f "gem:*" +``` + +## Settings + +Set these with `mise settings set [VARIABLE] [VALUE]` or by setting the environment variable listed. + + + diff --git a/docs/dev-tools/backends/pipx.md b/docs/dev-tools/backends/pipx.md index 2db98e704d..eeb234964b 100644 --- a/docs/dev-tools/backends/pipx.md +++ b/docs/dev-tools/backends/pipx.md @@ -39,6 +39,21 @@ The version will be set in `~/.config/mise/config.toml` with the following forma "pipx:psf/black" = "latest" ``` +## Python upgrades + +If the python version used by a pipx package changes, (by mise or system python), you may need to +reinstall the package. This can be done with: + +```sh +mise install -f pipx:psf/black +``` + +Or you can reinstall all pipx packages with: + +```sh +mise install -f "pipx:*" +``` + ### Supported Pipx Syntax | Description | Usage | diff --git a/docs/lang/java.md b/docs/lang/java.md index 859bdc09d0..8f61884feb 100644 --- a/docs/lang/java.md +++ b/docs/lang/java.md @@ -58,9 +58,9 @@ sudo ln -s ~/.local/share/mise/installs/java/openjdk-21/Contents /Library/Java/J > Note: Not all distributions of the Java SDK support this integration (e.g liberica). -## Legacy version files +## Idiomatic version files -The Java core plugin supports the legacy version files `.java-version` and `.sdkmanrc`. +The Java core plugin supports the idiomatic version files `.java-version` and `.sdkmanrc`. For `.sdkmanrc` files, mise will try to map the vendor and version to the appropriate version string. For example, the version `20.0.2-tem` will be mapped to `temurin-20.0.2`. Due to Azul's Zulu diff --git a/docs/mise.usage.kdl b/docs/mise.usage.kdl index 9bd336bfe1..d4b40814d0 100644 --- a/docs/mise.usage.kdl +++ b/docs/mise.usage.kdl @@ -227,7 +227,7 @@ cmd "direnv" help="Output direnv function to use mise inside direnv" { See https://mise.jdx.dev/direnv.html for more information -Because this generates the legacy files based on currently installed plugins, +Because this generates the idiomatic files based on currently installed plugins, you should run this command after installing new plugins. Otherwise direnv may not know to update environment variables when legacy file versions change." cmd "envrc" hide=true help="[internal] This is an internal command that writes an envrc file\nfor direnv to consume." diff --git a/e2e/backend/test_gem_slow b/e2e/backend/test_gem_slow new file mode 100644 index 0000000000..036cd83c01 --- /dev/null +++ b/e2e/backend/test_gem_slow @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +mise use ruby +assert "mise x gem:rubocop@1.69.0 -- rubocop --version" "1.69.0" diff --git a/e2e/plugins/core/test_ruby_build_slow b/e2e/plugins/core/test_ruby_build_slow index bdf1589cc9..9aea189da7 100644 --- a/e2e/plugins/core/test_ruby_build_slow +++ b/e2e/plugins/core/test_ruby_build_slow @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Install and build ruby using ruby-build -export MISE_RUBY_INSTALL=0 -latest=$(mise latest ruby) -assert_contains "mise x ruby@$latest -- ruby --version" "ruby $latest" +# tested in test_gem_slow +#export MISE_RUBY_INSTALL=0 +#latest=$(mise latest ruby) +#assert_contains "mise x ruby@$latest -- ruby --version" "ruby $latest" diff --git a/mise.usage.kdl b/mise.usage.kdl index d2c5a3d617..865fdc0a3a 100644 --- a/mise.usage.kdl +++ b/mise.usage.kdl @@ -291,9 +291,9 @@ cmd "direnv" help="Output direnv function to use mise inside direnv" { See https://mise.jdx.dev/direnv.html for more information -Because this generates the legacy files based on currently installed plugins, +Because this generates the idiomatic files based on currently installed plugins, you should run this command after installing new plugins. Otherwise -direnv may not know to update environment variables when legacy file versions change." +direnv may not know to update environment variables when idiomatic file versions change." cmd "envrc" hide=true help="[internal] This is an internal command that writes an envrc file\nfor direnv to consume." cmd "exec" hide=true help="[internal] This is an internal command that writes an envrc file\nfor direnv to consume." cmd "activate" help="Output direnv function to use mise inside direnv" { @@ -301,9 +301,9 @@ direnv may not know to update environment variables when legacy file versions ch See https://mise.jdx.dev/direnv.html for more information -Because this generates the legacy files based on currently installed plugins, +Because this generates the idiomatic files based on currently installed plugins, you should run this command after installing new plugins. Otherwise -direnv may not know to update environment variables when legacy file versions change." +direnv may not know to update environment variables when idiomatic file versions change." after_long_help r"Examples: $ mise direnv activate > ~/.config/direnv/lib/use_mise.sh @@ -1030,7 +1030,7 @@ Note that aliases are also stored in this file but managed separately with `mise aliases get`" after_long_help r"Examples: - $ mise settings get legacy_version_file + $ mise settings get idiomatic_version_file true " flag "-l --local" help="Use the local config file instead of the global one" @@ -1047,7 +1047,7 @@ but managed separately with `mise aliases`" after_long_help r#"Examples: $ mise settings ls - legacy_version_file = false + idiomatic_version_file = false ... $ mise settings ls python @@ -1065,7 +1065,7 @@ but managed separately with `mise aliases`" This modifies the contents of ~/.config/mise/config.toml" after_long_help r"Examples: - $ mise settings legacy_version_file=true + $ mise settings idiomatic_version_file=true " flag "-l --local" help="Use the local config file instead of the global one" arg "" help="The setting to set" @@ -1078,7 +1078,7 @@ This modifies the contents of ~/.config/mise/config.toml" This modifies the contents of ~/.config/mise/config.toml" after_long_help r"Examples: - $ mise settings unset legacy_version_file + $ mise settings unset idiomatic_version_file " flag "-l --local" help="Use the local config file instead of the global one" arg "" help="The setting to remove" diff --git a/schema/mise.json b/schema/mise.json index f85482b349..bc01dfba5a 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -285,6 +285,19 @@ "description": "Timeout in seconds for all HTTP requests in mise.", "type": "string" }, + "idiomatic_version_file": { + "default": true, + "description": "Set to false to disable the idiomatic version files such as .node-version, .ruby-version, etc.", + "type": "boolean" + }, + "idiomatic_version_file_disable_tools": { + "default": [], + "description": "Specific tools to disable idiomatic version files for.", + "type": "array", + "items": { + "type": "string" + } + }, "ignored_config_paths": { "default": [], "description": "This is a list of config paths that mise will ignore.", @@ -301,11 +314,13 @@ "legacy_version_file": { "default": true, "description": "Set to false to disable the idiomatic version files such as .node-version, .ruby-version, etc.", + "deprecated": "Use idiomatic_version_file instead.", "type": "boolean" }, "legacy_version_file_disable_tools": { "default": [], "description": "Specific tools to disable idiomatic version files for.", + "deprecated": "Use idiomatic_version_file_disable_tools instead.", "type": "array", "items": { "type": "string" diff --git a/schema/mise.plugin.json b/schema/mise.plugin.json index 07b8ec8704..f57b551816 100644 --- a/schema/mise.plugin.json +++ b/schema/mise.plugin.json @@ -23,7 +23,7 @@ "additionalProperties": false, "properties": { "data": { - "description": "list of legacy filenames to check for", + "description": "list of idiomatic filenames to check for", "type": "string" } } diff --git a/settings.toml b/settings.toml index 6378a1f4f3..90b4e5d631 100644 --- a/settings.toml +++ b/settings.toml @@ -315,6 +315,28 @@ type = "Duration" default = "30s" description = "Timeout in seconds for all HTTP requests in mise." +[idiomatic_version_file] +env = "MISE_IDIOMATIC_VERSION_FILE" +type = "Bool" +default = true +description = "Set to false to disable the idiomatic version files such as .node-version, .ruby-version, etc." +docs = """ +Plugins can read the versions files used by other version managers (if enabled by the plugin) +for example, `.nvmrc` in the case of node's nvm. See [idiomatic version files](#idiomatic-version-files) +for more +information. + +Set to "false" to disable idiomatic version file parsing. +""" + +[idiomatic_version_file_disable_tools] +env = "MISE_IDIOMATIC_VERSION_FILE_DISABLE_TOOLS" +type = "ListString" +rust_type = "BTreeSet" +default = [] +parse_env = "list_by_comma" +description = "Specific tools to disable idiomatic version files for." + [ignored_config_paths] env = "MISE_IGNORED_CONFIG_PATHS" type = "ListPath" @@ -330,20 +352,13 @@ rust_type = "usize" default = 4 description = "How many jobs to run concurrently such as tool installs." -# TODO: rename to "idiomatic_version_file" [legacy_version_file] env = "MISE_LEGACY_VERSION_FILE" type = "Bool" default = true +deprecated = "Use idiomatic_version_file instead." description = "Set to false to disable the idiomatic version files such as .node-version, .ruby-version, etc." -docs = """ -Plugins can read the versions files used by other version managers (if enabled by the plugin) -for example, `.nvmrc` in the case of node's nvm. See [legacy version files](#legacy-version-files) -for more -information. - -Set to "false" to disable legacy version file parsing. -""" +hide = true [legacy_version_file_disable_tools] env = "MISE_LEGACY_VERSION_FILE_DISABLE_TOOLS" @@ -351,7 +366,9 @@ type = "ListString" rust_type = "BTreeSet" default = [] parse_env = "list_by_comma" +deprecated = "Use idiomatic_version_file_disable_tools instead." description = "Specific tools to disable idiomatic version files for." +hide = true [libgit2] env = "MISE_LIBGIT2" diff --git a/src/backend/asdf.rs b/src/backend/asdf.rs index 4fab577b80..b48d558003 100644 --- a/src/backend/asdf.rs +++ b/src/backend/asdf.rs @@ -15,7 +15,7 @@ use crate::hash::hash_to_str; use crate::install_context::InstallContext; use crate::plugins::asdf_plugin::AsdfPlugin; use crate::plugins::mise_plugin_toml::MisePluginToml; -use crate::plugins::Script::{Download, ExecEnv, Install, ParseLegacyFile}; +use crate::plugins::Script::{Download, ExecEnv, Install, ParseIdiomaticFile}; use crate::plugins::{Plugin, PluginType, Script, ScriptManager}; use crate::toolset::{ToolRequest, ToolVersion, Toolset}; use crate::ui::progress_report::SingleReport; @@ -36,7 +36,7 @@ pub struct AsdfBackend { remote_version_cache: CacheManager>, latest_stable_cache: CacheManager>, alias_cache: CacheManager>, - legacy_filename_cache: CacheManager>, + idiomatic_filename_cache: CacheManager>, } impl AsdfBackend { @@ -69,8 +69,8 @@ impl AsdfBackend { .with_fresh_file(plugin_path.clone()) .with_fresh_file(plugin_path.join("bin/list-aliases")) .build(), - legacy_filename_cache: CacheManagerBuilder::new( - ba.cache_path.join("legacy_filenames.msgpack.z"), + idiomatic_filename_cache: CacheManagerBuilder::new( + ba.cache_path.join("idiomatic_filenames.msgpack.z"), ) .with_fresh_file(plugin_path.clone()) .with_fresh_file(plugin_path.join("bin/list-legacy-filenames")) @@ -87,28 +87,28 @@ impl AsdfBackend { &*self.plugin } - 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()? { + fn fetch_cached_idiomatic_file(&self, idiomatic_file: &Path) -> Result> { + let fp = self.idiomatic_cache_file_path(idiomatic_file); + if !fp.exists() || fp.metadata()?.modified()? < idiomatic_file.metadata()?.modified()? { return Ok(None); } Ok(Some(fs::read_to_string(fp)?.trim().into())) } - fn legacy_cache_file_path(&self, legacy_file: &Path) -> PathBuf { + fn idiomatic_cache_file_path(&self, idiomatic_file: &Path) -> PathBuf { self.ba .cache_path - .join("legacy") + .join("idiomatic") .join(&self.name) - .join(hash_to_str(&legacy_file.to_string_lossy())) + .join(hash_to_str(&idiomatic_file.to_string_lossy())) .with_extension("txt") } - fn write_legacy_cache(&self, legacy_file: &Path, legacy_version: &str) -> Result<()> { - let fp = self.legacy_cache_file_path(legacy_file); + fn write_idiomatic_cache(&self, idiomatic_file: &Path, idiomatic_version: &str) -> Result<()> { + let fp = self.idiomatic_cache_file_path(idiomatic_file); file::create_dir_all(fp.parent().unwrap())?; - file::write(fp, legacy_version)?; + file::write(fp, idiomatic_version)?; Ok(()) } @@ -288,39 +288,42 @@ impl Backend for AsdfBackend { Ok(aliases) } - fn legacy_filenames(&self) -> Result> { - if let Some(data) = &self.toml.list_legacy_filenames.data { - return Ok(self.plugin.parse_legacy_filenames(data)); + fn idiomatic_filenames(&self) -> Result> { + if let Some(data) = &self.toml.list_idiomatic_filenames.data { + return Ok(self.plugin.parse_idiomatic_filenames(data)); } - if !self.plugin.has_list_legacy_filenames_script() { + if !self.plugin.has_list_idiomatic_filenames_script() { return Ok(vec![]); } - self.legacy_filename_cache - .get_or_try_init(|| self.plugin.fetch_legacy_filenames()) + self.idiomatic_filename_cache + .get_or_try_init(|| self.plugin.fetch_idiomatic_filenames()) .wrap_err_with(|| { eyre!( - "Failed fetching legacy filenames for plugin {}", + "Failed fetching idiomatic filenames for plugin {}", style(&self.name).blue().for_stderr(), ) }) .cloned() } - fn parse_legacy_file(&self, legacy_file: &Path) -> Result { - if let Some(cached) = self.fetch_cached_legacy_file(legacy_file)? { + fn parse_idiomatic_file(&self, idiomatic_file: &Path) -> Result { + if let Some(cached) = self.fetch_cached_idiomatic_file(idiomatic_file)? { return Ok(cached); } - trace!("parsing legacy file: {}", legacy_file.to_string_lossy()); - let script = ParseLegacyFile(legacy_file.to_string_lossy().into()); - let legacy_version = match self.plugin.script_man.script_exists(&script) { + trace!( + "parsing idiomatic file: {}", + idiomatic_file.to_string_lossy() + ); + let script = ParseIdiomaticFile(idiomatic_file.to_string_lossy().into()); + let idiomatic_version = match self.plugin.script_man.script_exists(&script) { true => self.plugin.script_man.read(&script)?, - false => fs::read_to_string(legacy_file)?, + false => fs::read_to_string(idiomatic_file)?, } .trim() .to_string(); - self.write_legacy_cache(legacy_file, &legacy_version)?; - Ok(legacy_version) + self.write_idiomatic_cache(idiomatic_file, &idiomatic_version)?; + Ok(idiomatic_version) } fn plugin(&self) -> Option<&dyn Plugin> { diff --git a/src/backend/backend_type.rs b/src/backend/backend_type.rs index 03e4f94466..e71505f3bc 100644 --- a/src/backend/backend_type.rs +++ b/src/backend/backend_type.rs @@ -19,6 +19,7 @@ pub enum BackendType { Asdf, Cargo, Core, + Gem, Go, Npm, Pipx, @@ -43,6 +44,7 @@ impl BackendType { "asdf" => BackendType::Asdf, "cargo" => BackendType::Cargo, "core" => BackendType::Core, + "gem" => BackendType::Gem, "go" => BackendType::Go, "npm" => BackendType::Npm, "pipx" => BackendType::Pipx, diff --git a/src/backend/gem.rs b/src/backend/gem.rs new file mode 100644 index 0000000000..6adf085e6b --- /dev/null +++ b/src/backend/gem.rs @@ -0,0 +1,139 @@ +use crate::backend::backend_type::BackendType; +use crate::backend::Backend; +use crate::cli::args::BackendArg; +use crate::cmd::CmdLineRunner; +use crate::config::SETTINGS; +use crate::file; +use crate::http::HTTP_FETCH; +use crate::install_context::InstallContext; +use crate::toolset::ToolVersion; +use indoc::formatdoc; +use std::fmt::Debug; +use url::Url; + +#[derive(Debug)] +pub struct GemBackend { + ba: BackendArg, +} + +impl Backend for GemBackend { + fn get_type(&self) -> BackendType { + BackendType::Gem + } + + fn ba(&self) -> &BackendArg { + &self.ba + } + + fn get_dependencies(&self) -> eyre::Result> { + Ok(vec!["ruby"]) + } + + fn _list_remote_versions(&self) -> eyre::Result> { + // The `gem list` command does not supporting listing versions as json output + // so we use the rubygems.org api to get the list of versions. + let raw = HTTP_FETCH.get_text(get_gem_url(&self.tool_name())?)?; + let gem_versions: Vec = serde_json::from_str(&raw)?; + let mut versions: Vec = vec![]; + for version in gem_versions.iter().rev() { + versions.push(version.number.clone()); + } + Ok(versions) + } + + fn install_version_impl( + &self, + ctx: &InstallContext, + tv: ToolVersion, + ) -> eyre::Result { + SETTINGS.ensure_experimental("gem backend")?; + + CmdLineRunner::new("gem") + .arg("install") + .arg(self.tool_name()) + .arg("--version") + .arg(&tv.version) + .arg("--install-dir") + .arg(tv.install_path().join("libexec")) + // NOTE: Use `#!/usr/bin/env ruby` may cause some gems to not work properly + // using a different ruby then they were installed with. Therefore we + // we avoid the use of `--env-shebang` for now. However, this means that + // uninstalling the ruby version used to install the gem will break the + // gem. We should find a way to fix this. + // .arg("--env-shebang") + .with_pr(ctx.pr.as_ref()) + .envs(self.dependency_env()?) + .execute()?; + + // We install the gem to {install_path}/libexec and create a wrapper script for each executable + // in {install_path}/bin that sets GEM_HOME and executes the gem installed + env_script_all_bin_files(&tv.install_path())?; + + Ok(tv) + } +} + +impl GemBackend { + pub fn from_arg(ba: BackendArg) -> Self { + Self { ba } + } +} + +fn get_gem_url(n: &str) -> eyre::Result { + Ok(format!("https://rubygems.org/api/v1/versions/{n}.json").parse()?) +} + +fn env_script_all_bin_files(install_path: &std::path::Path) -> eyre::Result { + let install_bin_path = install_path.join("bin"); + let install_libexec_path = install_path.join("libexec"); + + match std::fs::create_dir_all(&install_bin_path) { + Ok(_) => {} + Err(e) => { + return Err(eyre::eyre!("couldn't create directory: {}", e)); + } + } + + get_gem_executables(install_path)? + .into_iter() + .for_each(|path| { + let exec_path = install_bin_path.join(path.file_name().unwrap()); + file::write( + &exec_path, + formatdoc!( + r#" + #!/usr/bin/env bash + GEM_HOME="{gem_home}" exec {gem_exec_path} "$@" + "#, + gem_home = install_libexec_path.to_str().unwrap(), + gem_exec_path = path.to_str().unwrap(), + ), + ) + .unwrap(); + file::make_executable(&exec_path).unwrap(); + }); + + Ok(true) +} + +fn get_gem_executables(install_path: &std::path::Path) -> eyre::Result> { + // TODO: Find a way to get the list of executables from the gemspec of the + // installed gem rather than just listing the files in the bin directory. + let install_libexec_bin_path = install_path.join("libexec/bin"); + let mut files = vec![]; + + for entry in std::fs::read_dir(install_libexec_bin_path)? { + let entry = entry?; + let path = entry.path(); + if file::is_executable(&path) { + files.push(path); + } + } + + Ok(files) +} + +#[derive(Debug, serde::Deserialize)] +struct GemVersion { + number: String, +} diff --git a/src/backend/go.rs b/src/backend/go.rs index 7196262fe7..497f865f4e 100644 --- a/src/backend/go.rs +++ b/src/backend/go.rs @@ -2,17 +2,15 @@ use std::fmt::Debug; use crate::backend::backend_type::BackendType; use crate::backend::Backend; -use crate::cache::{CacheManager, CacheManagerBuilder}; use crate::cli::args::BackendArg; use crate::cmd::CmdLineRunner; -use crate::config::{Settings, SETTINGS}; +use crate::config::SETTINGS; use crate::install_context::InstallContext; use crate::toolset::ToolVersion; #[derive(Debug)] pub struct GoBackend { ba: BackendArg, - remote_version_cache: CacheManager>, } impl Backend for GoBackend { @@ -29,33 +27,29 @@ impl Backend for GoBackend { } fn _list_remote_versions(&self) -> eyre::Result> { - self.remote_version_cache - .get_or_try_init(|| { - let mut mod_path = Some(self.tool_name()); - - while let Some(cur_mod_path) = mod_path { - let res = cmd!("go", "list", "-m", "-versions", "-json", &cur_mod_path) - .full_env(self.dependency_env()?) - .read(); - if let Ok(raw) = res { - let res = serde_json::from_str::(&raw); - if let Ok(mut mod_info) = res { - // remove the leading v from the versions - mod_info.versions = mod_info - .versions - .into_iter() - .map(|v| v.trim_start_matches('v').to_string()) - .collect(); - return Ok(mod_info.versions); - } - }; - - mod_path = trim_after_last_slash(cur_mod_path); + let mut mod_path = Some(self.tool_name()); + + while let Some(cur_mod_path) = mod_path { + let res = cmd!("go", "list", "-m", "-versions", "-json", &cur_mod_path) + .full_env(self.dependency_env()?) + .read(); + if let Ok(raw) = res { + let res = serde_json::from_str::(&raw); + if let Ok(mut mod_info) = res { + // remove the leading v from the versions + mod_info.versions = mod_info + .versions + .into_iter() + .map(|v| v.trim_start_matches('v').to_string()) + .collect(); + return Ok(mod_info.versions); } + }; - Ok(vec![]) - }) - .cloned() + mod_path = trim_after_last_slash(cur_mod_path); + } + + Ok(vec![]) } fn install_version_impl( @@ -63,8 +57,7 @@ impl Backend for GoBackend { ctx: &InstallContext, tv: ToolVersion, ) -> eyre::Result { - let settings = Settings::get(); - settings.ensure_experimental("go backend")?; + SETTINGS.ensure_experimental("go backend")?; let version = if tv.version.starts_with("v") { warn!("usage of a 'v' prefix in the version is discouraged"); @@ -94,14 +87,7 @@ impl Backend for GoBackend { impl GoBackend { pub fn from_arg(ba: BackendArg) -> Self { - Self { - remote_version_cache: CacheManagerBuilder::new( - ba.cache_path.join("remote_versions.msgpack.z"), - ) - .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()) - .build(), - ba, - } + Self { ba } } } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 57c35da503..2266f80b8c 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -33,6 +33,7 @@ pub mod asdf; pub mod backend_type; pub mod cargo; mod external_plugin_cache; +pub mod gem; pub mod go; pub mod npm; pub mod pipx; @@ -121,6 +122,7 @@ pub fn arg_to_backend(ba: BackendArg) -> Option { 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::Gem => Some(Arc::new(gem::GemBackend::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))), @@ -366,10 +368,10 @@ pub trait Backend: Debug + Send + Sync { fn get_aliases(&self) -> eyre::Result> { Ok(BTreeMap::new()) } - fn legacy_filenames(&self) -> eyre::Result> { + fn idiomatic_filenames(&self) -> eyre::Result> { Ok(vec![]) } - fn parse_legacy_file(&self, path: &Path) -> eyre::Result { + fn parse_idiomatic_file(&self, path: &Path) -> eyre::Result { let contents = file::read_to_string(path)?; Ok(contents.trim().to_string()) } diff --git a/src/backend/vfox.rs b/src/backend/vfox.rs index 134dde80e0..9d2bd9edf3 100644 --- a/src/backend/vfox.rs +++ b/src/backend/vfox.rs @@ -95,6 +95,10 @@ impl Backend for VfoxBackend { .filter(|(k, _)| k.to_uppercase() != "PATH") .collect()) } + + fn plugin(&self) -> Option<&dyn Plugin> { + Some(&*self.plugin) + } } impl VfoxBackend { 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 a81ad0bb7a..6d629a9921 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 @@ -7,6 +7,7 @@ aqua asdf cargo core +gem go npm pipx diff --git a/src/cli/config/snapshots/mise__cli__config__get__tests__toml_get.snap b/src/cli/config/snapshots/mise__cli__config__get__tests__toml_get.snap index 4a3bca69d3..6e8e18d523 100644 --- a/src/cli/config/snapshots/mise__cli__config__get__tests__toml_get.snap +++ b/src/cli/config/snapshots/mise__cli__config__get__tests__toml_get.snap @@ -12,8 +12,8 @@ TEST_ENV_VAR = "test-123" [settings] always_keep_download = true always_keep_install = true +idiomatic_version_file = true jobs = 2 -legacy_version_file = true plugin_autoupdate_last_check_duration = "20m" [tasks.configtask] diff --git a/src/cli/direnv/activate.rs b/src/cli/direnv/activate.rs index 41959d9ad5..709234d6f5 100644 --- a/src/cli/direnv/activate.rs +++ b/src/cli/direnv/activate.rs @@ -5,9 +5,9 @@ use indoc::indoc; /// /// See https://mise.jdx.dev/direnv.html for more information /// -/// Because this generates the legacy files based on currently installed plugins, +/// Because this generates the idiomatic files based on currently installed plugins, /// you should run this command after installing new plugins. Otherwise -/// direnv may not know to update environment variables when legacy file versions change. +/// direnv may not know to update environment variables when idiomatic file versions change. #[derive(Debug, clap::Args)] #[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)] pub struct DirenvActivate {} diff --git a/src/cli/direnv/mod.rs b/src/cli/direnv/mod.rs index 3db7c66f27..24ed781d8d 100644 --- a/src/cli/direnv/mod.rs +++ b/src/cli/direnv/mod.rs @@ -11,9 +11,9 @@ mod exec; /// /// See https://mise.jdx.dev/direnv.html for more information /// -/// Because this generates the legacy files based on currently installed plugins, +/// Because this generates the idiomatic files based on currently installed plugins, /// you should run this command after installing new plugins. Otherwise -/// direnv may not know to update environment variables when legacy file versions change. +/// direnv may not know to update environment variables when idiomatic file versions change. #[derive(Debug, clap::Args)] #[clap(verbatim_doc_comment)] pub struct Direnv { diff --git a/src/cli/settings/get.rs b/src/cli/settings/get.rs index 201cab61d2..0a51748f29 100644 --- a/src/cli/settings/get.rs +++ b/src/cli/settings/get.rs @@ -53,7 +53,7 @@ impl SettingsGet { static AFTER_LONG_HELP: &str = color_print::cstr!( r#"Examples: - $ mise settings get legacy_version_file + $ mise settings get idiomatic_version_file true "# ); diff --git a/src/cli/settings/ls.rs b/src/cli/settings/ls.rs index 03752dc523..f96f8f00c5 100644 --- a/src/cli/settings/ls.rs +++ b/src/cli/settings/ls.rs @@ -67,7 +67,7 @@ static AFTER_LONG_HELP: &str = color_print::cstr!( r#"Examples: $ mise settings ls - legacy_version_file = false + idiomatic_version_file = false ... $ mise settings ls python diff --git a/src/cli/settings/set.rs b/src/cli/settings/set.rs index f475f8c558..ab18677546 100644 --- a/src/cli/settings/set.rs +++ b/src/cli/settings/set.rs @@ -110,6 +110,6 @@ fn parse_duration(value: &str) -> Result { static AFTER_LONG_HELP: &str = color_print::cstr!( r#"Examples: - $ mise settings legacy_version_file=true + $ mise settings idiomatic_version_file=true "# ); diff --git a/src/cli/settings/unset.rs b/src/cli/settings/unset.rs index 693fff7c73..1391628cb2 100644 --- a/src/cli/settings/unset.rs +++ b/src/cli/settings/unset.rs @@ -43,6 +43,6 @@ impl SettingsUnset { static AFTER_LONG_HELP: &str = color_print::cstr!( r#"Examples: - $ mise settings unset legacy_version_file + $ mise settings unset idiomatic_version_file "# ); diff --git a/src/config/config_file/legacy_version.rs b/src/config/config_file/idiomatic_version.rs similarity index 83% rename from src/config/config_file/legacy_version.rs rename to src/config/config_file/idiomatic_version.rs index 55eb1a5e91..bbde740365 100644 --- a/src/config/config_file/legacy_version.rs +++ b/src/config/config_file/idiomatic_version.rs @@ -8,12 +8,12 @@ use crate::config::config_file::ConfigFile; use crate::toolset::{ToolRequest, ToolRequestSet, ToolSource}; #[derive(Debug, Clone)] -pub struct LegacyVersionFile { +pub struct IdiomaticVersionFile { path: PathBuf, tools: ToolRequestSet, } -impl LegacyVersionFile { +impl IdiomaticVersionFile { pub fn init(path: PathBuf) -> Self { Self { path, @@ -22,11 +22,11 @@ impl LegacyVersionFile { } pub fn parse(path: PathBuf, plugins: BackendList) -> Result { - let source = ToolSource::LegacyVersionFile(path.clone()); + let source = ToolSource::IdiomaticVersionFile(path.clone()); let mut tools = ToolRequestSet::new(); for plugin in plugins { - let version = plugin.parse_legacy_file(&path)?; + let version = plugin.parse_idiomatic_file(&path)?; for version in version.split_whitespace() { let tr = ToolRequest::new(plugin.ba().clone(), version, source.clone())?; tools.add_version(tr, &source); @@ -37,11 +37,11 @@ impl LegacyVersionFile { } pub fn from_file(path: &Path) -> Result { - trace!("parsing legacy version: {}", path.display()); + trace!("parsing idiomatic version: {}", path.display()); let file_name = &path.file_name().unwrap().to_string_lossy().to_string(); let tools = backend::list() .into_iter() - .filter(|f| match f.legacy_filenames() { + .filter(|f| match f.idiomatic_filenames() { Ok(f) => f.contains(file_name), Err(_) => false, }) @@ -50,7 +50,7 @@ impl LegacyVersionFile { } } -impl ConfigFile for LegacyVersionFile { +impl ConfigFile for IdiomaticVersionFile { fn get_path(&self) -> &Path { self.path.as_path() } @@ -80,7 +80,7 @@ impl ConfigFile for LegacyVersionFile { } fn source(&self) -> ToolSource { - ToolSource::LegacyVersionFile(self.path.clone()) + ToolSource::IdiomaticVersionFile(self.path.clone()) } fn to_tool_request_set(&self) -> Result { diff --git a/src/config/config_file/mod.rs b/src/config/config_file/mod.rs index 3a5eb0015f..f21bb4305c 100644 --- a/src/config/config_file/mod.rs +++ b/src/config/config_file/mod.rs @@ -7,8 +7,8 @@ use std::sync::{Mutex, Once}; use eyre::eyre; use eyre::Result; +use idiomatic_version::IdiomaticVersionFile; use indexmap::IndexMap; -use legacy_version::LegacyVersionFile; use once_cell::sync::Lazy; use serde_derive::Deserialize; use versions::Versioning; @@ -27,7 +27,7 @@ use crate::toolset::{ToolRequest, ToolRequestSet, ToolSource, ToolVersionList, T use crate::ui::{prompt, style}; use crate::{backend, config, dirs, env, file}; -pub mod legacy_version; +pub mod idiomatic_version; pub mod mise_toml; pub mod toml; pub mod tool_versions; @@ -36,7 +36,7 @@ pub mod tool_versions; pub enum ConfigFileType { MiseToml, ToolVersions, - LegacyVersion, + IdiomaticVersion, } pub trait ConfigFile: Debug + Send + Sync { @@ -190,8 +190,8 @@ fn init(path: &Path) -> Box { match detect_config_file_type(path) { Some(ConfigFileType::MiseToml) => Box::new(MiseToml::init(path)), Some(ConfigFileType::ToolVersions) => Box::new(ToolVersions::init(path)), - Some(ConfigFileType::LegacyVersion) => { - Box::new(LegacyVersionFile::init(path.to_path_buf())) + Some(ConfigFileType::IdiomaticVersion) => { + Box::new(IdiomaticVersionFile::init(path.to_path_buf())) } _ => panic!("Unknown config file type: {}", path.display()), } @@ -214,7 +214,9 @@ pub fn parse(path: &Path) -> eyre::Result> { match detect_config_file_type(path) { Some(ConfigFileType::MiseToml) => Ok(Box::new(MiseToml::from_file(path)?)), Some(ConfigFileType::ToolVersions) => Ok(Box::new(ToolVersions::from_file(path)?)), - Some(ConfigFileType::LegacyVersion) => Ok(Box::new(LegacyVersionFile::from_file(path)?)), + Some(ConfigFileType::IdiomaticVersion) => { + Ok(Box::new(IdiomaticVersionFile::from_file(path)?)) + } #[allow(clippy::box_default)] _ => Ok(Box::new(MiseToml::default())), } @@ -420,9 +422,9 @@ fn detect_config_file_type(path: &Path) -> Option { } f if backend::list() .iter() - .any(|b| b.legacy_filenames().unwrap().contains(&f.to_string())) => + .any(|b| b.idiomatic_filenames().unwrap().contains(&f.to_string())) => { - Some(ConfigFileType::LegacyVersion) + Some(ConfigFileType::IdiomaticVersion) } _ => None, } @@ -471,11 +473,11 @@ mod tests { reset(); assert_eq!( detect_config_file_type(Path::new("/foo/bar/.nvmrc")), - Some(ConfigFileType::LegacyVersion) + Some(ConfigFileType::IdiomaticVersion) ); assert_eq!( detect_config_file_type(Path::new("/foo/bar/.ruby-version")), - Some(ConfigFileType::LegacyVersion) + Some(ConfigFileType::IdiomaticVersion) ); assert_eq!( detect_config_file_type(Path::new("/foo/bar/.test-tool-versions")), diff --git a/src/config/mod.rs b/src/config/mod.rs index 8e6ac15100..4f0dc7dfa3 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -14,7 +14,7 @@ use walkdir::WalkDir; use crate::backend::ABackend; use crate::cli::version; -use crate::config::config_file::legacy_version::LegacyVersionFile; +use crate::config::config_file::idiomatic_version::IdiomaticVersionFile; use crate::config::config_file::mise_toml::{MiseToml, Tasks}; use crate::config::config_file::ConfigFile; use crate::config::env_directive::EnvResults; @@ -83,9 +83,9 @@ impl Config { } pub fn load() -> Result { time!("load start"); - let legacy_files = load_legacy_files(); - time!("load legacy_files"); - let config_filenames = legacy_files + let idiomatic_files = load_idiomatic_files(); + time!("load idiomatic_files"); + let config_filenames = idiomatic_files .keys() .chain(DEFAULT_CONFIG_FILENAMES.iter()) .cloned() @@ -94,7 +94,7 @@ impl Config { let config_paths = load_config_paths(&config_filenames, false); time!("load config_paths"); trace!("config_paths: {config_paths:?}"); - let config_files = load_all_config_files(&config_paths, &legacy_files)?; + let config_files = load_all_config_files(&config_paths, &idiomatic_files)?; time!("load config_files"); let mut config = Self { @@ -581,18 +581,18 @@ fn get_project_root(config_files: &ConfigMap) -> Option { project_root } -fn load_legacy_files() -> BTreeMap> { - if !SETTINGS.legacy_version_file { +fn load_idiomatic_files() -> BTreeMap> { + if !SETTINGS.idiomatic_version_file { return BTreeMap::new(); } - let legacy = backend::list() + let idiomatic = backend::list() .into_par_iter() .filter(|tool| { !SETTINGS - .legacy_version_file_disable_tools + .idiomatic_version_file_disable_tools .contains(tool.id()) }) - .filter_map(|tool| match tool.legacy_filenames() { + .filter_map(|tool| match tool.idiomatic_filenames() { Ok(filenames) => Some( filenames .iter() @@ -609,14 +609,14 @@ fn load_legacy_files() -> BTreeMap> { .flatten() .collect::>(); - let mut legacy_filenames = BTreeMap::new(); - for (filename, plugin) in legacy { - legacy_filenames + let mut idiomatic_filenames = BTreeMap::new(); + for (filename, plugin) in idiomatic { + idiomatic_filenames .entry(filename) .or_insert_with(Vec::new) .push(plugin); } - legacy_filenames + idiomatic_filenames } pub static LOCAL_CONFIG_FILENAMES: Lazy> = Lazy::new(|| { @@ -786,7 +786,7 @@ pub fn local_toml_config_path() -> PathBuf { fn load_all_config_files( config_filenames: &[PathBuf], - legacy_filenames: &BTreeMap>, + idiomatic_filenames: &BTreeMap>, ) -> Result { Ok(config_filenames .iter() @@ -794,7 +794,7 @@ fn load_all_config_files( .collect_vec() .into_par_iter() .map(|f| { - let cf = parse_config_file(f, legacy_filenames).wrap_err_with(|| { + let cf = parse_config_file(f, idiomatic_filenames).wrap_err_with(|| { format!( "error parsing config file: {}", style::ebold(display_path(f)) @@ -812,16 +812,16 @@ fn load_all_config_files( fn parse_config_file( f: &PathBuf, - legacy_filenames: &BTreeMap>, + idiomatic_filenames: &BTreeMap>, ) -> Result> { - match legacy_filenames.get(&f.file_name().unwrap().to_string_lossy().to_string()) { + match idiomatic_filenames.get(&f.file_name().unwrap().to_string_lossy().to_string()) { Some(plugin) => { - trace!("legacy version file: {}", display_path(f)); + trace!("idiomatic version file: {}", display_path(f)); let tools = backend::list() .into_iter() .filter(|f| plugin.contains(&f.to_string())) .collect::>(); - LegacyVersionFile::parse(f.into(), tools).map(|f| Box::new(f) as Box) + IdiomaticVersionFile::parse(f.into(), tools).map(|f| Box::new(f) as Box) } None => config_file::parse(f), } diff --git a/src/config/settings.rs b/src/config/settings.rs index e360959277..d79ce247b4 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -115,6 +115,14 @@ impl Settings { sb = sb.preloaded(DEFAULT_SETTINGS.clone()); settings = sb.load()?; + if !settings.legacy_version_file { + settings.idiomatic_version_file = false; + } + if !settings.idiomatic_version_file_disable_tools.is_empty() { + settings + .disable_tools + .extend(settings.idiomatic_version_file_disable_tools.clone()); + } if settings.raw { settings.jobs = 1; } diff --git a/src/plugins/asdf_plugin.rs b/src/plugins/asdf_plugin.rs index 45b3fb95b5..fc80403627 100644 --- a/src/plugins/asdf_plugin.rs +++ b/src/plugins/asdf_plugin.rs @@ -142,18 +142,19 @@ impl AsdfPlugin { }) } - pub fn fetch_legacy_filenames(&self) -> eyre::Result> { - let stdout = self.script_man.read(&Script::ListLegacyFilenames)?; - Ok(self.parse_legacy_filenames(&stdout)) + pub fn fetch_idiomatic_filenames(&self) -> eyre::Result> { + let stdout = self.script_man.read(&Script::ListIdiomaticFilenames)?; + Ok(self.parse_idiomatic_filenames(&stdout)) } - pub fn parse_legacy_filenames(&self, data: &str) -> Vec { + pub fn parse_idiomatic_filenames(&self, data: &str) -> Vec { data.split_whitespace().map(|v| v.into()).collect() } pub fn has_list_alias_script(&self) -> bool { self.script_man.script_exists(&Script::ListAliases) } - pub fn has_list_legacy_filenames_script(&self) -> bool { - self.script_man.script_exists(&Script::ListLegacyFilenames) + pub fn has_list_idiomatic_filenames_script(&self) -> bool { + self.script_man + .script_exists(&Script::ListIdiomaticFilenames) } pub fn has_latest_stable_script(&self) -> bool { self.script_man.script_exists(&Script::LatestStable) diff --git a/src/plugins/core/assets/rubygems_plugin.rb b/src/plugins/core/assets/rubygems_plugin.rb index 5c2ccd04cd..5c5b10b824 100644 --- a/src/plugins/core/assets/rubygems_plugin.rb +++ b/src/plugins/core/assets/rubygems_plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true def debug? - !!ENV["MISE_DEBUG"] + ENV["MISE_DEBUG"] == "true" end def log_debug(msg) diff --git a/src/plugins/core/bun.rs b/src/plugins/core/bun.rs index 82e77940ad..45100f98d7 100644 --- a/src/plugins/core/bun.rs +++ b/src/plugins/core/bun.rs @@ -94,7 +94,7 @@ impl Backend for BunPlugin { Ok(versions) } - fn legacy_filenames(&self) -> Result> { + fn idiomatic_filenames(&self) -> Result> { Ok(vec![".bun-version".into()]) } diff --git a/src/plugins/core/deno.rs b/src/plugins/core/deno.rs index a38d61e4b9..105e8573b6 100644 --- a/src/plugins/core/deno.rs +++ b/src/plugins/core/deno.rs @@ -104,7 +104,7 @@ impl Backend for DenoPlugin { Ok(versions) } - fn legacy_filenames(&self) -> Result> { + fn idiomatic_filenames(&self) -> Result> { Ok(vec![".deno-version".into()]) } diff --git a/src/plugins/core/go.rs b/src/plugins/core/go.rs index 3263e3db6b..9dfb20f532 100644 --- a/src/plugins/core/go.rs +++ b/src/plugins/core/go.rs @@ -193,7 +193,7 @@ impl Backend for GoPlugin { Ok(versions) }) } - fn legacy_filenames(&self) -> eyre::Result> { + fn idiomatic_filenames(&self) -> eyre::Result> { Ok(vec![".go-version".into()]) } diff --git a/src/plugins/core/java.rs b/src/plugins/core/java.rs index 10777f84e7..4843693fd3 100644 --- a/src/plugins/core/java.rs +++ b/src/plugins/core/java.rs @@ -332,11 +332,11 @@ impl Backend for JavaPlugin { Ok(aliases) } - fn legacy_filenames(&self) -> Result> { + fn idiomatic_filenames(&self) -> Result> { Ok(vec![".java-version".into(), ".sdkmanrc".into()]) } - fn parse_legacy_file(&self, path: &Path) -> Result { + fn parse_idiomatic_file(&self, path: &Path) -> Result { let contents = file::read_to_string(path)?; if path.file_name() == Some(".sdkmanrc".as_ref()) { let version = contents diff --git a/src/plugins/core/node.rs b/src/plugins/core/node.rs index 9b27ea2141..609865be2c 100644 --- a/src/plugins/core/node.rs +++ b/src/plugins/core/node.rs @@ -348,11 +348,11 @@ impl Backend for NodePlugin { Ok(aliases) } - fn legacy_filenames(&self) -> Result> { + fn idiomatic_filenames(&self) -> Result> { Ok(vec![".node-version".into(), ".nvmrc".into()]) } - fn parse_legacy_file(&self, path: &Path) -> Result { + fn parse_idiomatic_file(&self, path: &Path) -> Result { let body = file::read_to_string(path)?; // strip comments let body = body.split('#').next().unwrap_or_default().to_string(); diff --git a/src/plugins/core/python.rs b/src/plugins/core/python.rs index a491969667..59e16d3883 100644 --- a/src/plugins/core/python.rs +++ b/src/plugins/core/python.rs @@ -367,8 +367,11 @@ impl Backend for PythonPlugin { } } - fn legacy_filenames(&self) -> eyre::Result> { - Ok(vec![".python-version".to_string()]) + fn idiomatic_filenames(&self) -> eyre::Result> { + Ok(vec![ + ".python-version".to_string(), + ".python-versions".to_string(), + ]) } fn install_version_impl( diff --git a/src/plugins/core/ruby.rs b/src/plugins/core/ruby.rs index aa26504750..09fbf7ab48 100644 --- a/src/plugins/core/ruby.rs +++ b/src/plugins/core/ruby.rs @@ -206,6 +206,10 @@ impl RubyPlugin { } fn test_gem(&self, config: &Config, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> { + if !regex!(r#"^\d"#).is_match(&self.tool_name()) { + // don't expect gem for artichoke + return Ok(()); + } pr.set_message("gem -v".into()); CmdLineRunner::new(self.gem_path(tv)) .with_pr(pr) @@ -346,11 +350,11 @@ impl Backend for RubyPlugin { Ok(versions) } - fn legacy_filenames(&self) -> Result> { + fn idiomatic_filenames(&self) -> Result> { Ok(vec![".ruby-version".into(), "Gemfile".into()]) } - fn parse_legacy_file(&self, path: &Path) -> Result { + fn parse_idiomatic_file(&self, path: &Path) -> Result { let v = match path.file_name() { Some(name) if name == "Gemfile" => parse_gemfile(&file::read_to_string(path)?), _ => { diff --git a/src/plugins/core/ruby_windows.rs b/src/plugins/core/ruby_windows.rs index b1a9554095..ac42571214 100644 --- a/src/plugins/core/ruby_windows.rs +++ b/src/plugins/core/ruby_windows.rs @@ -160,11 +160,11 @@ impl Backend for RubyPlugin { Ok(versions) } - fn legacy_filenames(&self) -> Result> { + fn idiomatic_filenames(&self) -> Result> { Ok(vec![".ruby-version".into(), "Gemfile".into()]) } - fn parse_legacy_file(&self, path: &Path) -> Result { + fn parse_idiomatic_file(&self, path: &Path) -> Result { let v = match path.file_name() { Some(name) if name == "Gemfile" => parse_gemfile(&file::read_to_string(path)?), _ => { diff --git a/src/plugins/core/zig.rs b/src/plugins/core/zig.rs index fa1e975267..40b81c3098 100644 --- a/src/plugins/core/zig.rs +++ b/src/plugins/core/zig.rs @@ -133,7 +133,7 @@ impl Backend for ZigPlugin { Ok(versions) } - fn legacy_filenames(&self) -> Result> { + fn idiomatic_filenames(&self) -> Result> { Ok(vec![".zig-version".into()]) } diff --git a/src/plugins/mise_plugin_toml.rs b/src/plugins/mise_plugin_toml.rs index f4e56c4513..80afa1fbd7 100644 --- a/src/plugins/mise_plugin_toml.rs +++ b/src/plugins/mise_plugin_toml.rs @@ -18,7 +18,7 @@ pub struct MisePluginToml { pub exec_env: MisePluginTomlScriptConfig, pub list_aliases: MisePluginTomlScriptConfig, pub list_bin_paths: MisePluginTomlScriptConfig, - pub list_legacy_filenames: MisePluginTomlScriptConfig, + pub list_idiomatic_filenames: MisePluginTomlScriptConfig, } impl MisePluginToml { @@ -46,12 +46,12 @@ impl MisePluginToml { "exec-env" => self.exec_env = self.parse_script_config(k, v)?, "list-aliases" => self.list_aliases = self.parse_script_config(k, v)?, "list-bin-paths" => self.list_bin_paths = self.parse_script_config(k, v)?, - "list-legacy-filenames" => { - self.list_legacy_filenames = self.parse_script_config(k, v)? + "list-idiomatic-filenames" | "list-legacy-filenames" => { + self.list_idiomatic_filenames = self.parse_script_config(k, v)? } // this is an old key used in rtx-python // this file is invalid, so just stop parsing entirely if we see it - "legacy-filenames" => return Ok(()), + "idiomatic-filenames" | "legacy-filenames" => return Ok(()), _ => Err(eyre!("unknown key: {}", k))?, } } @@ -121,8 +121,8 @@ mod tests { let cf = parse(&formatdoc! {r#" [list-aliases] data = "test-aliases" - [list-legacy-filenames] - data = "test-legacy-filenames" + [list-idiomatic-filenames] + data = "test-idiomatic-filenames" [exec-env] cache-key = ["foo", "bar"] [list-bin-paths] diff --git a/src/plugins/script_manager.rs b/src/plugins/script_manager.rs index abcdec2ba6..62103be233 100644 --- a/src/plugins/script_manager.rs +++ b/src/plugins/script_manager.rs @@ -33,8 +33,8 @@ pub enum Script { LatestStable, ListAliases, ListAll, - ListLegacyFilenames, - ParseLegacyFile(String), + ListIdiomaticFilenames, + ParseIdiomaticFile(String), // RuntimeVersion Download, @@ -51,9 +51,9 @@ impl Display for Script { // Plugin Script::LatestStable => write!(f, "latest-stable"), Script::ListAll => write!(f, "list-all"), - Script::ListLegacyFilenames => write!(f, "list-legacy-filenames"), + Script::ListIdiomaticFilenames => write!(f, "list-idiomatic-filenames"), Script::ListAliases => write!(f, "list-aliases"), - Script::ParseLegacyFile(_) => write!(f, "parse-legacy-file"), + Script::ParseIdiomaticFile(_) => write!(f, "parse-idiomatic-file"), Script::Hook(script) => write!(f, "{script}"), // RuntimeVersion @@ -137,7 +137,7 @@ impl ScriptManager { pub fn cmd(&self, script: &Script) -> Expression { let args = match script { - Script::ParseLegacyFile(filename) => vec![filename.clone()], + Script::ParseIdiomaticFile(filename) => vec![filename.clone()], Script::RunExternalCommand(_, args) => args.clone(), _ => vec![], }; diff --git a/src/test.rs b/src/test.rs index cb9ee46c1e..fdf1196dba 100644 --- a/src/test.rs +++ b/src/test.rs @@ -164,7 +164,7 @@ pub fn reset() { [settings] always_keep_download= true always_keep_install= true - legacy_version_file= true + idiomatic_version_file= true plugin_autoupdate_last_check_duration = "20m" jobs = 2 "#}, diff --git a/src/toolset/tool_source.rs b/src/toolset/tool_source.rs index e382bb40c2..95f0d073ae 100644 --- a/src/toolset/tool_source.rs +++ b/src/toolset/tool_source.rs @@ -11,7 +11,7 @@ use crate::file::display_path; pub enum ToolSource { ToolVersions(PathBuf), MiseToml(PathBuf), - LegacyVersionFile(PathBuf), + IdiomaticVersionFile(PathBuf), Argument, Environment(String, String), #[default] @@ -23,7 +23,7 @@ impl Display for ToolSource { match self { ToolSource::ToolVersions(path) => write!(f, "{}", display_path(path)), ToolSource::MiseToml(path) => write!(f, "{}", display_path(path)), - ToolSource::LegacyVersionFile(path) => write!(f, "{}", display_path(path)), + ToolSource::IdiomaticVersionFile(path) => write!(f, "{}", display_path(path)), ToolSource::Argument => write!(f, "--runtime"), ToolSource::Environment(k, v) => write!(f, "{k}={v}"), ToolSource::Unknown => write!(f, "unknown"), @@ -36,7 +36,7 @@ impl ToolSource { match self { ToolSource::ToolVersions(path) => Some(path), ToolSource::MiseToml(path) => Some(path), - ToolSource::LegacyVersionFile(path) => Some(path), + ToolSource::IdiomaticVersionFile(path) => Some(path), _ => None, } } @@ -51,8 +51,8 @@ impl ToolSource { "type".to_string() => "mise.toml".to_string(), "path".to_string() => path.to_string_lossy().to_string(), }, - ToolSource::LegacyVersionFile(path) => indexmap! { - "type".to_string() => "legacy-version-file".to_string(), + ToolSource::IdiomaticVersionFile(path) => indexmap! { + "type".to_string() => "idiomatic-version-file".to_string(), "path".to_string() => path.to_string_lossy().to_string(), }, ToolSource::Argument => indexmap! { @@ -85,8 +85,8 @@ impl Serialize for ToolSource { s.serialize_field("type", "mise.toml")?; s.serialize_field("path", path)?; } - ToolSource::LegacyVersionFile(path) => { - s.serialize_field("type", "legacy-version-file")?; + ToolSource::IdiomaticVersionFile(path) => { + s.serialize_field("type", "idiomatic-version-file")?; s.serialize_field("path", path)?; } ToolSource::Argument => { @@ -122,7 +122,7 @@ mod tests { let ts = ToolSource::MiseToml(PathBuf::from("/home/user/.mise.toml")); assert_str_eq!(ts.to_string(), "/home/user/.mise.toml"); - let ts = ToolSource::LegacyVersionFile(PathBuf::from("/home/user/.node-version")); + let ts = ToolSource::IdiomaticVersionFile(PathBuf::from("/home/user/.node-version")); assert_str_eq!(ts.to_string(), "/home/user/.node-version"); let ts = ToolSource::Argument; @@ -152,11 +152,11 @@ mod tests { } ); - let ts = ToolSource::LegacyVersionFile(PathBuf::from("/home/user/.node-version")); + let ts = ToolSource::IdiomaticVersionFile(PathBuf::from("/home/user/.node-version")); assert_eq!( ts.as_json(), indexmap! { - "type".to_string() => "legacy-version-file".to_string(), + "type".to_string() => "idiomatic-version-file".to_string(), "path".to_string() => "/home/user/.node-version".to_string(), } ); diff --git a/tasks/test/coverage b/tasks/test/coverage index 14c1b67a71..e34bfc6156 100755 --- a/tasks/test/coverage +++ b/tasks/test/coverage @@ -18,6 +18,6 @@ mise x -- bun i echo "::endgroup::" mise x -- ./e2e/run_all_tests echo "::group::mise test-tool" -MISE_USE_VERSIONS_HOST=0 mise test-tool --all +MISE_USE_VERSIONS_HOST=1 mise test-tool --all echo "::group::Render lcov report" cargo llvm-cov report --lcov --output-path "coverage-${TEST_TRANCHE:-}.lcov" diff --git a/test/config/config.toml b/test/config/config.toml index 25d0843f8c..c0c5a213af 100644 --- a/test/config/config.toml +++ b/test/config/config.toml @@ -13,6 +13,6 @@ run = 'echo "testing!"' [settings] always_keep_download= true always_keep_install= true -legacy_version_file= true +idiomatic_version_file= true plugin_autoupdate_last_check_duration = "20m" jobs = 2