feat: auto-select no-YJIT Ruby on older glibc systems#8069
Conversation
Summary of ChangesHello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the Ruby installation process by automatically selecting the appropriate Ruby variant based on the system's glibc version. This ensures compatibility with older Linux distributions that do not support YJIT builds, while still utilizing YJIT on newer systems. The changes involve detecting the glibc version, preferring the no-YJIT variant when necessary, and falling back to the standard build if the preferred variant is unavailable. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull request overview
Adds Linux glibc-version detection to automatically prefer the “no-YJIT” precompiled Ruby artifacts on older glibc systems, while preserving existing behavior on newer systems and non-Linux platforms.
Changes:
- Detect glibc version (cached via
OnceLock) and decide when to prefer no-YJIT assets. - Update GitHub release asset selection to prefer
.no_yjitartifacts on glibc < 2.35 with fallback to standard assets. - Derive downloaded tarball filename from the URL rather than always constructing the standard name.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/plugins/core/ruby.rs
Outdated
| let filename = url | ||
| .rsplit('/') | ||
| .next() | ||
| .unwrap_or(&format!("ruby-{}.{}.tar.gz", tv.version, platform)) | ||
| .to_string(); | ||
| let tarball_path = tv.download_path().join(&filename); | ||
|
|
There was a problem hiding this comment.
unwrap_or(&format!(...)) attempts to return a reference tied to the url string slice lifetime, but format!(...) creates a temporary String that does not live long enough. This will fail to compile due to borrowing a temporary. Restructure this to return an owned String from both branches (e.g., match on url.rsplit('/').next() and to_string() the slice, otherwise format!(...)).
| let filename = url | |
| .rsplit('/') | |
| .next() | |
| .unwrap_or(&format!("ruby-{}.{}.tar.gz", tv.version, platform)) | |
| .to_string(); | |
| let tarball_path = tv.download_path().join(&filename); | |
| let filename = match url.rsplit('/').next() { | |
| Some(name) => name.to_string(), | |
| None => format!("ruby-{}.{}.tar.gz", tv.version, platform), | |
| }; | |
| let tarball_path = tv.download_path().join(&filename); |
src/plugins/core/ruby.rs
Outdated
| } | ||
| match Self::glibc_version() { | ||
| Some((major, minor)) => major < 2 || (major == 2 && minor < 35), | ||
| None => false, // can't detect, assume modern system |
There was a problem hiding this comment.
When glibc detection fails (None), this defaults to the standard (YJIT) build. That undermines the PR’s goal on older/minimal Linux environments where ldd may be missing or its output may not parse, and can lead to selecting an incompatible YJIT artifact. A safer behavior would be to treat None as 'prefer no-YJIT' (and rely on the existing fallback to standard when the .no_yjit asset doesn’t exist).
| None => false, // can't detect, assume modern system | |
| // If detection fails on Linux (e.g. no `ldd` or unparsable output), | |
| // conservatively prefer the no-YJIT variant and rely on the existing | |
| // fallback to the standard build when the `.no_yjit` asset is missing. | |
| None => true, |
src/plugins/core/ruby.rs
Outdated
| fn glibc_version() -> Option<(u32, u32)> { | ||
| static GLIBC_VERSION: OnceLock<Option<(u32, u32)>> = OnceLock::new(); | ||
| *GLIBC_VERSION.get_or_init(|| { | ||
| let output = std::process::Command::new("ldd") |
There was a problem hiding this comment.
Invoking ldd via PATH (Command::new(\"ldd\")) allows PATH hijacking (running an unexpected binary). Consider using a more robust approach: (1) prefer absolute paths like /usr/bin/ldd (optionally probing known locations), or (2) avoid spawning a process by using a libc-based glibc version query when available and falling back to a command only if needed.
| let output = std::process::Command::new("ldd") | |
| // Prefer absolute ldd paths to avoid PATH hijacking. | |
| // Try a small set of common locations and fall back to None if none exist. | |
| const LDD_CANDIDATES: [&str; 2] = ["/usr/bin/ldd", "/bin/ldd"]; | |
| let ldd_path = LDD_CANDIDATES | |
| .iter() | |
| .map(Path::new) | |
| .find(|p| p.is_file())?; | |
| let output = std::process::Command::new(ldd_path) |
There was a problem hiding this comment.
Code Review
This pull request introduces a feature to automatically select a no-YJIT precompiled Ruby variant on Linux systems with glibc versions older than 2.35. The implementation correctly detects the glibc version and modifies the asset selection logic. The changes are well-structured. I've provided a couple of suggestions to improve the robustness and performance of the new logic.
| for release in &releases { | ||
| for asset in &release.assets { | ||
| if asset.name == no_yjit_name && no_yjit_asset.is_none() { | ||
| no_yjit_asset = | ||
| Some((asset.browser_download_url.clone(), asset.digest.clone())); | ||
| } | ||
| if asset.name == standard_name && standard_asset.is_none() { | ||
| standard_asset = | ||
| Some((asset.browser_download_url.clone(), asset.digest.clone())); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This loop can be slightly optimized. By using if/else if, we avoid a second string comparison for each asset. Also, we can break out of the loop once both no_yjit_asset and standard_asset have been found, which avoids iterating through all remaining releases unnecessarily.
for release in &releases {
for asset in &release.assets {
if no_yjit_asset.is_none() && asset.name == no_yjit_name {
no_yjit_asset =
Some((asset.browser_download_url.clone(), asset.digest.clone()));
} else if standard_asset.is_none() && asset.name == standard_name {
standard_asset =
Some((asset.browser_download_url.clone(), asset.digest.clone()));
}
}
if no_yjit_asset.is_some() && standard_asset.is_some() {
break;
}
}
src/plugins/core/ruby.rs
Outdated
| let filename = url | ||
| .rsplit('/') | ||
| .next() | ||
| .unwrap_or(&format!("ruby-{}.{}.tar.gz", tv.version, platform)) | ||
| .to_string(); |
There was a problem hiding this comment.
The current logic to extract the filename from the URL can result in an empty string if the URL ends with a /. Using find(|s| !s.is_empty()) will correctly find the last non-empty path segment, making this more robust.
let filename = url
.rsplit('/')
.find(|s| !s.is_empty())
.unwrap_or(&format!("ruby-{}.{}.tar.gz", tv.version, platform))
.to_string();
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.8 x -- echo |
23.5 ± 0.6 | 22.3 | 29.8 | 1.00 |
mise x -- echo |
24.0 ± 0.7 | 22.0 | 26.1 | 1.02 ± 0.04 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.8 env |
22.6 ± 0.7 | 21.0 | 27.8 | 1.00 |
mise env |
24.2 ± 0.5 | 21.4 | 26.1 | 1.07 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.8 hook-env |
24.0 ± 0.7 | 22.1 | 32.2 | 1.00 |
mise hook-env |
24.8 ± 0.6 | 22.9 | 27.3 | 1.04 ± 0.04 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.8 ls |
21.9 ± 0.7 | 20.3 | 23.8 | 1.00 |
mise ls |
22.8 ± 0.8 | 20.8 | 24.8 | 1.04 ± 0.05 |
xtasks/test/perf
| Command | mise-2026.2.8 | mise | Variance |
|---|---|---|---|
| install (cached) | 126ms | 126ms | +0% |
| ls (cached) | 76ms | 77ms | -1% |
| bin-paths (cached) | 80ms | 82ms | -2% |
| task-ls (cached) | 558ms | 553ms | +0% |
On Linux with glibc < 2.35, automatically prefer the no-YJIT precompiled Ruby variant from jdx/ruby. YJIT builds require glibc 2.35+ while no-YJIT builds target glibc 2.17, supporting older distros like RHEL 7, Amazon Linux 2, and CentOS 7. Falls back to the standard YJIT build if no no-YJIT variant is available for the requested version. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3296770 to
a8a7b55
Compare
- Fix borrowing temporary in filename extraction using match - Optimize asset search loop with if/else if and early break - Fix lockfile determinism by passing prefer_no_yjit parameter instead of reading host glibc in resolve_lock_info Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
### 🚀 Features - auto-select no-YJIT Ruby on older glibc systems by @jdx in [#8069](#8069) ### 🐛 Bug Fixes - **(shim)** update mise-shim.exe during self-update on Windows by @jdx in [#8075](#8075) - Bump xx to 2.5 by @erickt in [#8077](#8077) ### 📚 Documentation - **(ruby)** remove experimental language for precompiled binaries by @jdx in [#8073](#8073) ### New Contributors - @erickt made their first contribution in [#8077](#8077) ## 📦 Aqua Registry Updates #### Updated Packages (1) - [`carthage-software/mago`](https://github.com/carthage-software/mago)
## Summary - On Linux with glibc < 2.35, automatically download the no-YJIT precompiled Ruby variant from jdx/ruby - YJIT builds require glibc 2.35+ (Ubuntu 22.04+, Debian 12+, Fedora 36+) - No-YJIT builds target glibc 2.17, supporting older distros (RHEL 7, Amazon Linux 2, CentOS 7) - Detects glibc version via `ldd --version`, cached for the process lifetime - Falls back to standard YJIT build if no no-YJIT variant exists for the requested version Depends on jdx/ruby#11 which adds no-YJIT Linux variants to the release pipeline. ## Test plan - [ ] On glibc 2.35+ system: installs standard YJIT build (unchanged behavior) - [ ] On glibc < 2.35 system: installs `.no_yjit.` variant - [ ] On glibc < 2.35 with version that has no no-YJIT variant: falls back to standard build - [ ] On macOS: unchanged behavior (always YJIT) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes runtime platform detection and alters which Ruby tarball is downloaded on Linux, which could cause install failures on edge cases (missing `ldd`, non-glibc systems, or unexpected `ldd --version` output). > > **Overview** > Precompiled Ruby installs now *auto-select* the `.no_yjit` release asset on Linux when detected glibc is older than 2.35, with fallback to the standard precompiled build if the variant isn’t available. > > This adds a process-wide glibc version detector (`LINUX_GLIBC_VERSION` via `ldd --version`) and updates the Ruby plugin’s GitHub release asset selection and download filename handling to support the no-YJIT artifact naming. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8804fe6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
### 🚀 Features - auto-select no-YJIT Ruby on older glibc systems by @jdx in [jdx#8069](jdx#8069) ### 🐛 Bug Fixes - **(shim)** update mise-shim.exe during self-update on Windows by @jdx in [jdx#8075](jdx#8075) - Bump xx to 2.5 by @erickt in [jdx#8077](jdx#8077) ### 📚 Documentation - **(ruby)** remove experimental language for precompiled binaries by @jdx in [jdx#8073](jdx#8073) ### New Contributors - @erickt made their first contribution in [jdx#8077](jdx#8077) ## 📦 Aqua Registry Updates #### Updated Packages (1) - [`carthage-software/mago`](https://github.com/carthage-software/mago)
Summary
ldd --version, cached for the process lifetimeDepends on jdx/ruby#11 which adds no-YJIT Linux variants to the release pipeline.
Test plan
.no_yjit.variant🤖 Generated with Claude Code
Note
Medium Risk
Changes runtime platform detection and alters which Ruby tarball is downloaded on Linux, which could cause install failures on edge cases (missing
ldd, non-glibc systems, or unexpectedldd --versionoutput).Overview
Precompiled Ruby installs now auto-select the
.no_yjitrelease asset on Linux when detected glibc is older than 2.35, with fallback to the standard precompiled build if the variant isn’t available.This adds a process-wide glibc version detector (
LINUX_GLIBC_VERSIONvialdd --version) and updates the Ruby plugin’s GitHub release asset selection and download filename handling to support the no-YJIT artifact naming.Written by Cursor Bugbot for commit 8804fe6. This will update automatically on new commits. Configure here.