Skip to content

Add a native build mode so macOS gets tested at all - #10

Merged
jeremy merged 1 commit into
mainfrom
native-build-mode
Aug 3, 2026
Merged

Add a native build mode so macOS gets tested at all#10
jeremy merged 1 commit into
mainfrom
native-build-mode

Conversation

@jeremy

@jeremy jeremy commented Aug 3, 2026

Copy link
Copy Markdown
Member

Every build these definitions have been through was Linux in Docker, because test/build
can only test via Docker. macOS is the platform this repo exists for and it had never been
built with the current flags.

So test/build native <version>: builds on the host into a throwaway prefix and runs the
same post-flight checks the containers run.

That matters most for the arithmetic assertion. The 2**64 == 0 bug was GCC exploiting
signed-overflow UB at -O3; whether Apple clang does the same was untested, and untestable.
Today's Macs all compute 2**64 correctly because none of them is at -O3 via CFLAGS —
the risk arrives with the next rebuild, not before it, which is exactly when you want to
have already checked.

Opt-in by name. PLATFORMS is untouched, so test/build all stays the clean-room
Docker matrix. A native build compiles against whatever the host happens to have and proves
nothing about a fresh machine.

Shape of the change

test/build is the only file touched.

  • The verify script takes its prefix from $RUBY_PREFIX instead of hardcoding /opt/ruby,
    so both paths run identical assertions — the point of the mode is that macOS gets these
    and not a weaker set.
  • test_ruby splits into docker_build and native_build with the result reporting shared.
    The docker invocation itself is unchanged.
  • build_image returns early for native; the RESULTS/trap, JOBS throttle,
    single-printf result lines and exit-code-is-failure-count convention are reused as-is.
    Still Bash 3.2 clean.

Two details worth their comments. ruby-build is resolved once up front and a missing one
is a hard error, never a skip — a native run that can't build is a failed run. It usually
isn't on PATH, since mise invokes its own copy directly, so mise's cache is consulted too
via mise cache rather than a hardcoded ~/.cache/mise: on macOS that cache lives under
~/Library/Caches, and macOS is the whole point here. Each build also gets its own
TMPDIR, because ruby-build derives both its log path and its build directory from it and
these run concurrently — otherwise a failure tail is some neighbour's log.

Prefixes are removed as soon as they're verified rather than kept. Six Rubies is several GB
and the machines that need this are laptops.

Verification

Platform Result
macOS 27, M1 Max, Apple clang 21 (Xcode-beta) — JOBS=2 test/build native all 6/6
Arch — test/build native all 6/6
Arch + Ubuntu Noble — test/build all (Docker, must be undisturbed) 12/12

Apple clang does not reproduce the GCC signed-overflow miscompile. And not a vacuous
pass: a 1.8.7 built on that Mac reports

CFLAGS:  -O3 -fno-strict-overflow -Wno-compound-token-split-by-macro … -Wno-incompatible-function-pointer-types …
CC:      clang
2**64:   18446744073709551616

so the flags really did reach the compile line.

One wrinkle worth knowing: 1.8.7's freshen_automake_config shells out to
brew install automake on macOS, so that build inherits whatever state brew is in. It
failed once mid-upgrade with a brew rename error, unrelated to anything here, and passed on
a retry.

bin/ci green with signoff; shellcheck clean at --severity=warning.

Every build these definitions have been through was Linux in Docker, because
test/build can only test via Docker. macOS is the platform this repo exists for
and it has never been built with the current flags — so `test/build native`,
which builds on the host into a throwaway prefix and runs the same post-flight
checks the containers run.

That matters most for the arithmetic assertion. The 2**64 == 0 bug was GCC
exploiting signed-overflow UB at -O3; whether Apple clang does the same was
untested, and untestable. Today's Macs all compute 2**64 correctly because none
of them is at -O3 via CFLAGS — the risk arrives with the next rebuild, not
before it, which is exactly when you want to have already checked.

Opt-in by name. PLATFORMS is untouched, so `test/build all` stays the clean-room
Docker matrix; a native build compiles against whatever the host happens to have
and proves nothing about a fresh machine.

The verify script now takes its prefix from $RUBY_PREFIX rather than hardcoding
/opt/ruby, so both paths run the same assertions — the point of the mode is that
macOS gets these and not a weaker set. test_ruby splits into docker_build and
native_build with the result reporting shared; the docker invocation itself is
unchanged.

Two details worth their comments. ruby-build is resolved once up front and a
missing one is a hard error, never a skip — a native run that can't build is a
failed run. It usually isn't on PATH, since mise invokes its own copy directly,
so mise's cache is consulted too, via `mise cache` rather than a hardcoded
~/.cache/mise: on macOS that cache lives under ~/Library/Caches, and macOS is
the whole point here. Each build also gets its own TMPDIR, because ruby-build
derives both its log path and its build directory from it and these run
concurrently — otherwise a failure tail is some neighbour's log.

Prefixes are removed as soon as they're verified rather than kept. Six Rubies is
several GB and the machines that need this are laptops.

Verified. macOS (M1 Max, Apple clang 21 under Xcode-beta): 6/6, arithmetic
assertions included, so clang does not reproduce the GCC signed-overflow
miscompile. Not a vacuous pass either — a 1.8.7 built there reports CFLAGS
"-O3 -fno-strict-overflow …", CC clang, and 2**64 correct, so the flags really
did reach the compile line. Arch native 6/6, and the Docker matrix still 12/12.

One wrinkle worth knowing: 1.8.7's freshen_automake_config shells out to
`brew install automake` on macOS, so that build inherits whatever state brew is
in. It failed once mid-upgrade with a brew rename error, unrelated to anything
here, and passed on a retry.
Copilot AI review requested due to automatic review settings August 3, 2026 09:16

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@jeremy
jeremy merged commit 5276d4f into main Aug 3, 2026
1 of 3 checks passed
@jeremy
jeremy deleted the native-build-mode branch August 3, 2026 16:30
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.

2 participants