Skip to content

ci(compile-smoke): darwin leg + msys cross compile with teeth - #137

Merged
ronaldtse merged 1 commit into
mainfrom
ci/compile-smoke-legs
Oct 8, 2026
Merged

ronaldtse merged 1 commit into
mainfrom
ci/compile-smoke-legs

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Root cause

#122 — no darwin compile smoke. The release-src compile gate (tools/smoke_matrix → _release-line.yml) planned legs only over the shipped scenarios (linux-gnu / linux-musl / msys). Tfs::ReleaseDiff attributed a _darwin patch to the empty set — "no shipped scenario" — so the patch set's darwin-specific hunks (config_status_mainlibs_darwin.patch, configure_extstatic_bundle_loader_darwin.patch, the darwin paths of every base patch) compiled in no gated leg, ever. A darwin-only compile break would ship in the src release and surface downstream in the runtime factory.

#109 — the msys smoke never compiled _WIN32 code, and a 3.4 patch drifted from the c_api ABI. tools/compile_smoke --platform msys ran configure --host=x86_64-w64-mingw32 without pinning CC. Ruby's configure then used the host cc: the tree configured for the host, _WIN32 stayed undefined, and the windows half of every patch — exactly the code the msys scenario exists to compile — was never built, while the leg stayed green. Under a real cross gcc (x86_64-w64-mingw32-gcc 16.1.0) the hole surfaces immediately:

tools/compile_smoke: FAIL 3.4.9 (msys): compile.o did not compile
== make compile.o ==
compiling compile.c
In file included from compile.c:14768:
prism_compile.c: In function 'tfs_string_file_init':
prism_compile.c:44:29: error: passing argument 2 of 'tebako_fs_fstat' from incompatible pointer type [-Wincompatible-pointer-types]
   44 |     if (tebako_fs_fstat(fd, &sb) == -1) {
      |                             ^~~
      |                             struct stat *
ci/include/tebako/fs/c_api.h:80:49: note: expected 'struct tebako_stat *' but argument is of type 'struct stat *'

The drift is in the 3.4 prism_compile_memfs patches, not the ci/include stub. The stub is byte-identical with the c_api authority (the product repo's include/tebako/fs/c_api.h, the header the runtime factory's link unit vendors) on the whole declared surface — the pinned struct tebako_stat block and both declarations:

$ diff <(sed -n '/#if defined(_WIN32)/,/^#endif/p' <authority>) <(same <stub>)
STAT BLOCK IDENTICAL
$ grep -h 'tebako_fs_stat\|tebako_fs_fstat' <authority> <stub> | grep ^int | sort -u
int tebako_fs_fstat(int fd, struct tebako_stat* st);
int tebako_fs_stat(const char* path, struct tebako_stat* st);

The 3.4 patches declared the CRT struct stat sb (32-bit st_size on mingw) and passed it to tebako_fs_fstat, which fills the pinned 56-byte tebako_stat — invisible while the msys smoke compiled no _WIN32 code, an error under any recent cross gcc (and a stack overflow waiting to happen on the msys line: the callee writes 56 bytes into a smaller struct). The 4.0 line already carries the correct shape (struct tebako_stat sb;); 3.4 lagged.

The fix

  • patches/3.4/prism_compile_memfs{,_6}.patch — the call sites now use struct tebako_stat sb;, mirroring the 4.0 line's patch verbatim (comment included). Hunk offsets adjusted; tools/lint verifies every 3.4 version still applies cleanly.
  • tools/compile_smoke — CC is pinned to x86_64-w64-mingw32-gcc for the msys scenario, and a pinned cross compiler that is not on PATH is a named error with an install hint (never a silent host-tree smoke).
  • Smoke universe — Tfs::Versions::SMOKE_ONLY_SCENARIOS = %w[darwin]: no darwin tarball ships (the darwin tree is the base selection plus the _darwin features), but the smoke universe is shipped scenarios + darwin. _darwin patches now attribute to [["darwin", nil]] (was []); base patches attribute darwin too. The build universe is unchanged — the darwin attribution matches no build row, so a darwin-only change still re-rolls no asset (spec-pinned).
  • _release-line.yml — smoke legs with platform: darwin run on macos-latest (arm64); the apt toolchain step is skipped there.
  • lint-patches.yml — the same compile gate now also runs at change time: tools/smoke_matrix diffed previous-release-tag..HEAD, so exactly the lines whose patch sets changed get legs (vacuously green when none did). Apply-clean is not compile-clean; a broken patch now fails on the PR, not at the release.

Leg definitions added

tools/smoke_matrix v0.0.0-next on this branch (diffs v0.2.38..HEAD — this branch touches patches/3.4 base patches, so the 3.4 tip gets every scenario):

{
  "include": [
    { "line": "3.4", "version": "3.4.10", "platform": "linux-gnu" },
    { "line": "3.4", "version": "3.4.10", "platform": "linux-musl" },
    { "line": "3.4", "version": "3.4.10", "platform": "msys" },
    { "line": "3.4", "version": "3.4.10", "platform": "darwin" }
  ]
}

The darwin leg runs as runs-on: ${{ matrix.platform == 'darwin' && 'macos-latest' || 'ubuntu-latest' }} in both _release-line.yml (the release gate) and lint-patches.yml (the change-time gate).

Evidence

Teeth proof (local, macOS arm64, x86_64-w64-mingw32-gcc 16.1.0): the msys leg now compiles _WIN32 code for real. A deliberate #error planted in the #elif defined(_WIN32) branch of patches/3.4/process_c_tebako_spawn_msys.patch (applied cleanly — tools/lint 3.4.9 OK) FAILS the leg:

tools/compile_smoke: FAIL 3.4.9 (msys): process.o did not compile
== make process.o ==
compiling process.c
process.c:2814:2: error: #error deliberate compile-smoke teeth check (reverted before merge)
make: *** [process.o] Error 1
exit=1

The break reverted, the same leg greens again (exit 0):

OK 3.4.9 (msys, pass2): 11 patched translation units compile (ext/digest/digest.o, win32/winmain.o, dir.o, dln.o, file.o, io.o, main.o, process.o, compile.o, ruby.o, win32/file.o)

(An earlier attempt planted the break in the base process_c_tebako_spawn.patch and the leg stayed green — correctly: the base patch is shadowed by its _msys sibling in the msys selection and never applies there. The break belongs to the msys variant, as above.)

Fixed-state compiles (local):

=== msys real cross compile, 3.4.9 (prism_compile_memfs_6 variant) — the #109 repro version ===
OK 3.4.9 (msys, pass2): 11 patched translation units compile (ext/digest/digest.o, win32/winmain.o, dir.o, dln.o, file.o, io.o, main.o, process.o, compile.o, ruby.o, win32/file.o)
=== msys real cross compile, 3.4.5 (prism_compile_memfs whole-line variant) ===
OK 3.4.5 (msys, pass2): 11 patched translation units compile (ext/digest/digest.o, win32/winmain.o, dir.o, dln.o, file.o, io.o, main.o, process.o, compile.o, ruby.o, win32/file.o)
=== darwin native, 3.4.10 (line tip; the new leg's runner family) ===
OK 3.4.10 (darwin, pass2): 8 patched translation units compile (dir.o, dln.o, file.o, io.o, main.o, process.o, compile.o, ruby.o)

compile.o is the prism path that errored in the repro above — green now.

Before state (this branch's parent): the repro transcript at the top of this body (real cross gcc, unfixed patches — the #109 error, exit 1). With no CC pinned at all, configure on a mingw-equipped host fails even earlier (cross objdump against host objects) — the old leg could only ever green by compiling a host tree.

Regression gates: bundle exec rspec — 121 examples, 0 failures (specs updated for the new darwin attribution and smoke universe, including a spec that the build plan still re-rolls nothing on a darwin-only change). tools/validate_manifests, tools/validate_msys_pairs green. tools/lint green for 3.4.5 / 3.4.9 / 3.4.10 (both prism patch variants apply cleanly). actionlint clean on both changed workflows. CI legs on this PR below.

Closes #122
Closes #109

ruby#122: the release-src smoke gate covered linux-gnu/linux-musl/msys
only, so the darwin-specific hunks compiled in no gated leg. The smoke
universe (Tfs::Versions::SMOKE_ONLY_SCENARIOS) now adds darwin: a
darwin tree exists for every version (the base selection plus the
_darwin features) even though no darwin tarball ships. _darwin patch
attribution moves from the empty set to the smoke-only darwin
scenario, and base patches attribute it too; the build universe is
unchanged (no darwin build row exists to match). The release smoke
job runs darwin legs on macos-latest, and lint-patches gains the same
compile gate at change time (tools/smoke_matrix diffed
previous-release..HEAD), so an uncompilable patch fails on the PR
instead of at the release.

ruby#109: the msys leg ran configure without CC, so a POSIX host
configured a HOST tree — _WIN32 undefined, the windows half of every
patch never compiled, and the leg greened regardless. CC is now
pinned to x86_64-w64-mingw32-gcc, a missing cross compiler is a
named error. Under the real cross compile the 3.4 prism_compile_memfs
patches no longer type-check: they passed the CRT struct stat (32-bit
st_size on mingw) to tebako_fs_fstat, which fills the pinned 56-byte
tebako_stat — fix both call sites to the struct tebako_stat shape the
4.0 line already carries. The ci/include stub itself already matches
the c_api authority byte-for-byte on the declared surface (verified);
it is not the drift.
Comment on lines +58 to +83
name: plan the compile-smoke legs
runs-on: ubuntu-latest
outputs:
smoke: ${{ steps.emit.outputs.smoke }}
smoke_count: ${{ steps.emit.outputs.smoke_count }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: fetch tags (previous-release diff base)
run: git fetch --tags --force
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: "3.2"
- id: emit
run: |
smoke=$(tools/smoke_matrix "change-$GITHUB_SHA")
{
echo "smoke=$smoke"
# The count drives the job-level if: on the smoke job (an
# unguarded empty matrix poisons the run conclusion).
echo "smoke_count=$(jq '.include | length' <<< "$smoke")"
} >> "$GITHUB_OUTPUT"
echo "plan: change=$GITHUB_SHA smoke legs=$smoke"

smoke:
Comment on lines +84 to +105
name: smoke (${{ matrix.version }} / ${{ matrix.platform }})
needs: smoke-plan
if: needs.smoke-plan.outputs.smoke_count != '0'
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.smoke-plan.outputs.smoke) }}
runs-on: ${{ matrix.platform == 'darwin' && 'macos-latest' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: "3.2"
- name: scenario toolchain
if: ${{ matrix.platform != 'darwin' }}
run: |
sudo apt-get update -qq
case "${{ matrix.platform }}" in
linux-musl) sudo apt-get install -y -qq musl-tools ;;
msys) sudo apt-get install -y -qq gcc-mingw-w64-x86-64 ;;
esac
- name: configure + compile patched translation units
run: tools/compile_smoke "${{ matrix.version }}" "$PWD/build/compile-smoke" --platform "${{ matrix.platform }}"
@ronaldtse
ronaldtse merged commit 389c04e into main Oct 8, 2026
48 checks passed
@ronaldtse
ronaldtse deleted the ci/compile-smoke-legs branch October 8, 2026 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants