feat(system): build source formulae from source with a native formula DSL shim#10364
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an end-to-end source-build path for Homebrew formulae: API/schema extensions, a Ruby Formula-DSL shim, a full source-build pipeline, install-time pour-or-build branching with updated receipts, documentation, and an e2e test. ChangesHomebrew source-build integration
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Greptile SummaryThis PR adds native source builds for
Confidence Score: 5/5Safe to merge; the new source-build path is well-isolated with upfront buildability checks, mandatory sha256 verification, loud failures for unsupported DSL, and keg rollback on error. Both findings are in the build-root cleanup path that runs only after the keg is already correctly installed: one is a theoretical panic on a malformed Homebrew API response (SHA256 is by definition 64 hex chars), the other is a false-failure UX issue if src/system/packages/brew/source.rs — the two minor issues are both in this file's post-build cleanup and cache-path construction. Important Files Changed
Reviews (4): Last reviewed commit: "feat(system): build source formulae nati..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/system/packages/brew/mod.rs (1)
170-170: 📐 Maintainability & Code Quality | ⚡ Quick winPrefer
.expect()over.unwrap()to document the invariant.The
.unwrap()here is safe because any formula without a bottle (reaching thisNonearm) was classified insource_buildsat lines 83-86, which guaranteedbrew_binisSomeat lines 87-106. However, this invariant is subtle and spans ~70 lines of code. Using.expect()with a message would make the guarantee explicit and aid future maintainers.🔍 Suggested change
- source::build(brew_bin.as_ref().unwrap(), name, &*pr).and_then(|()| { + source::build( + brew_bin.as_ref().expect("brew_bin is Some when source builds required"), + name, + &*pr + ).and_then(|()| {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/system/packages/brew/mod.rs` at line 170, Replace the call to brew_bin.as_ref().unwrap() passed into source::build with brew_bin.as_ref().expect(...) to document the invariant; specifically, update the invocation in the source::build(...) call so it uses expect with a clear message (e.g., indicating that brew_bin must be Some because formulas without bottles are handled by the source_builds branch) referencing the local variable brew_bin and the call site source::build to make the guarantee explicit for future maintainers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/config/config_file/mod.rs`:
- Around line 308-314: The prefilter in Config::get_tracked_config_files
currently drops any path where is_path_trusted(path) is false, which removes
safe configs that were loaded without trust; instead, change the logic to be
file-type-aware or to attempt parse() first and only exclude paths that actually
fail with UntrustedConfig. Concretely, in Config::get_tracked_config_files
replace the unconditional is_path_trusted check with either (a) a quick
file-type check to let "safe" config files through, or preferably (b) call
parse(path) and catch errors, only filtering out the path when the returned
error matches UntrustedConfig (keeping files that parse successfully or fail for
other reasons); use the existing symbols is_path_trusted, config_trust_root,
parse(), and UntrustedConfig to locate and implement this behavior.
In `@src/config/mod.rs`:
- Around line 2606-2608: The require_task_include_trust flag is computed from
any trusted config in configs, which lets a trusted sibling vouch for includes
owned by an untrusted config; change the logic so you determine which config
actually supplied task_config.includes (the config where includes was taken
from), call is_path_trusted on that specific config's path to set
require_task_include_trust for those includes, and only if includes came from
the default global dirs fall back to configs.iter().any(|cf|
is_path_trusted(cf.get_path())); apply the same per-source check where similar
logic is used around the code handling include lookup (the block that references
require_task_include_trust and trust_check_task_include and the handling at
lines ~2610-2618).
---
Nitpick comments:
In `@src/system/packages/brew/mod.rs`:
- Line 170: Replace the call to brew_bin.as_ref().unwrap() passed into
source::build with brew_bin.as_ref().expect(...) to document the invariant;
specifically, update the invocation in the source::build(...) call so it uses
expect with a clear message (e.g., indicating that brew_bin must be Some because
formulas without bottles are handled by the source_builds branch) referencing
the local variable brew_bin and the call site source::build to make the
guarantee explicit for future maintainers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 05b1dc9f-7316-4865-8d05-a64568187748
📒 Files selected for processing (13)
docs/cli/trust.mddocs/faq.mddocs/system-packages/brew.mde2e/config/test_trust_safe_confige2e/tasks/test_task_monorepo_truste2e/tasks/test_task_untrusted_config_errormise.usage.kdlsrc/cli/trust.rssrc/config/config_file/mise_toml.rssrc/config/config_file/mod.rssrc/config/mod.rssrc/system/packages/brew/mod.rssrc/system/packages/brew/source.rs
…nical prefix brew derives its install prefix from its own location (HOMEBREW_PREFIX cannot redirect it), so a PATH brew rooted elsewhere would install kegs where mise never looks. Verify candidates with `brew --prefix` and reject mismatches. Addresses Cursor Bugbot review feedback on #10364. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f35ba2a to
9635483
Compare
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.5 x -- echo |
19.5 ± 0.9 | 17.6 | 23.3 | 1.00 |
mise x -- echo |
20.4 ± 1.7 | 18.4 | 44.3 | 1.05 ± 0.10 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.5 env |
19.0 ± 0.9 | 17.4 | 24.6 | 1.00 |
mise env |
19.7 ± 0.9 | 18.0 | 24.4 | 1.04 ± 0.07 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.5 hook-env |
19.8 ± 0.8 | 17.8 | 22.6 | 1.00 |
mise hook-env |
20.3 ± 0.9 | 18.4 | 24.3 | 1.03 ± 0.06 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.6.5 ls |
16.1 ± 0.8 | 14.4 | 19.5 | 1.00 |
mise ls |
16.7 ± 0.9 | 15.1 | 21.9 | 1.04 ± 0.07 |
xtasks/test/perf
| Command | mise-2026.6.5 | mise | Variance |
|---|---|---|---|
| install (cached) | 134ms | 134ms | +0% |
| ls (cached) | 59ms | 59ms | +0% |
| bin-paths (cached) | 64ms | 65ms | -1% |
| task-ls (cached) | 125ms | 127ms | -1% |
902a631 to
7022904
Compare
7022904 to
4d94b2d
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/cli/test_system_install_brew_source_slow`:
- Around line 14-16: Replace the fixed HOME-based prefix with a per-test
temporary directory: in test_system_install_brew_source_slow set
MISE_SYSTEM_BREW_PREFIX to a fresh temp dir (e.g. use mktemp -d or a test helper
that returns a unique path) before exporting
MISE_SYSTEM_BREW_FORCE_SOURCE=hello, and ensure the tmp dir is unique per run
(and optionally cleaned up). This guarantees the source-build path and the
Cellar/hello/* receipt glob read only artifacts from the current test run rather
than stale files from $HOME.
In `@src/system/packages/brew/shim.rb`:
- Around line 218-235: The install implementation currently moves sources (in
install_one using FileUtils.mv) which deletes the original and breaks subsequent
installs of the same artifact; change install semantics to copy instead of move
so a single source can be installed to multiple destinations. In the install_one
method (and the Pathname#install behavior), replace the move with a copy
operation that handles files, symlinks and directories (e.g., use a recursive
copy that preserves symlinks/permissions) so the source remains intact after
install.
- Around line 400-401: The current shim implements def patch(*args, &) =
opoo("resource patches are not supported by mise's build shim") which only
warns; change the Resource#patch shim to fail hard instead of warning by
replacing the opoo path with a fatal failure (raise an exception or call odie)
that includes the same explanatory message so any attempt to call patch on a
resource immediately aborts the build and surfaces the incompatibility; keep the
livecheck stub as-is.
In `@src/system/packages/brew/source.rs`:
- Around line 266-273: The code stages a non-archive file using the cached
prefixed name (`basename`), producing files like "abcdef-foo"; instead, derive
the original filename by stripping the cache checksum prefix from `basename`
(remove the leading "<sha>-..." prefix or split on the first '-' and take the
remainder) and use that original name when copying into the stage; keep using
`TarFormat::from_file_name(&basename)`/`format.is_archive()` for detection but
call `crate::file::copy(archive, stage.join(&original_basename))` in the
non-archive branch so the staged file has the original basename referenced by
formulas.
- Around line 62-79: check_buildable currently permits missing tap_git_head and
ruby_source_checksum, allowing fetch_formula_rb to fall back to HEAD and skip
verification; update check_buildable(Formula) to also require a pinned tap git
head and a verified ruby source checksum: after obtaining src via
Formula::stable_url() and before returning Ok, bail with clear error messages if
formula.tap_git_head is None and if src.ruby_source_checksum (or its sha256
field) is None, similar to the existing checks for src.checksum and
ruby_source_path so fetch_formula_rb cannot proceed without pinned, verified
formula-Ruby metadata.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9832d1d5-cdf4-4e06-9f38-d53d4747cb45
📒 Files selected for processing (12)
docs/system-packages/brew.mde2e/cli/test_system_install_brew_source_slowsrc/system/packages/apt.rssrc/system/packages/brew/api.rssrc/system/packages/brew/mod.rssrc/system/packages/brew/pour.rssrc/system/packages/brew/resolve.rssrc/system/packages/brew/shim.rbsrc/system/packages/brew/source.rssrc/system/packages/dnf.rssrc/system/packages/mod.rssrc/system/packages/pacman.rs
✅ Files skipped from review due to trivial changes (1)
- docs/system-packages/brew.md
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/system/packages/brew/shim.rb (1)
272-275: 📐 Maintainability & Code Quality | 💤 Low value
atomic_writeis not actually atomic.The method uses
File.writedirectly without the temp-file-then-rename pattern that makes writes atomic. A crash or interrupt mid-write could leave a corrupted file. Consider using a proper atomic pattern or renaming this method to avoid confusion.Suggested atomic pattern
def atomic_write(content) dirname.mkpath - File.write(to_s, content) + tmp = Pathname.new("#{self}.tmp.#{$$}") + tmp.write(content) + tmp.rename(self) end🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/system/packages/brew/shim.rb` around lines 272 - 275, The method atomic_write currently calls File.write(to_s, content) and is not atomic; change it to write the data to a temporary file in the same directory (e.g., build a tmp path from to_s), flush/fsync the temp file, then rename (File.rename) the temp file to to_s so the swap is atomic; keep the dirname.mkpath step, ensure the temp file is created in the same directory (so rename is atomic on the same filesystem), and remove or handle the temp file on error. Reference: atomic_write, dirname.mkpath, File.write(to_s, content), and to_s.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/system/packages/brew/shim.rb`:
- Around line 121-122: The brew shim uses Ruby 3.1-only syntax (endless methods
self.mac? / self.linux? and anonymous block-forwarding methods livecheck and
bottle), but ruby_bin() (via
ToolsetBuilder::new().with_args(...).with_default_to_latest(true) and
ts.install_missing_versions(...)) can pick a pinned older Ruby; update ruby
provisioning to enforce a minimum Ruby >= 3.1: in the ruby_bin() flow ensure the
selected/installed version is >= "3.1.0" (if a pinned version is older, override
it by installing/selecting the latest available >=3.1, or fail with a clear
error), and add a short user-facing note/documentation explaining the Ruby ≥3.1
requirement for the brew source-build shim.
---
Nitpick comments:
In `@src/system/packages/brew/shim.rb`:
- Around line 272-275: The method atomic_write currently calls File.write(to_s,
content) and is not atomic; change it to write the data to a temporary file in
the same directory (e.g., build a tmp path from to_s), flush/fsync the temp
file, then rename (File.rename) the temp file to to_s so the swap is atomic;
keep the dirname.mkpath step, ensure the temp file is created in the same
directory (so rename is atomic on the same filesystem), and remove or handle the
temp file on error. Reference: atomic_write, dirname.mkpath, File.write(to_s,
content), and to_s.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e418910b-8ecf-4cec-a8b2-542f63672561
📒 Files selected for processing (12)
docs/system-packages/brew.mde2e/cli/test_system_install_brew_source_slowsrc/system/packages/apt.rssrc/system/packages/brew/api.rssrc/system/packages/brew/mod.rssrc/system/packages/brew/pour.rssrc/system/packages/brew/resolve.rssrc/system/packages/brew/shim.rbsrc/system/packages/brew/source.rssrc/system/packages/dnf.rssrc/system/packages/mod.rssrc/system/packages/pacman.rs
✅ Files skipped from review due to trivial changes (2)
- src/system/packages/dnf.rs
- docs/system-packages/brew.md
🚧 Files skipped from review as they are similar to previous changes (9)
- src/system/packages/apt.rs
- src/system/packages/pacman.rs
- src/system/packages/mod.rs
- src/system/packages/brew/api.rs
- src/system/packages/brew/resolve.rs
- src/system/packages/brew/pour.rs
- e2e/cli/test_system_install_brew_source_slow
- src/system/packages/brew/mod.rs
- src/system/packages/brew/source.rs
| def self.mac? = RbConfig::CONFIG["host_os"].include?("darwin") | ||
| def self.linux? = RbConfig::CONFIG["host_os"].include?("linux") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check what Ruby version mise provisions by default for source builds
# and whether there's any version enforcement
# Search for ruby version requirements/checks in source build code
rg -n 'ruby' src/system/packages/brew/source.rs -C3
# Check if there's any toolset/version configuration
rg -n 'ruby' src/toolset/ --type rust -C2 | head -50
# Look for any minimum version checks
rg -nP 'ruby.*version|minimum.*ruby|ruby.*3\.' --type rustRepository: jdx/mise
Length of output: 9871
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Ensure file exists and print the relevant sections with line numbers
ls -l src/system/packages/brew/shim.rb
echo '--- shim.rb:121-140 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '105,155p'
echo '--- shim.rb:380-430 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '380,430p'
echo '--- shim.rb:520-560 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '520,560p'
echo '--- Search for Ruby min-version enforcement ---'
rg -n "minimum.*ruby|min.*ruby|ruby.*minimum|requires.*ruby|RUBY_VERSION|host_os.*darwin|3\.0|3\.1|3\.2|3\.3|3\.4" src/system/packages/brew/source.rs src/plugins/core/ruby.rs src/plugins/core/ruby_common.rs src/cli -S || true
echo '--- Search broader for "3.1" in repo ---'
rg -n "3\.1" src -S || trueRepository: jdx/mise
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -l src/system/packages/brew/shim.rb
echo '--- shim.rb:121-140 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '105,155p'
echo '--- shim.rb:380-430 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '380,430p'
echo '--- shim.rb:520-560 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '520,560p'
echo '--- Search for Ruby min-version enforcement ---'
rg -n "minimum.*ruby|min.*ruby|ruby.*minimum|requires.*ruby|RUBY_VERSION" src -S || true
echo '--- Search for explicit Ruby >= version checks ---'
rg -n "RbConfig::CONFIG\\[\"host_os\"\\]|include\\?\\(\"linux\"\\)|include\\?\\(\"darwin\"\\)" -S src/system/packages/brew/shim.rb || trueRepository: jdx/mise
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -l src/system/packages/brew/shim.rb
echo '--- shim.rb:121-140 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '105,155p'
echo '--- shim.rb:380-430 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '380,430p'
echo '--- shim.rb:520-560 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '520,560p'
echo '--- Search for Ruby min-version enforcement (repo-wide) ---'
rg -n "minimum.*ruby|min.*ruby|ruby.*minimum|requires.*ruby|RUBY_VERSION|Gem::Version.*ruby|Gem::Version.*RUBY" src -S || true
echo '--- Search for explicit Ruby version comparisons (repo-wide) ---'
rg -n "Gem::Version|RUBY_VERSION|RbConfig|ruby\\s*[<>=]=?|ruby\\s*>=|ruby\\s*3\\." src/system/packages/brew/shim.rb src -S || true
echo '--- Search for "3.1" occurrences (repo-wide) ---'
rg -n "3\\.1" src -S || trueRepository: jdx/mise
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -l src/system/packages/brew/shim.rb
echo '--- shim.rb:121-140 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '105,155p'
echo '--- shim.rb:380-430 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '380,430p'
echo '--- shim.rb:520-560 ---'
nl -ba src/system/packages/brew/shim.rb | sed -n '520,560p'
echo '--- Search for Ruby min-version enforcement (repo-wide) ---'
rg -n "minimum.*ruby|min.*ruby|ruby.*minimum|requires.*ruby|RUBY_VERSION|Gem::Version.*ruby|Gem::Version.*RUBY" src -S || true
echo '--- Search for explicit Ruby version comparisons (repo-wide) ---'
rg -n "Gem::Version|RUBY_VERSION|RbConfig|ruby\\s*[<>=]=?|ruby\\s*>=|ruby\\s*3\\." src/system/packages/brew/shim.rb src -S || true
echo '--- Search for "3.1" occurrences (repo-wide) ---'
rg -n "3\\.1" src -S || trueRepository: jdx/mise
Length of output: 220
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- shim.rb exists ---'
ls -l src/system/packages/brew/shim.rb
echo '--- shim.rb:105-155 (with line numbers) ---'
awk 'NR>=105 && NR<=155 {printf "%6d %s\n", NR, $0}' src/system/packages/brew/shim.rb
echo '--- shim.rb:360-440 (with line numbers) ---'
awk 'NR>=360 && NR<=440 {printf "%6d %s\n", NR, $0}' src/system/packages/brew/shim.rb
echo '--- shim.rb:505-570 (with line numbers) ---'
awk 'NR>=505 && NR<=570 {printf "%6d %s\n", NR, $0}' src/system/packages/brew/shim.rb
echo '--- Repo-wide search for Ruby min-version enforcement patterns ---'
rg -n "minimum.*ruby|min.*ruby|requires.*ruby|RUBY_VERSION|Gem::Version.*ruby|ruby.*Gem::Version|ruby\s*[<>=]=" src -S || true
echo '--- Search for Ruby version comparisons (including 3.0/3.1/3.2...) ---'
rg -n "ruby\s*[<>=]=?\s*['\"]?3\.(0|1|2|3|4)" src -S || true
echo '--- Search for .ruby-version / tool default constraints ---'
rg -n "\.ruby-version|tools\.ruby|set\s+.*ruby|default.*ruby|with_default_to_latest" src -S || true
echo '--- Search for "brew source build" ruby provisioning defaults ---'
rg -n "brew source build|resolve ruby|install_missing_versions|missing_args_only" src/system/packages/brew/source.rs src -S || trueRepository: jdx/mise
Length of output: 14228
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- src/system/packages/brew/source.rs: around ruby_bin() ---'
awk 'NR>=150 && NR<=220 {printf "%6d %s\n", NR, $0}' src/system/packages/brew/source.rs
echo '--- src/system/packages/brew/source.rs: around build invocation (shim execution) ---'
awk 'NR>=105 && NR<=150 {printf "%6d %s\n", NR, $0}' src/system/packages/brew/source.rsRepository: jdx/mise
Length of output: 5238
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- src/toolset/builder.rs: with_default_to_latest implementation ---'
awk 'NR>=1 && NR<=220 {print}' src/toolset/builder.rs | sed -n '1,220p'
echo '--- src/toolset/builder.rs: search for default_to_latest usage ---'
rg -n "default_to_latest" src/toolset/builder.rs
echo '--- src/toolset/builder.rs: around line hits ---'
# get line range around each hit
for l in $(rg -n "default_to_latest" src/toolset/builder.rs | cut -d: -f1 | sort -u); do
start=$((l-60)); if [ $start -lt 1 ]; then start=1; fi
end=$((l+60))
awk -v s=$start -v e=$end 'NR>=s && NR<=e {printf "%6d %s\n", NR, $0}' src/toolset/builder.rs
done
echo '--- src/plugins/core/ruby.rs: locate latest version resolution ---'
rg -n "latest_ruby|default.*ruby|tool.*ruby|resolve.*ruby|with_default_to_latest" src/plugins/core/ruby.rs
echo '--- src/plugins/core/ruby.rs: inspect around latest_ruby_build_version ---'
awk 'NR>=240 && NR<=360 {printf "%6d %s\n", NR, $0}' src/plugins/core/ruby.rsRepository: jdx/mise
Length of output: 30796
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- Search for how ruby resolves "latest" ---'
rg -n "\"latest\"|'latest'|version == \"latest\"|match .*latest" src/plugins/core/ruby.rs src/plugins/core/ruby_common.rs src/plugins/core/ruby_windows.rs src -S
echo '--- Inspect likely resolve paths in ruby plugin ---'
rg -n "fn (resolve|latest)|ToolRequest|ToolVersion|ToolVersion.*latest|list.*latest" src/plugins/core/ruby.rs -S
echo '--- Inspect core tool/version resolution for "latest" if present ---'
rg -n "latest.*(version|resolve)|resolve.*latest|ToolVersion.*latest|\"latest\"" src/toolset src/plugins -SRepository: jdx/mise
Length of output: 25993
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- Search for default/pinned ruby version in repo ---'
rg -n "tools\.ruby|MISE_RUBY_VERSION|MISE_.*_VERSION.*ruby|default.*ruby.*version" -S . || true
echo '--- Search for explicit ruby version pins near system/build logic ---'
rg -n "brew.*ruby|system.*ruby|build.*ruby" src -S || trueRepository: jdx/mise
Length of output: 2772
🌐 Web query:
Ruby "endless method" syntax introduced in which Ruby version "def foo = expr"
💡 Result:
The "endless method" syntax (also known as shorthand method definition), which uses the format def foo = expr, was introduced in Ruby 3.0 [1][2][3]. This feature was implemented as part of Feature #16746 and was officially included in the Ruby 3.0 release on December 25, 2020 [4][1][5].
Citations:
- 1: https://dev.to/lucianghinda/history-of-the-endless-method-syntax-2a2k
- 2: https://allaboutcoding.ghinda.com/history-of-the-endless-method-syntax
- 3: https://learn.shortruby.com/blog/history-of-endless-method
- 4: Endless method definition [Feature #16746] ruby/ruby#2996
- 5: https://bugs.ruby-lang.org/issues/16746
🌐 Web query:
Ruby anonymous block forwarding syntax def foo(&) introduced in which Ruby version
💡 Result:
The anonymous block forwarding syntax, which allows a method to forward a block to another method without naming the block parameter using the def foo(&) syntax, was introduced in Ruby 3.1 [1][2]. While the feature was long discussed under Ruby Feature #11256, it was implemented and officially released as part of the Ruby 3.1.0 release [3][4][2]. The syntax is used when a method simply needs to pass its received block to another method [5][2]. Example of the syntax: def foo(&) bar(&) end
Citations:
- 1: https://writesoftwarewell.com/anonymous-block-forwarding-in-ruby
- 2: https://rubyreferences.github.io/rubychanges/3.1.html
- 3: https://redmine.ruby-lang.org/issues/11256
- 4: https://bugs.ruby-lang.org/issues/11256
- 5: ruby/ruby@4adb012
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- src/task/mod.rs around 3301 ---'
awk 'NR>=3285 && NR<=3325 {printf "%6d %s\n", NR, $0}' src/task/mod.rs
echo '--- src/system/packages/brew/mod.rs ---'
wc -l src/system/packages/brew/mod.rs
awk 'NR>=1 && NR<=120 {printf "%6d %s\n", NR, $0}' src/system/packages/brew/mod.rs
echo '--- src/system/packages/brew/source.rs top-of-file docs ---'
wc -l src/system/packages/brew/source.rs
awk 'NR>=1 && NR<=120 {printf "%6d %s\n", NR, $0}' src/system/packages/brew/source.rsRepository: jdx/mise
Length of output: 12952
Enforce/communicate Ruby ≥ 3.1 for the brew source-build shim
src/system/packages/brew/shim.rb relies on Ruby syntax that requires Ruby 3.1+:
- Endless methods (
def self.mac? = ...) require Ruby 3.0+ - Anonymous block forwarding (
def livecheck(&) = nilanddef bottle(&) = nil) requires Ruby 3.1+
src/system/packages/brew/source.rs provisions the Ruby used to run the shim via ruby_bin() using ToolsetBuilder with with_default_to_latest(true) and otherwise preserves any already-active configured ruby version—there’s no minimum-version guard here. If a user pins Ruby < 3.1, the shim will fail to parse.
Consider documenting the Ruby ≥ 3.1 requirement for mise system install source builds, and/or enforcing it when provisioning Ruby for brew/source.rs (or rewriting the shim to pre-3.1 syntax).
Also applies to: 400-401, 539-543
🧰 Tools
🪛 RuboCop (1.87.0)
[fatal] 121-121: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
[fatal] 122-122: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)
(Lint/Syntax)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/system/packages/brew/shim.rb` around lines 121 - 122, The brew shim uses
Ruby 3.1-only syntax (endless methods self.mac? / self.linux? and anonymous
block-forwarding methods livecheck and bottle), but ruby_bin() (via
ToolsetBuilder::new().with_args(...).with_default_to_latest(true) and
ts.install_missing_versions(...)) can pick a pinned older Ruby; update ruby
provisioning to enforce a minimum Ruby >= 3.1: in the ruby_bin() flow ensure the
selected/installed version is >= "3.1.0" (if a pinned version is older, override
it by installing/selecting the latest available >=3.1, or fail with a clear
error), and add a short user-facing note/documentation explaining the Ruby ≥3.1
requirement for the brew source-build shim.
4d94b2d to
561e1c2
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 561e1c2. Configure here.
Formulae without a usable bottle (source-only formulae, or bottles that only exist for other platforms) previously failed. mise now builds them from source — still without Homebrew installed: - ruby is provisioned through mise's own tool machinery (precompiled by default), so no interpreter needs to be present on the machine - the formula's .rb is downloaded from homebrew/core pinned to the commit the API metadata was generated from and verified against the API's sha256; the source archive is verified the same way - build dependencies are added to the install closure and poured as regular bottles before the build - shim.rb implements the commonly-used subset of the Formula DSL (configure/cmake/meson builds, resources, patches, path and ENV helpers, std_*_args, completions) and runs def install against the canonical prefix; unsupported install-time helpers fail with a clear error instead of miscompiling silently - the keg gets the same brew-compatible receipt as a poured bottle with poured_from_bottle: false, matching how brew marks source builds The system package manager trait is now async_trait(?Send) because the toolset machinery holds non-Send shell state across awaits; the driver awaits managers sequentially so the futures never cross threads. MISE_SYSTEM_BREW_FORCE_SOURCE=<names> (undocumented, like MISE_SYSTEM_BREW_PREFIX) forces the source path for bottled formulae, which the new e2e test uses to build GNU hello without root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
561e1c2 to
1e77b1c
Compare

Summary
The
brewsystem package manager previously hard-failed on formulae without a usable bottle (source-only formulae, or formulae whose bottles only exist for other platforms — e.g. missingarm64_linux). This PR adds native source builds: mise evaluates the formula's Ruby itself and runsdef installagainst the canonical prefix. No Homebrew installation is required — consistent with the integration's "Homebrew formulae without Homebrew" design.How it works
ToolsetBuilder+install_missing_versions, scoped to just ruby) — precompiled by default, so it's a fast download, and a user-configured ruby is respected..rbis downloaded from homebrew/core pinned totap_git_head(the commit the API metadata was generated from) and verified against the API'sruby_source_checksum.urls.stable) is downloaded and sha256-verified against the API metadata.build_dependencies, with per-platformvariationsapplied) are added to the install closure for bottle-less formulae and poured as regular bottles first — so e.g. buildingzopflifrom source pourscmakeas a bottle and builds with it.shim.rb— a fresh ~700-line implementation of the commonly-used Formula DSL subset — evaluates the formula and runsdef installwithPATH/PKG_CONFIG_PATH/compiler flags pointing at the dependency kegs (plus-Wl,-rpath,<prefix>/libon Linux). Covered: configure/cmake/meson-style builds, resources and patches (downloaded with mandatory sha256 verification),on_macos/on_linux/version blocks, path helpers,ENVhelpers (grafted onto the realENVobject the way brew's EnvActivation does),std_*_args,inreplace,generate_completions_from_executable,Formula[]dep references,rpath/loader_path.INSTALL_RECEIPT.jsonas a poured bottle, withpoured_from_bottle: false— exactly how brew marks its own source builds — and is linked through the existinglink_kegpath with the same rollback-on-failure behavior.Failure philosophy
Unknown install-time helpers fail loudly (
formula uses X, which mise's source-build shim does not support) rather than miscompiling silently — these kegs sit in the canonical prefix claiming brew compatibility, so a wrong build is worse than no build. Unknown class-level DSL (new inert metadata likecompatibility_version, which brew adds regularly) warns and continues, so working formulae don't break every time brew grows an annotation. Unbuildable specs (VCS download strategies, missing checksums) are rejected up front, before any pours happen.Other changes
SystemPackageManageris now#[async_trait(?Send)]: the toolset machinery holds non-Send shell state across awaits. The driver awaits managers sequentially on one task, so futures never cross threads.init_footer/footer_inc/footer_finish) the same way tool installs do, so multi-formula installs show themise VERSION by @jdx [cur/total]bar; per-formula jobs were already clx (MultiProgressReport), with build output streaming into the job line and replayed on failure.MISE_SYSTEM_BREW_FORCE_SOURCE=<names>(undocumented, likeMISE_SYSTEM_BREW_PREFIX) forces bottled formulae down the source path for testing.Testing
test_system_install_brew_source_slow): builds GNU hello from source through the full pipeline — ruby provisioning, formula+source fetch/verify, shim build, receipt, link, status, idempotency. Uses a throwaway prefix, so no root needed; passes in ~30s locally.hello(autotools) andzopfli(cmake, with thecmakebuild dep poured as a bottle, keg correctly named1.0.3_1with formula revision) both build, link, and run;lzip(ENV.cxx/ENV.cflags/deparallelize+make check) builds via the standalone shim.Known limitations
virtualenv_install_with_resources, go/cargo resource DSLs beyondstd_*_args) are not implemented — they fail with a clear error naming the feature.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Note
Medium Risk
Large new build surface (Ruby shim + Cellar linking) touches canonical Homebrew prefixes and runs arbitrary compile steps; failures are intended to be loud, but wrong or partial kegs would still be high impact.
Overview
Brew system packages no longer fail when there is no usable bottle for the host. They fall back to native source builds (still without invoking
brew): mise-managed Ruby runs a new Formula DSL shim (shim.rb) after fetching a pin-verified formula.rband stable source from homebrew/core API metadata.Install flow changes: dependency resolution now pulls build dependencies into the closure for bottle-less formulae; dry-run distinguishes pour vs build from source; multi-formula installs use the same overall progress footer as tool installs. Unsupported formula shapes are rejected up front via
check_buildable(e.g. VCS download strategies).INSTALL_RECEIPT.jsonrecordspoured_from_bottlefor both poured and source kegs.Plumbing:
SystemPackageManageris#[async_trait(?Send)]everywhere (brew source path provisions Ruby via toolset with non-Sendstate). API types gain source/build-dep fields. Docs and a slow e2e test (helloforced viaMISE_SYSTEM_BREW_FORCE_SOURCE) cover the pipeline.Reviewed by Cursor Bugbot for commit 1e77b1c. Bugbot is set up for automated code reviews on this repo. Configure here.