Skip to content

feat(system): build source formulae from source with a native formula DSL shim#10364

Merged
jdx merged 1 commit into
mainfrom
claude/admiring-benz-3c27c0
Jun 13, 2026
Merged

feat(system): build source formulae from source with a native formula DSL shim#10364
jdx merged 1 commit into
mainfrom
claude/admiring-benz-3c27c0

Conversation

@jdx

@jdx jdx commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

The brew system 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. missing arm64_linux). This PR adds native source builds: mise evaluates the formula's Ruby itself and runs def install against the canonical prefix. No Homebrew installation is required — consistent with the integration's "Homebrew formulae without Homebrew" design.

How it works

  1. Ruby is provisioned through mise's own tool machinery (ToolsetBuilder + install_missing_versions, scoped to just ruby) — precompiled by default, so it's a fast download, and a user-configured ruby is respected.
  2. The formula .rb is downloaded from homebrew/core pinned to tap_git_head (the commit the API metadata was generated from) and verified against the API's ruby_source_checksum.
  3. The source archive (urls.stable) is downloaded and sha256-verified against the API metadata.
  4. Build dependencies (build_dependencies, with per-platform variations applied) are added to the install closure for bottle-less formulae and poured as regular bottles first — so e.g. building zopfli from source pours cmake as a bottle and builds with it.
  5. shim.rb — a fresh ~700-line implementation of the commonly-used Formula DSL subset — evaluates the formula and runs def install with PATH/PKG_CONFIG_PATH/compiler flags pointing at the dependency kegs (plus -Wl,-rpath,<prefix>/lib on Linux). Covered: configure/cmake/meson-style builds, resources and patches (downloaded with mandatory sha256 verification), on_macos/on_linux/version blocks, path helpers, ENV helpers (grafted onto the real ENV object the way brew's EnvActivation does), std_*_args, inreplace, generate_completions_from_executable, Formula[] dep references, rpath/loader_path.
  6. The keg gets the same brew-compatible INSTALL_RECEIPT.json as a poured bottle, with poured_from_bottle: false — exactly how brew marks its own source builds — and is linked through the existing link_keg path 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 like compatibility_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

  • SystemPackageManager is 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.
  • The install loop drives the clx overall-progress header (init_footer/footer_inc/footer_finish) the same way tool installs do, so multi-formula installs show the mise 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, like MISE_SYSTEM_BREW_PREFIX) forces bottled formulae down the source path for testing.

Testing

  • e2e (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.
  • Manually verified: hello (autotools) and zopfli (cmake, with the cmake build dep poured as a bottle, keg correctly named 1.0.3_1 with formula revision) both build, link, and run; lzip (ENV.cxx/ENV.cflags/deparallelize + make check) builds via the standalone shim.
  • Unit tests for bottle classification, buildability checks, and missing-bottle reasons.

Known limitations

  • Language-ecosystem helpers (virtualenv_install_with_resources, go/cargo resource DSLs beyond std_*_args) are not implemented — they fail with a clear error naming the feature.
  • No superenv compiler shims; the environment is deterministic flags + dep kegs. Complex formulae may fail (loudly) where brew would succeed.
  • Source builds need a toolchain (Xcode CLT / gcc+make), as with brew itself.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Brew packages now fall back to building from source when no usable bottle exists; dry-run output distinguishes pour vs. source-build steps.
    • Source-build flow includes a Ruby-based shim to evaluate formulas and a source-download/verify/stage pipeline.
  • Bug Fixes

    • Clearer errors/progress for source builds; install receipts now record whether a keg was poured or built from source.
    • Dependency resolution now includes build-time deps for source builds.
  • Documentation

    • Updated Homebrew integration docs with Linux/source-formula guidance and revised limitations.
  • Tests

    • Added end-to-end test for building a brew package from source.

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 .rb and 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.json records poured_from_bottle for both poured and source kegs.

Plumbing: SystemPackageManager is #[async_trait(?Send)] everywhere (brew source path provisions Ruby via toolset with non-Send state). API types gain source/build-dep fields. Docs and a slow e2e test (hello forced via MISE_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.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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.

Changes

Homebrew source-build integration

Layer / File(s) Summary
API schema & dependency selection
src/system/packages/brew/api.rs, src/system/packages/brew/resolve.rs
Adds SourceUrl/RubySourceChecksum, formula fields for source metadata and build deps, build_dependencies_for and stable_url() helpers; resolve now enqueues install_deps(...) including build deps when a bottle is not used.
Source build implementation (core)
src/system/packages/brew/source.rs
Implements bottle override, deterministic missing_bottle_reason, check_buildable, Ruby provisioning, fetch_formula_rb, fetch_source, stage_source, build_env, and an async build(...) that stages, runs the shim, executes the build, writes receipts, links kegs, and includes focused unit tests.
Formula DSL shim and packaging primitives
src/system/packages/brew/shim.rb
New Ruby shim providing a minimal Homebrew DSL for evaluating formula .rb: download/verify/unpack, Resource and PatchSpec handling, Pathname/ENV helpers, Formula class, and main entrypoint; fails clearly on unsupported DSL features.
Install flow, pour changes, and async_trait adjustments
src/system/packages/brew/mod.rs, src/system/packages/brew/pour.rs, src/system/packages/mod.rs, src/system/packages/{apt,dnf,pacman}.rs
install_via_pour classifies source builds, validates buildability, updates dry-run wording, branches per-formula to pour or source::build, makes write_receipt(..., poured_from_bottle) public and settable, and switches async-trait annotations to #[async_trait(?Send)] where source builds require non-Send shell state.
Docs and end-to-end test
docs/system-packages/brew.md, e2e/cli/test_system_install_brew_source_slow
Docs state mise never shells out to Homebrew for pouring and describe the source-build pipeline and supported shapes; adds an e2e test that forces and verifies brew:hello builds from source, checks INSTALL_RECEIPT.json has poured_from_bottle:false, and asserts idempotency.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • jdx/mise#10326: Related prior work introducing the initial brew pour-without-Homebrew manager and pouring flow.

Poem

I nibble formulas late at night,
When bottles fail, I set things right.
I stitch the Ruby, fetch the tar,
Pour deps first, then build—hurrah!
A rabbit hops, receipts in sight. 🐇🍵

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: implementing native source builds for Homebrew formulae using a Formula DSL shim, without requiring Homebrew.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread src/config/config_file/mise_toml.rs
Comment thread src/system/packages/brew/source.rs Outdated
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds native source builds for brew: system packages when no usable bottle exists (source-only formulae or missing platform bottles). Instead of hard-failing, mise now provisions a mise-managed Ruby, fetches and checksum-verifies the formula .rb from homebrew/core pinned to tap_git_head, stages the verified source archive, pours build dependencies as bottles, and runs def install via a new ~910-line Formula DSL shim (shim.rb + source.rs) — all without Homebrew installed.

  • Source build pipeline (source.rs): Ruby provisioning via ToolsetBuilder, sha256-verified formula and source downloads, block_in_place for the blocking build subprocess, keg rollback on failure, and brew-compatible INSTALL_RECEIPT.json with poured_from_bottle: false.
  • Formula DSL shim (shim.rb): Implements the commonly-used Formula API subset; unknown install-time helpers fail loudly with a named error; unknown class-level DSL warns and continues to tolerate brew's evolving annotation DSL.
  • Dependency resolution (resolve.rs, api.rs): Build dependencies are added to the install closure only for source-built formulae; dep_tag is now public; Variation gains build_dependencies; API types gain SourceUrl, RubySourceChecksum, and the new Formula fields.
  • SystemPackageManager trait (mod.rs): Changed to async_trait(?Send) so the brew manager can await toolset machinery holding non-Send shell state; concrete types still implement Send + Sync; all other managers updated uniformly.

Confidence Score: 5/5

Safe 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 remove_all on a cache directory fails after a successful build. Neither affects correctness of the installed keg.

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

Filename Overview
src/system/packages/brew/source.rs New 518-line file implementing the source-build pipeline: Ruby provisioning, formula/source fetch+verify, build environment construction, keg installation, receipt writing, and linking. Two minor issues: unchecked sha256 slice panics on malformed API checksums; cleanup failure after a successful build propagates as a failed install.
src/system/packages/brew/shim.rb New ~910-line Ruby DSL shim implementing the commonly-used Formula API subset. Sound design — unknown install-time helpers fail loudly, unknown class-level DSL warns and continues. sha256-verified downloads for resources and patches; block_in_place for the blocking build call is handled on the Rust side. No issues found.
src/system/packages/brew/mod.rs Install loop refactored to branch on bottle vs source-build; adds overall progress footer, dry-run distinguishes pour/build, and previous reviewer concerns (block_in_place, expect on brew_bin) are addressed. No issues found.
src/system/packages/brew/api.rs Adds SourceUrl, RubySourceChecksum structs and build_dependencies/urls/ruby_source_path/ruby_source_checksum/tap_git_head fields to Formula. All new fields use serde(default) and proper Option wrapping.
src/system/packages/brew/resolve.rs dep_tag is now pub for source.rs reuse; install_deps helper correctly adds build_dependencies to the closure only for source-built formulae. Both BFS queue and topological-sort correctly use install_deps.
src/system/packages/brew/pour.rs write_receipt is now pub with a poured_from_bottle parameter; pour call-site passes true. Both built_as_bottle and poured_from_bottle are now data-driven, correctly marking source kegs with false.
src/system/packages/mod.rs SystemPackageManager trait changed to async_trait(?Send) with a clear comment explaining why; all concrete impls (apt, dnf, pacman, brew) updated uniformly. Trait still requires Send + Sync on the concrete types.
e2e/cli/test_system_install_brew_source_slow New e2e test for the source-build pipeline using GNU hello with MISE_SYSTEM_BREW_FORCE_SOURCE. Correctly guards on Linux + known architectures + C toolchain; tests dry-run, install, binary execution, receipt content, and idempotency.
docs/system-packages/brew.md Updated documentation for Linux and source-formula guidance; revised limitations section. No issues.

Reviews (4): Last reviewed commit: "feat(system): build source formulae nati..." | Re-trigger Greptile

Comment thread src/system/packages/brew/mod.rs Outdated
Comment thread src/system/packages/brew/mod.rs Outdated
Comment thread src/config/config_file/mise_toml.rs

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/system/packages/brew/mod.rs (1)

170-170: 📐 Maintainability & Code Quality | ⚡ Quick win

Prefer .expect() over .unwrap() to document the invariant.

The .unwrap() here is safe because any formula without a bottle (reaching this None arm) was classified in source_builds at lines 83-86, which guaranteed brew_bin is Some at 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

📥 Commits

Reviewing files that changed from the base of the PR and between ac042eb and f35ba2a.

📒 Files selected for processing (13)
  • docs/cli/trust.md
  • docs/faq.md
  • docs/system-packages/brew.md
  • e2e/config/test_trust_safe_config
  • e2e/tasks/test_task_monorepo_trust
  • e2e/tasks/test_task_untrusted_config_error
  • mise.usage.kdl
  • src/cli/trust.rs
  • src/config/config_file/mise_toml.rs
  • src/config/config_file/mod.rs
  • src/config/mod.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/source.rs

Comment thread src/config/config_file/mod.rs
Comment thread src/config/mod.rs
jdx added a commit that referenced this pull request Jun 12, 2026
…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>
@jdx jdx force-pushed the claude/admiring-benz-3c27c0 branch from f35ba2a to 9635483 Compare June 12, 2026 23:11
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

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%

@jdx jdx force-pushed the claude/admiring-benz-3c27c0 branch from 902a631 to 7022904 Compare June 13, 2026 00:11
@jdx jdx changed the title feat(system): build source formulae via brew when no bottle exists feat(system): build source formulae from source with a native formula DSL shim Jun 13, 2026
Comment thread src/system/packages/brew/shim.rb
@jdx jdx force-pushed the claude/admiring-benz-3c27c0 branch from 7022904 to 4d94b2d Compare June 13, 2026 00:15
Comment thread src/system/packages/brew/source.rs
Comment thread src/system/packages/brew/source.rs Outdated

@coderabbitai coderabbitai Bot left a comment

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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 902a631 and 7022904.

📒 Files selected for processing (12)
  • docs/system-packages/brew.md
  • e2e/cli/test_system_install_brew_source_slow
  • src/system/packages/apt.rs
  • src/system/packages/brew/api.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/brew/resolve.rs
  • src/system/packages/brew/shim.rb
  • src/system/packages/brew/source.rs
  • src/system/packages/dnf.rs
  • src/system/packages/mod.rs
  • src/system/packages/pacman.rs
✅ Files skipped from review due to trivial changes (1)
  • docs/system-packages/brew.md

Comment thread e2e/cli/test_system_install_brew_source_slow
Comment thread src/system/packages/brew/shim.rb
Comment thread src/system/packages/brew/shim.rb Outdated
Comment thread src/system/packages/brew/source.rs
Comment thread src/system/packages/brew/source.rs Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/system/packages/brew/shim.rb (1)

272-275: 📐 Maintainability & Code Quality | 💤 Low value

atomic_write is not actually atomic.

The method uses File.write directly 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7022904 and 4d94b2d.

📒 Files selected for processing (12)
  • docs/system-packages/brew.md
  • e2e/cli/test_system_install_brew_source_slow
  • src/system/packages/apt.rs
  • src/system/packages/brew/api.rs
  • src/system/packages/brew/mod.rs
  • src/system/packages/brew/pour.rs
  • src/system/packages/brew/resolve.rs
  • src/system/packages/brew/shim.rb
  • src/system/packages/brew/source.rs
  • src/system/packages/dnf.rs
  • src/system/packages/mod.rs
  • src/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

Comment on lines +121 to +122
def self.mac? = RbConfig::CONFIG["host_os"].include?("darwin")
def self.linux? = RbConfig::CONFIG["host_os"].include?("linux")

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.

🎯 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 rust

Repository: 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 || true

Repository: 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 || true

Repository: 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 || true

Repository: 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 || true

Repository: 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 || true

Repository: 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.rs

Repository: 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.rs

Repository: 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 -S

Repository: 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 || true

Repository: 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:


🌐 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:


🏁 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.rs

Repository: 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(&) = nil and def 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.

@jdx jdx force-pushed the claude/admiring-benz-3c27c0 branch from 4d94b2d to 561e1c2 Compare June 13, 2026 00:33

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 1 potential issue.

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 561e1c2. Configure here.

Comment thread src/system/packages/brew/source.rs Outdated
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>
@jdx jdx force-pushed the claude/admiring-benz-3c27c0 branch from 561e1c2 to 1e77b1c Compare June 13, 2026 00:38
@jdx jdx enabled auto-merge (squash) June 13, 2026 00:49
@jdx jdx merged commit b7866f9 into main Jun 13, 2026
35 checks passed
@jdx jdx deleted the claude/admiring-benz-3c27c0 branch June 13, 2026 00:50
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