Skip to content

feat(backend): add global libc preference#9404

Merged
jdx merged 1 commit intomainfrom
codex/libc-setting-backends
Apr 26, 2026
Merged

feat(backend): add global libc preference#9404
jdx merged 1 commit intomainfrom
codex/libc-setting-backends

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 26, 2026

Summary

Adds a global libc setting (MISE_LIBC) for selecting Linux precompiled binary variants across backends that can safely honor a libc preference.

Details

  • Promotes libc preference into typed settings and schema generation, accepting musl, glibc, and gnu.
  • Threads libc through Platform::current() and PlatformTarget so generic GitHub asset matching can prefer musl or glibc variants consistently.
  • Adds aqua support in two layers:
    • switches aqua registry Linux replacements like unknown-linux-gnu/unknown-linux-musl according to the target/global libc preference;
    • keeps the fallback that prefers a very similar release asset with a standalone musl token when aqua cannot express the variant yet.
  • Applies the setting to Bun, Python precompiled builds, Node's common musl unofficial-build flavor, and vfox runtime envType.
  • Updates Docker and Node docs for the new global setting.

Backend Audit

Conda, Go, Zig, Deno, Ruby, Java, Erlang, and Rust do not currently expose a safe global libc selection path here because their upstream artifact naming is either not libc-specific, hard-coded by upstream, or already controlled by backend-specific settings.

Validation

  • cargo test libc
  • cargo test backend::aqua::lock_candidate_tests
  • cargo test -p vfox config::tests::test_env_type
  • cargo fmt --check
  • git diff --check
  • commit hook: cargo check --all-features, schema validation, markdownlint, shellcheck, shfmt, taplo, prettier, actionlint, lua checks

This PR was generated by an AI coding assistant.


Note

Medium Risk
Adds a new global libc setting that changes how Linux artifacts are selected across multiple backends and influences cache keys, so mis-detection or incorrect preference could cause wrong binaries/lock entries to be chosen.

Overview
Adds a global libc setting (schema + settings.toml, env MISE_LIBC, accepting glibc/gnu/musl) and uses it to influence Linux platform qualification and libc detection.

Updates platform/target handling to expose libc (including compound qualifiers) and adjusts caching and GitHub asset matching so musl variants can be preferred when requested.

Extends backend/tool integrations to honor the preference: aqua now rewrites Linux replacements and prefers musl release assets for musl lock targets, Node/Python/Bun select musl/glibc variants accordingly, and vfox passes an optional runtime envType into plugin hooks. Docs are updated to describe the new setting and Docker override behavior.

Reviewed by Cursor Bugbot for commit 16a6a2a. Bugbot is set up for automated code reviews on this repo. Configure here.

@jdx jdx changed the title [codex] feat(backend): add global libc preference feat(backend): add global libc preference Apr 26, 2026
@jdx jdx marked this pull request as ready for review April 26, 2026 15:55
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a global libc configuration setting to mise, enabling explicit control over glibc and musl detection across various backends including Aqua, Bun, Node, Python, and vfox. The changes update asset matching, platform identification, and cache key generation to respect this new setting. Review feedback highlights opportunities to make libc detection more robust in the Node and Python plugins by utilizing runtime platform detection instead of compile-time constants. Furthermore, performance improvements were identified in the Aqua backend to minimize redundant string allocations and tokenization during asset selection.

Comment thread src/plugins/core/node.rs Outdated
Comment on lines +788 to +795
let flavor = settings.node.flavor.as_deref().or_else(|| {
if target.is_current() && target.os_name() == "linux" && settings.libc() == Some("musl")
{
Some("musl")
} else {
None
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current logic for selecting the musl flavor is too restrictive. By checking target.is_current() and settings.libc(), it breaks flavor selection during cross-platform locking and fails to automatically select the musl flavor on native musl systems. Using target.libc() is more robust as it handles both the global setting and runtime detection for any target platform, aligning with the principle of using specific parameters over general context.

        let flavor = settings.node.flavor.as_deref().or(target.libc().filter(|&l| l == "musl"));
References
  1. When overriding a method or working within a specific context, use the provided parameters (like target) instead of re-deriving values from a more general context (like settings) to ensure configurations are correctly applied.

Comment thread src/plugins/core/node.rs Outdated
Comment on lines +872 to +878
let flavor = settings.node.flavor.as_deref().or_else(|| {
if settings.os() == "linux" && settings.libc() == Some("musl") {
Some("musl")
} else {
None
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the issue in build_platform_slug, this logic should respect the detected libc of the current platform rather than just the explicit setting. This ensures native musl systems get the correct flavor automatically.

    let flavor = settings.node.flavor.as_deref().or(crate::platform::Platform::current().libc().filter(|&l| l == "musl"));

Comment thread src/plugins/core/python.rs Outdated
Comment on lines +888 to +894
let libc = settings.libc().unwrap_or_else(|| {
if built_info::CFG_ENV == "musl" {
"musl"
} else {
"gnu"
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using built_info::CFG_ENV for libc detection is unreliable as it reflects the environment where mise was compiled, not the environment where it is running. This will fail to detect musl on a native musl system if mise was built for glibc. It should use the runtime detection logic, ideally via Platform::current().libc().

        let libc = settings.libc().or(crate::platform::Platform::current().libc()).unwrap_or("gnu");

Comment thread src/backend/aqua.rs
Comment on lines +2468 to +2482
asset_strs.iter().find_map(|expected| {
let exact = assets
.iter()
.find(|a| a.name == *expected || a.name.to_lowercase() == expected.to_lowercase());

if prefer_musl
&& let Some(musl_asset) = assets
.iter()
.find(|a| is_musl_variant_of_expected_asset(&a.name, expected))
{
return Some(musl_asset);
}

exact
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The asset matching logic performs redundant tokenization and allocations for every asset in the release for every expected string. For better performance, consider pre-calculating the tokens for all assets once before the asset_strs loop.

Comment thread src/backend/aqua.rs Outdated
Comment on lines +2491 to +2492
&& without_libc_variant_tokens(&asset_tokens)
== without_libc_variant_tokens(&expected_tokens)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This comparison allocates two new Vec on every call, which is inefficient in a nested loop. Using itertools::equal with filtered iterators avoids these allocations.

        && itertools::equal(
            asset_tokens.iter().filter(|&t| !matches!(t.as_str(), "musl" | "gnu" | "glibc")),
            expected_tokens.iter().filter(|&t| !matches!(t.as_str(), "musl" | "gnu" | "glibc"))
        )

Comment thread src/backend/aqua.rs Outdated
Comment on lines +2502 to +2508
fn without_libc_variant_tokens(tokens: &[String]) -> Vec<String> {
tokens
.iter()
.filter(|token| !matches!(token.as_str(), "musl" | "gnu" | "glibc"))
.cloned()
.collect()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function becomes unused if the optimized iterator-based comparison is used in is_musl_variant_of_expected_asset. It can be removed to reduce code complexity.

@jdx jdx force-pushed the codex/libc-setting-backends branch from 06c15fe to bc808e4 Compare April 26, 2026 15:59
@jdx jdx marked this pull request as draft April 26, 2026 16:00
@jdx jdx marked this pull request as ready for review April 26, 2026 16:01
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR introduces a global libc setting (MISE_LIBC / libc) that lets users pin Linux precompiled binary selection to musl or glibc/gnu across multiple backends — aqua, Node, Python, Bun, and vfox. Platform libc detection is now layered: the explicit setting takes precedence, then runtime linker-file detection, then compile-time fallback.

Confidence Score: 5/5

Safe to merge; no P1/P0 issues found. The two P2 observations are a documented behavior improvement and a minor assumption in the fuzzy matcher.

No correctness bugs or security issues were identified. The logic threading libc preference through platform detection, cache keys, and backends is internally consistent and well-tested. The python_os change is a behavior improvement (more correct) that is a minor migration concern for existing users.

src/plugins/core/python.rs — behavior change for musl-compiled mise binaries on glibc hosts worth documenting in a migration/changelog note.

Important Files Changed

Filename Overview
src/platform.rs Adds Platform::libc() that parses the qualifier for libc tokens; Platform::current() now honors settings.libc() before falling back to runtime detection. The old MISE_LIBC env-var fast-path in is_musl_system() is removed, correctly centralising override logic in current().
src/backend/aqua.rs Two-layer libc support for aqua: apply_aqua_libc_replacement rewrites registry replacements["linux"] triples, and select_github_release_asset_for_target adds musl-variant fuzzy matching for GitHub release assets when aqua templates can't express the variant yet. New helpers are well-tested.
src/plugins/core/python.rs python_os() now derives libc from Platform::current().libc() instead of compile-time CFG_ENV. This is more correct but changes behavior for users running a musl-compiled mise binary on a glibc host — they previously received musl Python, now receive glibc Python.
src/plugins/core/node.rs Both slug() and build_platform_slug() now derive musl flavor from Platform::current().libc() when node.flavor is unset. Glibc remains the default (no suffix) and node-specific flavors like glibc-217 still take precedence.
src/cache.rs Adds Platform::current().libc() to the global cache key. Using the resolved qualifier (empty string for glibc, "musl" for musl) ensures explicit libc=gnu and an unset libc produce identical cache keys on glibc systems, avoiding spurious cache misses.
src/config/settings.rs Adds Settings::libc() which normalises "glibc"/"gnu" → "gnu" and returns None for unrecognised values, cleanly centralising the alias resolution.
src/plugins/vfox_plugin.rs Sets vfox.runtime_env_type from settings.libc() before constructing plugins, threading the global libc preference into vfox's RUNTIME global without changing the singleton itself.
crates/vfox/src/runtime.rs Runtime::get() accepts an optional env_type_override, applied after cloning the singleton, so the singleton's own detection is not mutated between callers.
src/backend/asset_matcher.rs AssetLibc::matches_target now splits on - and matches any part, correctly supporting compound qualifiers like musl-baseline. New test verifies compound qualifier libc matching.
src/backend/platform_target.rs Adds PlatformTarget::libc() delegating to Platform::libc(), with tests for simple and compound qualifiers.
src/plugins/core/bun.rs is_musl() now consults Settings::get().libc() before the compile-time cfg!(target_env = "musl") fallback, allowing the global setting to override binary detection for musl/glibc selection.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Tool Install / Lock Request"] --> B{"settings.libc() set?"}
    B -- "musl / glibc / gnu" --> C["Normalize: musl → musl\nglibc / gnu → gnu"]
    B -- "unset" --> D["Platform::current()\nRuntime linker detection\n(ld-linux-* / ld-musl-*)"]
    D -- "no linker found" --> E["Compile-time fallback\ncfg!(target_env = musl)"]
    C --> F["Platform::current() qualifier"]
    D --> F
    E --> F
    F -- "qualifier = Some(musl)" --> G["libc = musl"]
    F -- "qualifier = None" --> H["libc = glibc (default)"]
    G --> I{"Backend"}
    H --> I
    I -- "aqua" --> J["apply_aqua_libc_replacement\n+ musl-variant fuzzy matching"]
    I -- "Node" --> K["append -musl flavor when musl"]
    I -- "Python" --> L["unknown-linux-{musl|gnu}"]
    I -- "Bun" --> M["is_musl() → musl slug"]
    I -- "vfox" --> N["Vfox.runtime_env_type\n→ RUNTIME.envType"]
    F --> O["CacheManagerBuilder\nkey += libc qualifier"]
Loading

Fix All in Claude Code

Reviews (3): Last reviewed commit: "feat(backend): add global libc preferenc..." | Re-trigger Greptile

Comment thread src/cache.rs
Comment thread schema/mise.json Outdated
Comment thread src/backend/aqua.rs
Comment thread src/plugins/core/node.rs
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 26, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 x -- echo 23.9 ± 0.4 23.2 28.8 1.00
mise x -- echo 24.4 ± 0.7 23.8 32.3 1.02 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 env 23.3 ± 0.5 22.5 27.6 1.00
mise env 23.8 ± 0.5 23.1 32.3 1.02 ± 0.03

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 hook-env 24.3 ± 0.7 23.3 26.6 1.00
mise hook-env 25.1 ± 0.6 23.9 27.6 1.03 ± 0.04

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 ls 24.2 ± 0.3 23.5 25.9 1.00
mise ls 25.5 ± 0.6 24.4 27.8 1.05 ± 0.03

xtasks/test/perf

Command mise-2026.4.22 mise Variance
install (cached) 171ms 179ms -4%
ls (cached) 82ms 85ms -3%
bin-paths (cached) 83ms 86ms -3%
task-ls (cached) 795ms 790ms +0%

@jdx jdx force-pushed the codex/libc-setting-backends branch from bc808e4 to a5b17eb Compare April 26, 2026 16:22
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a5b17eb. Configure here.

Comment thread src/backend/aqua.rs
Comment thread src/backend/asset_matcher.rs Outdated
@jdx jdx force-pushed the codex/libc-setting-backends branch from a5b17eb to 16a6a2a Compare April 26, 2026 16:31
@jdx jdx merged commit 09cff3d into main Apr 26, 2026
38 checks passed
@jdx jdx deleted the codex/libc-setting-backends branch April 26, 2026 16:59
jdx added a commit that referenced this pull request Apr 26, 2026
## Summary

[PR #9404](#9404) (`feat(backend): add
global libc preference`) taught Node's slug builder to append `-musl`
for musl targets but kept routing through `nodejs.org/dist/`, which does
not host musl tarballs (they live at `unofficial-builds.nodejs.org`).
The visible symptom was in [PR
#9396](#9396 `mise.lock` diff: the
URL gained a `-musl` suffix while the checksum stayed pinned to the
original glibc tarball.

```toml
[tools.node."platforms.linux-x64-musl"]
checksum = "sha256:dbf5b8665..."   # ← still the glibc checksum
url = ".../v24.14.0/node-v24.14.0-linux-x64-musl.tar.gz"   # ← 404, wrong host
```

Mechanically: `resolve_lock_info` builds a 404'ing URL on
`nodejs.org/dist`, fetches the wrong `SHASUMS256.txt` (which doesn't
list `-musl.tar.gz`), gets `None` back, and the lockfile merge preserves
the **stale glibc checksum** alongside the new URL. Anyone running `mise
install` against that lockfile on a musl system would either 404 or hit
a checksum mismatch.

The aqua/github-backed tools in the same release diff updated cleanly
because their checksum source rotates with the artifact. Node is unique
in fetching checksums from a separate `SHASUMS256.txt`.

## Changes

### `src/plugins/core/node.rs`

Add `mirror_url_for(&SettingsNode, filename)` that swaps to
`https://unofficial-builds.nodejs.org/download/release/` when a filename
references a musl artifact and the user has not set a custom
`node.mirror_url`. Wire it into `resolve_lock_info`, `get_tarball_url`,
`BuildOpts::new`, and `shasums_url` so the tarball URL and the matching
`SHASUMS256.txt` always come from the same host. Three unit tests cover
routing (default → glibc, musl → unofficial-builds, custom mirror passes
through unchanged).

### `src/lockfile.rs`

Defense in depth: when merging `PlatformInfo` (both in
`set_platform_info` and `merge_with`), drop the other side's
`checksum`/`size`/`url_api` if URLs disagree — those fields describe a
specific artifact and become stale once the URL changes. The
pre-existing `test_platform_info_merge_prefers_sha256` was asserting
that sha256 should win even across mismatched URLs, which is exactly the
latent bug; updated it to use a shared URL and added
`test_platform_info_merge_drops_stale_checksum_on_url_change`.

### `mise.lock`

Re-ran `mise lock node` to fix the three corrupted node musl entries.
Checksums verified against upstream:

```sh
$ curl -fsSL https://unofficial-builds.nodejs.org/download/release/v24.14.0/SHASUMS256.txt | grep -E "linux-(arm64|x64)-musl\.tar\.gz"
8f81d47b7f...  node-v24.14.0-linux-arm64-musl.tar.gz
bae0f23204...  node-v24.14.0-linux-x64-musl.tar.gz
```

Both match what mise now writes.

## Test plan

- [x] `cargo test` — 778 passed, 0 failed (includes 3 new node tests + 1
new lockfile test, plus an updated test that previously asserted the
latent bug).
- [x] `cargo clippy --all-features --tests` — clean.
- [x] `cargo fmt` — clean.
- [x] `mise lock node` against this branch produces correct URLs +
checksums for all three node musl platform variants.
- [ ] Install on Alpine / musl host: `MISE_LIBC=musl mise install
node@24.14.0` should download from `unofficial-builds.nodejs.org` and
run.
- [ ] Glibc regression: same flow without `MISE_LIBC=musl` should still
hit `nodejs.org/dist`.

*This PR was generated by an AI coding assistant.*

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes Node.js download/checksum URL selection and lockfile merge
behavior, which can affect installs and lockfile correctness across
platforms; scope is contained with added tests.
> 
> **Overview**
> Fixes Node.js *musl* installs/lock resolution by routing musl tarball
URLs (and their matching `SHASUMS256.txt`/signature URLs) to
`unofficial-builds.nodejs.org` when using the default mirror, while
still respecting user-configured `node.mirror_url`.
> 
> Hardens lockfile merging so when a platform artifact `url` changes,
stale artifact-bound fields (`checksum`, `size`, `url_api`) from the
other side are dropped instead of preserved, preventing mismatched
URL+checksum pairs.
> 
> Regenerates `mise.lock` Node musl entries to use the unofficial-builds
URLs with updated sha256 checksums, and adds/updates unit tests covering
the new mirror routing and merge semantics.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
dd68707. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jdx pushed a commit that referenced this pull request Apr 26, 2026
### 🚀 Features

- **(backend)** add global libc preference by @jdx in
[#9404](#9404)
- opt-in to pre-release versions for github and aqua backends by
@jakedgy in [#9329](#9329)

### 🐛 Bug Fixes

- **(backend)** allow unresolved latest opt-in by @jdx in
[#9401](#9401)
- **(install)** stop rewriting healthy runtime symlinks by @jdx in
[#9410](#9410)
- **(node)** route musl tarball URLs to unofficial-builds by @jdx in
[#9409](#9409)
- **(prune)** skip remote version resolution for tracked configs by @jdx
in [#9406](#9406)
- **(schema)** allow array values in tool additionalProperties by
@JP-Ellis in [#9400](#9400)

### 📦️ Dependency Updates

- bump communique to 1.1.2 by @jdx in
[#9402](#9402)

### 📦 Registry

- use aqua for rumdl by @scop in
[#9397](#9397)

### Chore

- **(ci)** improve pr-closer workflow by @jdx in
[#9403](#9403)
- **(release)** stop appending sponsor blurb when communique succeeds by
@jdx in [#9395](#9395)

### New Contributors

- @JP-Ellis made their first contribution in
[#9400](#9400)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant