From 0bcdf72876622a900b0ddac50197a38a7122861b Mon Sep 17 00:00:00 2001
From: jdx <216188+jdx@users.noreply.github.com>
Date: Mon, 11 Nov 2024 20:20:17 -0600
Subject: [PATCH] feat: aqua backend (#2995)
---
Cargo.lock | 21 ++
Cargo.toml | 4 +-
docs/.vitepress/config.ts | 1 +
docs/dev-tools/backends/aqua.md | 25 ++
docs/dev-tools/backends/vfox.md | 15 +-
docs/faq.md | 5 +-
docs/registry.md | 10 +-
e2e/backend/test_aqua | 6 +
registry.toml | 10 +-
schema/mise.json | 5 +
scripts/render-registry.js | 3 +
settings.toml | 6 +
src/aqua/aqua_registry.rs | 182 ++++++++++++
src/aqua/aqua_template.rs | 88 ++++++
src/aqua/mod.rs | 2 +
src/backend/aqua.rs | 267 ++++++++++++++++++
src/backend/mod.rs | 8 +-
...i__backends__ls__tests__backends_list.snap | 1 +
src/cli/settings/ls.rs | 2 +
src/cli/settings/set.rs | 1 +
src/cli/settings/unset.rs | 1 +
src/file.rs | 17 +-
src/github.rs | 8 +
src/main.rs | 2 +
src/maplit.rs | 17 ++
src/plugins/core/go.rs | 2 +-
src/plugins/core/java.rs | 2 +-
src/plugins/core/node.rs | 4 +-
src/plugins/core/python.rs | 2 +-
src/registry.rs | 5 +-
src/shims.rs | 1 +
src/toolset/mod.rs | 1 +
32 files changed, 689 insertions(+), 35 deletions(-)
create mode 100644 docs/dev-tools/backends/aqua.md
create mode 100644 e2e/backend/test_aqua
create mode 100644 src/aqua/aqua_registry.rs
create mode 100644 src/aqua/aqua_template.rs
create mode 100644 src/aqua/mod.rs
create mode 100644 src/backend/aqua.rs
create mode 100644 src/maplit.rs
diff --git a/Cargo.lock b/Cargo.lock
index 906bb8f699..ec7173ca40 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2064,6 +2064,7 @@ dependencies = [
"serde_derive",
"serde_ignored",
"serde_json",
+ "serde_yaml",
"sevenz-rust",
"sha2",
"shell-escape",
@@ -2091,6 +2092,7 @@ dependencies = [
"walkdir",
"which 7.0.0",
"xx",
+ "xz2",
"zip",
]
@@ -3157,6 +3159,19 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap 2.6.0",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
[[package]]
name = "sevenz-rust"
version = "0.6.1"
@@ -3986,6 +4001,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
[[package]]
name = "untrusted"
version = "0.9.0"
diff --git a/Cargo.toml b/Cargo.toml
index 41644d410c..fbbc980611 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -98,7 +98,8 @@ rmp-serde = "1"
serde = "1"
serde_derive = "1"
serde_ignored = "0.1"
-serde_json = { version = "1", features = [] }
+serde_json = "1"
+serde_yaml = "0.9"
sha2 = "0.10.8"
shell-escape = "0.1"
shell-words = "1"
@@ -129,6 +130,7 @@ vfox = { version = "0.3", default-features = false }
walkdir = "2"
which = "7"
xx = { version = "1", features = ["glob"] }
+xz2 = "0.1"
zip = { version = "2", default-features = false, features = ["deflate"] }
[target.'cfg(unix)'.dependencies]
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index e6301094a1..52cd3183e5 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -75,6 +75,7 @@ export default defineConfig({
link: "/dev-tools/backends/",
collapsed: true,
items: [
+ { text: "aqua", link: "/dev-tools/backends/aqua" },
{ text: "asdf", link: "/dev-tools/backends/asdf" },
{ text: "cargo", link: "/dev-tools/backends/cargo" },
{ text: "go", link: "/dev-tools/backends/go" },
diff --git a/docs/dev-tools/backends/aqua.md b/docs/dev-tools/backends/aqua.md
new file mode 100644
index 0000000000..25ba3952e0
--- /dev/null
+++ b/docs/dev-tools/backends/aqua.md
@@ -0,0 +1,25 @@
+# Aqua Backend
+
+[Aqua](https://aquaproj.github.io/) tools may be used natively in mise.
+
+The code for this is inside the mise repository at [`./src/backend/aqua.rs`](https://github.com/jdx/mise/blob/main/src/backend/aqua.rs).
+
+## Usage
+
+The following installs the latest version of ripgrep and sets it as the active version on PATH:
+
+```sh
+$ mise use -g aqua:BurntSushi/ripgrep
+$ rg --version
+ripgrep 14.1.1
+```
+
+The version will be set in `~/.config/mise/config.toml` with the following format:
+
+```toml
+[tools]
+"aqua:BurntSushi/ripgrep" = "latest"
+```
+
+Some tools will default to use aqua if they're specified in [registry.toml](https://github.com/jdx/mise/blob/main/registry.toml)
+to use the aqua backend. To see these tools, run `mise registry | grep aqua:`.
diff --git a/docs/dev-tools/backends/vfox.md b/docs/dev-tools/backends/vfox.md
index 8e0fafa358..ce293a3f6e 100644
--- a/docs/dev-tools/backends/vfox.md
+++ b/docs/dev-tools/backends/vfox.md
@@ -1,9 +1,8 @@
# Vfox Backend
-[Vfox](https://github.com/version-fox/vfox) plugins may be used in mise as an alternative for asdf
-plugins. On Windows, only vfox plugins are supported since asdf plugins require POSIX compatibility.
+[Vfox](https://github.com/version-fox/vfox) plugins may be used in mise to install tools.
-The code for this is inside of the mise repository at [`./src/backend/vfox.rs`](https://github.com/jdx/mise/blob/main/src/backend/vfox.rs).
+The code for this is inside the mise repository at [`./src/backend/vfox.rs`](https://github.com/jdx/mise/blob/main/src/backend/vfox.rs).
## Dependencies
@@ -13,14 +12,6 @@ No dependencies are required for vfox. Vfox lua code is read via a lua interpret
The following installs the latest version of cmake and sets it as the active version on PATH:
-```sh
-$ mise use -g vfox:cmake
-$ cmake --version
-cmake version 3.21.3
-```
-
-Alternatively, you can specify the GitHub repo:
-
```sh
$ mise use -g vfox:version-fox/vfox-cmake
$ cmake --version
@@ -31,7 +22,7 @@ The version will be set in `~/.config/mise/config.toml` with the following forma
```toml
[tools]
-"vfox:cmake" = "latest"
+"vfox:version-fox/vfox-cmake" = "latest"
```
## Default plugin backend
diff --git a/docs/faq.md b/docs/faq.md
index 976ff9112d..5bc2c69823 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -77,9 +77,8 @@ and `mise deactivate` to work without wrapping them in `eval "$(mise shell)"`.
## Windows support?
-Very basic support for windows is currently available, however because Windows can't support asdf
-plugins, they must use core and vfox only—which means only a handful of tools are available on
-Windows.
+Very basic support for windows is currently available, however because Windows can't support the asdf
+backend, windows can use core, aqua, or vfox backends though.
As of this writing, env var management and task execution are not yet supported on Windows.
diff --git a/docs/registry.md b/docs/registry.md
index c47e152665..362416a082 100644
--- a/docs/registry.md
+++ b/docs/registry.md
@@ -8,12 +8,12 @@ editLink: false
| ----------- | --------------- |
| 1password-cli | [asdf:NeoHsu/asdf-1password-cli](https://github.com/NeoHsu/asdf-1password-cli) |
| aapt2 | [asdf:ronnnnn/asdf-aapt2](https://github.com/ronnnnn/asdf-aapt2) |
-| act | [ubi:nektos/act](https://github.com/nektos/act) [asdf:gr1m0h/asdf-act](https://github.com/gr1m0h/asdf-act) |
-| action-validator | [ubi:mpalmer/action-validator](https://github.com/mpalmer/action-validator) [asdf:mpalmer/action-validator](https://github.com/mpalmer/action-validator) |
+| act | [aqua:nektos/act](https://github.com/nektos/act) [ubi:nektos/act](https://github.com/nektos/act) [asdf:gr1m0h/asdf-act](https://github.com/gr1m0h/asdf-act) |
+| action-validator | [aqua:mpalmer/action-validator](https://github.com/mpalmer/action-validator) [ubi:mpalmer/action-validator](https://github.com/mpalmer/action-validator) [asdf:mpalmer/action-validator](https://github.com/mpalmer/action-validator) |
| actionlint | [ubi:rhysd/actionlint](https://github.com/rhysd/actionlint) [asdf:crazy-matt/asdf-actionlint](https://github.com/crazy-matt/asdf-actionlint) |
| adr-tools | [asdf:https://gitlab.com/td7x/asdf/adr-tools](https://gitlab.com/td7x/asdf/adr-tools) |
| ag | [asdf:koketani/asdf-ag](https://github.com/koketani/asdf-ag) |
-| age | [asdf:threkk/asdf-age](https://github.com/threkk/asdf-age) |
+| age | [aqua:FiloSottile/age](https://github.com/FiloSottile/age) [asdf:threkk/asdf-age](https://github.com/threkk/asdf-age) |
| age-plugin-yubikey | [asdf:joke/asdf-age-plugin-yubikey](https://github.com/joke/asdf-age-plugin-yubikey) |
| agebox | [ubi:slok/agebox](https://github.com/slok/agebox) [asdf:slok/asdf-agebox](https://github.com/slok/asdf-agebox) |
| air | [asdf:pdemagny/asdf-air](https://github.com/pdemagny/asdf-air) |
@@ -179,7 +179,7 @@ editLink: false
| dhall | [asdf:aaaaninja/asdf-dhall](https://github.com/aaaaninja/asdf-dhall) |
| difftastic | [ubi:wilfred/difftastic](https://github.com/wilfred/difftastic) [asdf:volf52/asdf-difftastic](https://github.com/volf52/asdf-difftastic) |
| digdag | [asdf:jtakakura/asdf-digdag](https://github.com/jtakakura/asdf-digdag) |
-| direnv | [asdf:asdf-community/asdf-direnv](https://github.com/asdf-community/asdf-direnv) |
+| direnv | [aqua:direnv/direnv](https://github.com/direnv/direnv) [asdf:asdf-community/asdf-direnv](https://github.com/asdf-community/asdf-direnv) |
| dive | [ubi:wagoodman/dive](https://github.com/wagoodman/dive) [asdf:looztra/asdf-dive](https://github.com/looztra/asdf-dive) |
| djinni | [asdf:cross-language-cpp/asdf-djinni](https://github.com/cross-language-cpp/asdf-djinni) |
| dmd | [asdf:sylph01/asdf-dmd](https://github.com/sylph01/asdf-dmd) |
@@ -612,7 +612,7 @@ editLink: false
| revive | [asdf:bjw-s/asdf-revive](https://github.com/bjw-s/asdf-revive) |
| richgo | [asdf:paxosglobal/asdf-richgo](https://github.com/paxosglobal/asdf-richgo) |
| riff | [asdf:abinet/asdf-riff](https://github.com/abinet/asdf-riff) |
-| ripgrep | [ubi:BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep) [asdf:https://gitlab.com/wt0f/asdf-ripgrep](https://gitlab.com/wt0f/asdf-ripgrep) |
+| ripgrep | [ubi:BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep) [aqua:BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep) [asdf:https://gitlab.com/wt0f/asdf-ripgrep](https://gitlab.com/wt0f/asdf-ripgrep) |
| rke | [asdf:particledecay/asdf-rke](https://github.com/particledecay/asdf-rke) |
| rlwrap | [asdf:asdf-community/asdf-rlwrap](https://github.com/asdf-community/asdf-rlwrap) |
| rome | [asdf:kichiemon/asdf-rome](https://github.com/kichiemon/asdf-rome) |
diff --git a/e2e/backend/test_aqua b/e2e/backend/test_aqua
new file mode 100644
index 0000000000..25305df7e8
--- /dev/null
+++ b/e2e/backend/test_aqua
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+export MISE_EXPERIMENTAL=1
+
+assert_contains "mise x aqua:BurntSushi/ripgrep@14.0.0 -- rg --version" "ripgrep 14.0.0"
+assert "mise x age@1.2.0 -- age --version" "v1.2.0"
diff --git a/registry.toml b/registry.toml
index 82c8171592..e9ab236db8 100644
--- a/registry.toml
+++ b/registry.toml
@@ -7,12 +7,12 @@
[tools]
1password-cli = ["asdf:NeoHsu/asdf-1password-cli"]
aapt2 = ["asdf:ronnnnn/asdf-aapt2"]
-act = ["ubi:nektos/act", "asdf:gr1m0h/asdf-act"]
-action-validator = ["ubi:mpalmer/action-validator", "asdf:mpalmer/action-validator"]
+act = ["aqua:nektos/act", "ubi:nektos/act", "asdf:gr1m0h/asdf-act"]
+action-validator = ["aqua:mpalmer/action-validator", "ubi:mpalmer/action-validator", "asdf:mpalmer/action-validator"]
actionlint = ["ubi:rhysd/actionlint", "asdf:crazy-matt/asdf-actionlint"]
adr-tools = ["asdf:https://gitlab.com/td7x/asdf/adr-tools"]
ag = ["asdf:koketani/asdf-ag"]
-age = ["asdf:threkk/asdf-age"]
+age = ["aqua:FiloSottile/age", "asdf:threkk/asdf-age"]
age-plugin-yubikey = ["asdf:joke/asdf-age-plugin-yubikey"]
agebox = ["ubi:slok/agebox", "asdf:slok/asdf-agebox"]
air = ["asdf:pdemagny/asdf-air"]
@@ -178,7 +178,7 @@ devspace = ["asdf:NeoHsu/asdf-devspace"]
dhall = ["asdf:aaaaninja/asdf-dhall"]
difftastic = ["ubi:wilfred/difftastic[exe=difft]", "asdf:volf52/asdf-difftastic"]
digdag = ["asdf:jtakakura/asdf-digdag"]
-direnv = ["asdf:asdf-community/asdf-direnv"]
+direnv = ["aqua:direnv/direnv", "asdf:asdf-community/asdf-direnv"]
dive = ["ubi:wagoodman/dive", "asdf:looztra/asdf-dive"]
djinni = ["asdf:cross-language-cpp/asdf-djinni"]
dmd = ["asdf:sylph01/asdf-dmd"]
@@ -610,7 +610,7 @@ restish = ["ubi:danielgtaylor/restish", "go:github.com/danielgtaylor/restish"]
revive = ["asdf:bjw-s/asdf-revive"]
richgo = ["asdf:paxosglobal/asdf-richgo"]
riff = ["asdf:abinet/asdf-riff"]
-ripgrep = ["ubi:BurntSushi/ripgrep[exe=rg]", "asdf:https://gitlab.com/wt0f/asdf-ripgrep"]
+ripgrep = ["ubi:BurntSushi/ripgrep[exe=rg]", "aqua:BurntSushi/ripgrep", "asdf:https://gitlab.com/wt0f/asdf-ripgrep"]
rke = ["asdf:particledecay/asdf-rke"]
rlwrap = ["asdf:asdf-community/asdf-rlwrap"]
rome = ["asdf:kichiemon/asdf-rome"]
diff --git a/schema/mise.json b/schema/mise.json
index 1177e2c0a9..6fce7fa084 100644
--- a/schema/mise.json
+++ b/schema/mise.json
@@ -127,6 +127,11 @@
"description": "should mise keep install files after installation even if the installation fails",
"type": "boolean"
},
+ "aqua_registry_url": {
+ "default": "https://github.com/aquaproj/aqua-registry",
+ "description": "URL to fetch aqua registry from.",
+ "type": "string"
+ },
"asdf": {
"description": "use asdf as a default plugin backend",
"deprecated": "Use disable_backends instead.",
diff --git a/scripts/render-registry.js b/scripts/render-registry.js
index b0d1c65c2e..3c65058240 100755
--- a/scripts/render-registry.js
+++ b/scripts/render-registry.js
@@ -44,6 +44,9 @@ for (const match of stdout.split("\n")) {
return `[${match[1]}:${match[2]}](https://pkg.go.dev/${match[2]})`;
} else if (match[1] === "ubi") {
return `[${match[1]}:${match[2]}](https://github.com/${match[2]})`;
+ } else if (match[1] === "aqua") {
+ // TODO: handle non-github repos
+ return `[${match[1]}:${match[2]}](https://github.com/${match[2]})`;
} else {
throw new Error(`Unknown registry: ${full}`);
}
diff --git a/settings.toml b/settings.toml
index 0eaf01abe2..aa44ff58d3 100644
--- a/settings.toml
+++ b/settings.toml
@@ -55,6 +55,12 @@ env = "MISE_ALWAYS_KEEP_INSTALL"
type = "Bool"
description = "should mise keep install files after installation even if the installation fails"
+[aqua_registry_url]
+env = "MISE_AQUA_REGISTRY_URL"
+type = "Url"
+default = "https://github.com/aquaproj/aqua-registry"
+description = "URL to fetch aqua registry from."
+
[asdf]
env = "MISE_ASDF"
type = "Bool"
diff --git a/src/aqua/aqua_registry.rs b/src/aqua/aqua_registry.rs
new file mode 100644
index 0000000000..507e0f05ff
--- /dev/null
+++ b/src/aqua/aqua_registry.rs
@@ -0,0 +1,182 @@
+use crate::backend::aqua;
+use crate::config::SETTINGS;
+use crate::duration::DAILY;
+use crate::git::Git;
+use crate::{dirs, file};
+use eyre::Result;
+use itertools::Itertools;
+use once_cell::sync::Lazy;
+use serde_derive::Deserialize;
+use std::collections::HashMap;
+use std::path::PathBuf;
+
+pub static AQUA_REGISTRY: Lazy = Lazy::new(|| {
+ AquaRegistry::standard().unwrap_or_else(|err| {
+ warn!("failed to initialize aqua registry: {err:?}");
+ AquaRegistry::default()
+ })
+});
+static AQUA_REGISTRY_PATH: Lazy = Lazy::new(|| dirs::CACHE.join("aqua-registry"));
+
+#[derive(Default)]
+pub struct AquaRegistry {
+ path: PathBuf,
+}
+
+#[derive(Debug, Deserialize, Default, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum AquaPackageType {
+ #[default]
+ GithubRelease,
+ Http,
+}
+
+#[derive(Debug, Deserialize, Default, Clone)]
+#[serde(default)]
+pub struct AquaPackage {
+ pub r#type: AquaPackageType,
+ pub repo_owner: String,
+ pub repo_name: String,
+ pub asset: String,
+ pub url: String,
+ pub description: Option,
+ pub format: String,
+ pub rosetta2: bool,
+ pub windows_arm_emulation: bool,
+ pub complete_windows_ext: bool,
+ pub supported_envs: Vec,
+ pub files: Vec,
+ pub replacements: HashMap,
+ overrides: Vec,
+ version_constraint: String,
+ version_overrides: Vec,
+}
+
+#[derive(Debug, Deserialize, Clone)]
+struct AquaOverride {
+ #[serde(flatten)]
+ pkg: AquaPackage,
+ goos: Option,
+ goarch: Option,
+}
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct AquaFile {
+ // pub name: String,
+ pub src: Option,
+}
+
+#[derive(Debug, Deserialize)]
+struct RegistryYaml {
+ packages: Vec,
+}
+
+impl AquaRegistry {
+ pub fn standard() -> Result {
+ let path = AQUA_REGISTRY_PATH.clone();
+ let repo = Git::new(&path);
+ if repo.exists() {
+ fetch_latest_repo(&repo)?;
+ } else {
+ info!("cloning aqua registry to {path:?}");
+ repo.clone(&SETTINGS.aqua_registry_url)?;
+ }
+ Ok(Self { path })
+ }
+
+ pub fn package(&self, id: &str) -> Result