Skip to content
20 changes: 12 additions & 8 deletions mise.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ backend = "aqua:FiloSottile/age"
[[tools.bun]]
version = "1.3.3"
backend = "core:bun"
"platforms.linux-arm64" = { url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-linux-aarch64.zip"}
"platforms.linux-x64" = { checksum = "blake3:21d78fbc47b9175cc18a79bc228c6b3aa92d102c3f9c006934101c6b588cc900", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-linux-x64.zip"}
"platforms.macos-arm64" = { checksum = "blake3:2b8fa453577fa737c9eaf42730791360c8a133397db28684c93e49f9dd5d501a", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-darwin-aarch64.zip"}
"platforms.macos-x64" = { url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-darwin-x64.zip"}
"platforms.windows-x64" = { url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-windows-x64.zip"}
"platforms.linux-arm64" = { checksum = "sha256:41b9f4f25256db897c2c135320e4f96c373e20ae6f06d8015187dac83591efc8", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-linux-aarch64.zip"}
"platforms.linux-x64" = { checksum = "sha256:f5c546736f955141459de231167b6fdf7b01418e8be3609f2cde9dfe46a93a3d", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-linux-x64.zip"}
"platforms.macos-arm64" = { checksum = "sha256:f50f5cc767c3882c46675fbe07e0b7b1df71a73ce544aadb537ad9261af00bb1", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-darwin-aarch64.zip"}
"platforms.macos-x64" = { checksum = "sha256:fdaf5e3c91de2f2a8c83e80a125c5111d476e5f7575b2747d71bc51d2c920bd4", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-darwin-x64.zip"}
"platforms.windows-x64" = { checksum = "sha256:53e239b058c13f0bb70949b222c4d40c5ab7d6cad22268b2ace2187fcfd7a247", url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.3/bun-windows-x64.zip"}

[[tools.cargo-binstall]]
version = "1.16.2"
Expand Down Expand Up @@ -61,7 +61,11 @@ backend = "cargo:usage-cli"
[[tools.fd]]
version = "10.3.0"
backend = "aqua:sharkdp/fd"
"platforms.linux-arm64" = { checksum = "sha256:996b9b1366433b211cb3bbedba91c9dbce2431842144d925428ead0adf32020b", url = "https://github.com/sharkdp/fd/releases/download/v10.3.0/fd-v10.3.0-aarch64-unknown-linux-musl.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:2b6bfaae8c48f12050813c2ffe1884c61ea26e750d803df9c9114550a314cd14", url = "https://github.com/sharkdp/fd/releases/download/v10.3.0/fd-v10.3.0-x86_64-unknown-linux-musl.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:0570263812089120bc2a5d84f9e65cd0c25e4a4d724c80075c357239c74ae904", url = "https://github.com/sharkdp/fd/releases/download/v10.3.0/fd-v10.3.0-aarch64-apple-darwin.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3", url = "https://github.com/sharkdp/fd/releases/download/v10.3.0/fd-v10.3.0-x86_64-apple-darwin.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:318aa2a6fa664325933e81fda60d523fff29444129e91ebf0726b5b3bcd8b059", url = "https://github.com/sharkdp/fd/releases/download/v10.3.0/fd-v10.3.0-x86_64-pc-windows-msvc.zip"}

[[tools.gh]]
version = "2.83.1"
Expand Down Expand Up @@ -92,11 +96,11 @@ backend = "aqua:jqlang/jq"
[[tools.node]]
version = "24.11.1"
backend = "core:node"
"platforms.linux-arm64" = { url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-linux-arm64.tar.gz"}
"platforms.linux-arm64" = { checksum = "sha256:0dc93ec5c798b0d347f068db6d205d03dea9a71765e6a53922b682b91265d71f", url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-linux-arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:58a5ff5cc8f2200e458bea22e329d5c1994aa1b111d499ca46ec2411d58239ca", url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-linux-x64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:b05aa3a66efe680023f930bd5af3fdbbd542794da5644ca2ad711d68cbd4dc35", url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-darwin-arm64.tar.gz"}
"platforms.macos-x64" = { url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-darwin-x64.tar.gz"}
"platforms.windows-x64" = { url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-win-x64.zip"}
"platforms.macos-x64" = { checksum = "sha256:096081b6d6fcdd3f5ba0f5f1d44a47e83037ad2e78eada26671c252fe64dd111", url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-darwin-x64.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:5355ae6d7c49eddcfde7d34ac3486820600a831bf81dc3bdca5c8db6a9bb0e76", url = "https://nodejs.org/dist/v24.11.1/node-v24.11.1-win-x64.zip"}

[[tools."npm:ajv-cli"]]
version = "5.0.0"
Expand Down
19 changes: 19 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::config::{Config, Settings};
use crate::file::{display_path, remove_all, remove_all_with_warning};
use crate::install_context::InstallContext;
use crate::lockfile::PlatformInfo;
use crate::platform::Platform;
use crate::plugins::core::CORE_PLUGINS;
use crate::plugins::{PluginType, VERSION_REGEX};
use crate::registry::{REGISTRY, tool_enabled};
Expand Down Expand Up @@ -232,6 +233,24 @@ pub trait Backend: Debug + Send + Sync {
BTreeMap::new() // Default: no options affect artifact identity
}

/// Returns all platform variants that should be locked for a given base platform.
///
/// Some tools have compile-time variants (e.g., bun has baseline/musl variants)
/// that result in different download URLs and checksums. This method allows
/// backends to declare all variants so `mise lock` can fetch checksums for each.
///
/// Default returns just the base platform. Backends should override this to
/// return additional variants when applicable.
///
/// Example: For bun on linux-x64, this might return:
/// - linux-x64 (default, AVX2)
/// - linux-x64-baseline (no AVX2)
/// - linux-x64-musl (musl libc)
/// - linux-x64-musl-baseline (musl + no AVX2)
fn platform_variants(&self, platform: &Platform) -> Vec<Platform> {
vec![platform.clone()] // Default: just the base platform
}

async fn description(&self) -> Option<String> {
None
}
Expand Down
54 changes: 54 additions & 0 deletions src/backend/static_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,67 @@
use crate::backend::platform_target::PlatformTarget;
use crate::file;
use crate::hash;
use crate::http::HTTP;
use crate::toolset::ToolVersion;
use crate::toolset::ToolVersionOptions;
use crate::ui::progress_report::SingleReport;
use eyre::{Result, bail};
use indexmap::IndexSet;
use std::path::Path;

// ========== Checksum Fetching Helpers ==========

/// Fetches a checksum for a specific file from a SHASUMS256.txt-style file.
/// Uses cached HTTP requests since the same SHASUMS file is fetched for all platforms.
///
/// # Arguments
/// * `shasums_url` - URL to the SHASUMS256.txt file
/// * `filename` - The filename to look up in the SHASUMS file
///
/// # Returns
/// * `Some("sha256:<hash>")` if found
/// * `None` if the SHASUMS file couldn't be fetched or filename not found
pub async fn fetch_checksum_from_shasums(shasums_url: &str, filename: &str) -> Option<String> {
match HTTP.get_text_cached(shasums_url).await {
Ok(shasums_content) => {
let shasums = hash::parse_shasums(&shasums_content);
shasums.get(filename).map(|h| format!("sha256:{h}"))
}
Err(e) => {
debug!("Failed to fetch SHASUMS from {}: {e}", shasums_url);
None
}
}
}

/// Fetches a checksum from an individual checksum file (e.g., file.tar.gz.sha256).
/// The checksum file should contain just the hash, optionally followed by filename.
///
/// # Arguments
/// * `checksum_url` - URL to the checksum file (e.g., `https://example.com/file.tar.gz.sha256`)
/// * `algo` - The algorithm name to prefix (e.g., "sha256")
///
/// # Returns
/// * `Some("<algo>:<hash>")` if found
/// * `None` if the checksum file couldn't be fetched
pub async fn fetch_checksum_from_file(checksum_url: &str, algo: &str) -> Option<String> {
match HTTP.get_text(checksum_url).await {
Ok(content) => {
// Format is typically "<hash> <filename>" or just "<hash>"
content
.split_whitespace()
.next()
.map(|h| format!("{algo}:{}", h.trim()))
}
Err(e) => {
debug!("Failed to fetch checksum from {}: {e}", checksum_url);
None
}
}
}

// ========== Platform Patterns ==========

// Shared OS/arch patterns used across helpers
const OS_PATTERNS: &[&str] = &[
"linux", "darwin", "macos", "windows", "win", "freebsd", "openbsd", "netbsd", "android",
Expand Down
151 changes: 83 additions & 68 deletions src/cli/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,21 +180,39 @@ impl Lock {
config: &Config,
ts: &Toolset,
) -> Vec<(crate::cli::args::BackendArg, crate::toolset::ToolVersion)> {
// Collect tools from ALL config files (not just resolved current versions)
// This ensures base config tools are included even when overridden by env configs
// Calculate target config_root (same logic as get_lockfile_path)
let target_root = config
.config_files
.keys()
.next()
.map(|p| config_root::config_root(p))
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

// Collect tools from config files in the target config_root only
let mut all_tools: Vec<_> = Vec::new();
let mut seen: BTreeSet<(String, String)> = BTreeSet::new();

// First, get all tools from the resolved toolset (these are the "current" versions)
// but only if they come from a config file in the target config_root
for (backend, tv) in ts.list_current_versions() {
// Check if this tool's source is in the target config_root
if let Some(source_path) = tv.request.source().path()
&& config_root::config_root(source_path) != target_root
{
continue;
}
let key = (backend.ba().short.clone(), tv.version.clone());
if seen.insert(key) {
all_tools.push((backend.ba().as_ref().clone(), tv));
}
}

// Then, iterate ALL config files to find tools that may have been overridden
for (_path, cf) in config.config_files.iter() {
// Then, iterate config files in the target config_root to find tools that may have been overridden
for (path, cf) in config.config_files.iter() {
// Skip config files not in the target config_root
if config_root::config_root(path) != target_root {
continue;
}
if let Ok(trs) = cf.to_tool_request_set() {
for (ba, requests, _source) in trs.iter() {
for request in requests {
Expand All @@ -211,10 +229,9 @@ impl Lock {
}
}
}
// Also check installed versions that match this request
let installed = backend.list_installed_versions();
// For "latest" requests, find the highest installed version
if request.version() == "latest" {
// For "latest", find the highest installed version
let installed = backend.list_installed_versions();
if let Some(latest_version) = installed.iter().max_by(|a, b| {
versions::Versioning::new(a).cmp(&versions::Versioning::new(b))
}) {
Expand All @@ -227,21 +244,6 @@ impl Lock {
all_tools.push((ba.as_ref().clone(), tv));
}
}
} else {
// For prefix requests, find matching versions using proper fuzzy matching
// (ensures "1" matches "1.0.0" but not "10.0.0")
for version in
backend.list_installed_versions_matching(&request.version())
{
let key = (ba.short.clone(), version.clone());
if seen.insert(key.clone()) {
let tv = crate::toolset::ToolVersion::new(
request.clone(),
version,
);
all_tools.push((ba.as_ref().clone(), tv));
}
}
}
}
}
Expand Down Expand Up @@ -296,58 +298,71 @@ impl Lock {
let mut results = Vec::new();

let mpr = MultiProgressReport::get();
let total_tasks = tools.len() * platforms.len();
let pr = mpr.add("lock");
pr.set_length(total_tasks as u64);

// Spawn tasks for each tool/platform combination
// Collect all platform variants for each tool/platform combination
let mut all_tasks: Vec<(
crate::cli::args::BackendArg,
crate::toolset::ToolVersion,
Platform,
)> = Vec::new();
for (ba, tv) in tools {
let backend = crate::backend::get(ba);
for platform in platforms {
let ba = ba.clone();
let tv = tv.clone();
let platform = platform.clone();
let semaphore = semaphore.clone();

jset.spawn(async move {
let _permit = semaphore.acquire().await;
let target = PlatformTarget::new(platform.clone());
let backend = crate::backend::get(&ba);

let (info, options) = if let Some(backend) = backend {
let options = backend.resolve_lockfile_options(&tv.request, &target);
match backend.resolve_lock_info(&tv, &target).await {
Ok(info) if info.url.is_some() => (Some(info), options),
Ok(_) => {
debug!("No URL found for {} on {}", ba.short, platform.to_key());
(None, options)
}
Err(e) => {
warn!(
"Failed to resolve {} for {}: {}",
ba.short,
platform.to_key(),
e
);
(None, options)
}
}
} else {
warn!("Backend not found for {}", ba.short);
(None, BTreeMap::new())
};

(
ba.short.clone(),
tv.version.clone(),
ba.full(),
platform,
info,
options,
)
});
// Get all variants for this platform from the backend
let variants = if let Some(ref backend) = backend {
backend.platform_variants(platform)
} else {
vec![platform.clone()]
};
for variant in variants {
all_tasks.push((ba.clone(), tv.clone(), variant));
}
}
}

let total_tasks = all_tasks.len();
let pr = mpr.add("lock");
pr.set_length(total_tasks as u64);

// Spawn tasks for each tool/platform variant combination
for (ba, tv, platform) in all_tasks {
let semaphore = semaphore.clone();

jset.spawn(async move {
let _permit = semaphore.acquire().await;
let target = PlatformTarget::new(platform.clone());
let backend = crate::backend::get(&ba);

let (info, options) = if let Some(backend) = backend {
let options = backend.resolve_lockfile_options(&tv.request, &target);
match backend.resolve_lock_info(&tv, &target).await {
Ok(info) => (Some(info), options),
Err(e) => {
warn!(
"Failed to resolve {} for {}: {}",
ba.short,
platform.to_key(),
e
);
(None, options)
}
}
} else {
warn!("Backend not found for {}", ba.short);
(None, BTreeMap::new())
};

(
ba.short.clone(),
tv.version.clone(),
ba.full(),
platform,
info,
options,
)
});
}

// Collect all results
let mut completed = 0;
while let Some(result) = jset.join_next().await {
Expand Down
Loading
Loading