Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/registry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,18 @@ jobs:
list-changed-tools:
timeout-minutes: 10
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' }}
outputs:
tools: ${{ steps.diff.outputs.tools }}
tools: ${{ steps.diff.outputs.tools || steps.set-empty.outputs.tools }}
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2
if: ${{ github.event_name == 'pull_request' }}
with:
install_args: jd yj
- id: diff
if: ${{ github.event_name == 'pull_request' }}
shell: bash # -eo pipefail
run: |
changed_tools=$( \
Expand All @@ -64,6 +65,9 @@ jobs:
)
echo "$changed_tools"
echo "tools=$changed_tools" >> $GITHUB_OUTPUT
- id: set-empty
if: ${{ github.event_name != 'pull_request' }}
run: echo "tools=" >> $GITHUB_OUTPUT

test-tool:
name: test-tool-${{ matrix.tranche }}
Expand Down
21 changes: 20 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["crates/vfox"]
members = ["crates/vfox", "crates/aqua-registry"]

[package]
name = "mise"
Expand Down Expand Up @@ -79,7 +79,6 @@ duct = "0.13"
either = { version = "1", features = ["serde"] }
homedir = "0.3"
# expr-lang = { path = "../expr-lang" }
expr-lang = "0.3"
eyre = "0.6"
filetime = "0.2"
flate2 = "1"
Expand Down Expand Up @@ -157,6 +156,7 @@ urlencoding = "2.1.3"
usage-lib = { version = "2", features = ["clap", "docs"] }
versions = { version = "6", features = ["serde"] }
vfox = { path = "crates/vfox", default-features = false }
aqua-registry = { path = "crates/aqua-registry" }
walkdir = "2"
which = "7"
xx = { version = "2", features = ["glob"] }
Expand Down
68 changes: 1 addition & 67 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use heck::ToUpperCamelCase;
use indexmap::IndexMap;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::{env, fs};

fn main() {
Expand All @@ -14,7 +14,6 @@ fn main() {

codegen_settings();
codegen_registry();
codegen_aqua();
}

fn codegen_registry() {
Expand Down Expand Up @@ -329,68 +328,3 @@ pub static SETTINGS_META: Lazy<IndexMap<&'static str, SettingsMeta>> = Lazy::new

fs::write(&dest_path, lines.join("\n")).unwrap();
}

// pub static AQUA_STANDARD_REGISTRY_FILES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
// include!(concat!(env!("OUT_DIR"), "/aqua_standard_registry.rs"));
// });

fn codegen_aqua() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("aqua_standard_registry.rs");
let mut lines = vec!["[".to_string()];
for (k, v) in aqua_registries(&registry_dir()).unwrap_or_default() {
lines.push(format!(r####" ("{k}", r###"{v}"###),"####));
}
lines.push("].into()".to_string());
fs::write(&dest_path, lines.join("\n")).unwrap();
}

fn ls(path: &Path) -> Result<Vec<PathBuf>, std::io::Error> {
fs::read_dir(path)?
.map(|entry| entry.map(|e| e.path()))
.collect()
}

fn aqua_registries(d: &Path) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
let mut registries = vec![];
for f in ls(d)? {
if f.is_dir() {
registries.extend(aqua_registries(&f)?);
} else if f.file_name() == Some("registry.yaml".as_ref()) {
let registry_yaml = fs::read_to_string(&f)?;
registries.push((
f.parent()
.unwrap()
.strip_prefix(registry_dir())
.unwrap()
.iter()
.map(|s| s.to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("/"),
registry_yaml.clone(),
));
if registry_yaml.contains("aliases") {
let registry: serde_yaml::Value = serde_yaml::from_str(&registry_yaml)?;
if let Some(packages) = registry.get("packages").and_then(|p| p.as_sequence()) {
for package in packages {
if let Some(aliases) = package.get("aliases").and_then(|a| a.as_sequence())
{
for alias in aliases {
if let Some(name) = alias.get("name").and_then(|n| n.as_str()) {
registries.push((name.to_string(), registry_yaml.clone()));
}
}
}
}
}
}
}
}
Ok(registries)
}

fn registry_dir() -> PathBuf {
PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
.join("aqua-registry")
.join("pkgs")
}
49 changes: 49 additions & 0 deletions crates/aqua-registry/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[package]
name = "aqua-registry"
version = "2025.9.10"
edition = "2024"
description = "Aqua registry backend for mise"
authors = ["Jeff Dickey (@jdx)"]
license = "MIT"
repository = "https://github.com/jdx/mise"
homepage = "https://mise.jdx.dev"
readme = "README.md"
keywords = ["mise", "aqua", "registry", "package-manager"]
categories = ["development-tools"]
build = "build.rs"
publish = false

[package.metadata.cargo-machete]
ignored = ["serde"]

[features]
default = []

[dependencies]
# Core dependencies
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_yaml = "0.9"
thiserror = "2"
eyre = "0.6"
indexmap = { version = "2", features = ["serde"] }
itertools = "0.14"
strum = { version = "0.27", features = ["derive"] }

# Template parsing and evaluation
expr-lang = "0.3"
versions = { version = "6", features = ["serde"] }
heck = "0.5"

# Async runtime
tokio = { version = "1", features = ["sync"] }

# Logging
log = "0.4"


[dev-dependencies]
tokio = { version = "1", features = ["rt", "macros"] }

[build-dependencies]
serde_yaml = "0.9"
21 changes: 21 additions & 0 deletions crates/aqua-registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# aqua-registry

Aqua registry backend for [mise](https://mise.jdx.dev).

This crate provides support for the [Aqua](https://aquaproj.github.io/) registry format, allowing mise to install tools from the Aqua ecosystem.

## Features

- Parse and validate Aqua registry YAML files
- Resolve package versions and platform-specific assets
- Template string evaluation for dynamic asset URLs
- Support for checksums, signatures, and provenance verification
- Platform-aware asset resolution for cross-platform tool installation

## Usage

This crate is primarily used internally by mise. For more information about mise, visit [mise.jdx.dev](https://mise.jdx.dev).

## License

MIT
121 changes: 121 additions & 0 deletions crates/aqua-registry/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use std::env;
use std::fs;
use std::path::Path;

fn main() {
let out_dir = env::var("OUT_DIR").expect("OUT_DIR environment variable must be set");
generate_baked_registry(&out_dir);
}
fn generate_baked_registry(out_dir: &str) {
let dest_path = Path::new(out_dir).join("aqua_standard_registry.rs");

// Look for the aqua-registry directory in the workspace root
let registry_dir = find_registry_dir()
.expect("Could not find aqua-registry directory in workspace root. Expected to find it at workspace_root/aqua-registry/");

let registries =
collect_aqua_registries(&registry_dir).expect("Failed to collect aqua registry files");

if registries.is_empty() {
panic!(
"No aqua registry files found in {}/pkgs/",
registry_dir.display()
);
}

let mut code = String::from("HashMap::from([\n");
for (id, content) in registries {
code.push_str(&format!(" ({:?}, {:?}),\n", id, content));
}
code.push_str("])");

fs::write(dest_path, code).expect("Failed to write baked registry file");
}

fn find_registry_dir() -> Option<std::path::PathBuf> {
let current_dir = env::current_dir().ok()?;

// Look for the workspace root by finding a Cargo.toml that contains [workspace]
let workspace_root = current_dir.ancestors().find(|dir| {
let cargo_toml = dir.join("Cargo.toml");
if !cargo_toml.exists() {
return false;
}
// Check if this Cargo.toml defines a workspace
if let Ok(content) = fs::read_to_string(&cargo_toml) {
content.contains("[workspace]")
} else {
false
}
})?;

let aqua_registry = workspace_root.join("aqua-registry");
if aqua_registry.exists() {
return Some(aqua_registry);
}

None
}

fn collect_aqua_registries(
dir: &Path,
) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
let mut registries = Vec::new();

if !dir.exists() {
return Ok(registries);
}

let pkgs_dir = dir.join("pkgs");
if !pkgs_dir.exists() {
return Ok(registries);
}

collect_registries_recursive(&pkgs_dir, &mut registries, String::new())?;
Ok(registries)
}

fn collect_registries_recursive(
dir: &Path,
registries: &mut Vec<(String, String)>,
prefix: String,
) -> Result<(), Box<dyn std::error::Error>> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();

if path.is_dir() {
let dir_name = path.file_name().unwrap().to_string_lossy();
let new_prefix = if prefix.is_empty() {
dir_name.to_string()
} else {
format!("{}/{}", prefix, dir_name)
};
collect_registries_recursive(&path, registries, new_prefix)?;
} else if path.file_name() == Some(std::ffi::OsStr::new("registry.yaml")) {
let content = fs::read_to_string(&path)?;
registries.push((prefix.clone(), content.clone()));

// Process aliases if they exist
#[allow(clippy::collapsible_if)]
if content.contains("aliases") {
if let Ok(registry) = serde_yaml::from_str::<serde_yaml::Value>(&content) {
if let Some(packages) = registry.get("packages").and_then(|p| p.as_sequence()) {
for package in packages {
if let Some(aliases) =
package.get("aliases").and_then(|a| a.as_sequence())
{
for alias in aliases {
if let Some(name) = alias.get("name").and_then(|n| n.as_str()) {
registries.push((name.to_string(), content.clone()));
}
}
}
}
}
}
}
}
}
Ok(())
}
Loading
Loading