Skip to content

Give the Intel Mac host back the recipe that cross-builds it - #171

Merged
frangarcj merged 3 commits into
masterfrom
next-macos-cross-recipe
Aug 26, 2026
Merged

Give the Intel Mac host back the recipe that cross-builds it#171
frangarcj merged 3 commits into
masterfrom
next-macos-cross-recipe

Conversation

@frangarcj

Copy link
Copy Markdown
Contributor

stage3 (x86_64-apple-darwin) is the one red host of nine in vitasdk/autobuilds#36, and it dies at the only line of the job that demands an x86_64 slice:

00:43:14  Vita GCC/binutils contract OK
00:43:52  VitaSDK installed at .../bootstrap-installed
00:43:54  arch: posix_spawnp: .../bootstrap-installed/bin/arm-vita-eabi-gcc:
          Bad CPU type in executable

That job never cross-compiled anything. Line 532 of its own log, 18 minutes earlier:

-- Host:   arm64-apple-darwin
-- Build:  arm64-apple-darwin

and every component under it at checking host system type... aarch64-apple-darwin. It built a native arm64 SDK and published it under the Intel name.

cmake/hosts.json says this host is stage 3, cross-built on the arm64 runner. scripts/ci/build-host.sh has called it native since #161, which made it so in both places at once. db4d1a593 turned the lock back into a cross and left the recipe alone:

-x86_64-apple-darwin)
-	install_dependencies
-	scripts/setup-macos-cross.sh "$PWD/macos-cross"
-	export PATH="$PWD/macos-cross/bin:$PATH"
-	extra_cmake_args+=(-DCMAKE_TOOLCHAIN_FILE="$repo_root/cmake/toolchains/x86_64-apple-darwin.cmake")
-	;;

The first commit puts that back, unchanged.

Why everything green stayed green

Three checks ran over that tree and none of them could see it.

The toolchain contract, because run.sh under arch -x86_64 is a Rosetta process, and a Rosetta process execs an arm64 binary natively. It exercised an arm64 gcc and said OK. Only arch -x86_64 <binary> directly demands the slice, which is what the smoke test does and why it is the line that failed.

ValidateSdk, because it compares the tree against host_native — which was arm64-apple-darwin. Consistent tree, consistent answer. The published name it disagreed with was never part of the comparison.

The host binary format guard, because on Apple it only asked "is this Mach-O?". Both machines answer yes. It is the guard written for exactly this — a file from the producer riding into the consumer's tree in a staged build — and x86_64-apple-darwin, cross-built on the arm64 runner, is the one host pair where it was blind.

The bootstrap that came out was mixed, and the log shows it: pacman --version and vdpm --help ran fine under arch -x86_64 two lines before the failure, because the vdpm bundle is downloaded per published host name and really was x86_64. The client was Intel and the toolchain was not.

The three commits

  1. The recipe, plus tests/ci/test-cross-host-recipe.sh. The lock and the case block are two halves of one decision and nothing tied them together. The test runs the real case block for every host in hosts.json and fails when one that names a build_host gets no toolchain file, or one that names none gets one. With the bug reintroduced it says so by name:

    FAIL: x86_64-apple-darwin is cross-built from arm64-apple-darwin but its
      recipe does not use x86_64-apple-darwin.cmake
      recipe: <none>
    
  2. The name gate. VITASDK_HOST_NAME is what the artifacts are called; the toolchain triplet is what they are, and nothing compared them. Now the build stops as soon as both are known:

    CMake Error at cmake/PublishedHostName.cmake:36 (message):
      this build publishes as x86_64-apple-darwin but its toolchain builds
      arm64-apple-darwin.  Either it is missing the toolchain file that would
      make it cross-compile, or VITASDK_HOST_NAME names the wrong host.
    

    That is 1.5 seconds instead of the 18 minutes the smoke test needed to reach the same conclusion by running a binary. arm64 and aarch64 are accepted as one machine: Apple writes the first, config.sub the second. The FreeBSD publish rule moves into cmake/PublishedHostName.cmake so its test calls it instead of keeping a second copy.

  3. The Mach-O cputype, four bytes behind the magic, in the byte order the magic announces. A universal binary carries one per slice and none of its own, so it stays unclassified. This is what makes ValidateSdk able to see an arm64 binary in an Intel tree at all. Its test drives the check with the real cmake binary against the triplet of the machine it is not, which exercises the Mach-O path on macOS and the ELF one on Linux.

Verified

A reduced probe in the probe repository drives this script's own case block, configures, and builds one host dependency — deps_host exists only on the cross path, so the configure output already tells the two builds apart:

tree result
master -- Host: arm64-apple-darwin, no toolchain file, no zlib_host
this branch, recipe removed by hand configure stops, naming the cause
this branch -- Host: x86_64-apple-darwin, deps_host/lib/libz.a: x86_64

On a clean macos-14 runner against master that reproduces in a 33-second job. Every suite in the checks job passes locally: 7 cmake scripts, 8 CI tests.


AI tools were used in preparing this PR (Claude Opus 5, Anthropic).

hosts.json says x86_64-apple-darwin is stage 3, cross-built on the arm64
runner; build-host.sh has called it native since #161, which made it so in
both places at once. db4d1a5 turned the lock back into a cross and left
the recipe alone, so stage 3 configured with the machine's own compiler:
"-- Host: arm64-apple-darwin", every component at --host=aarch64-apple-
darwin, and an arm64 SDK published under the Intel name.

It stayed green through the SDK, the static validation and the toolchain
contract -- the contract runs run.sh under arch -x86_64, and a Rosetta
process execs an arm64 binary natively, so it never demanded the slice.
What demanded it was the bootstrap smoke test, 18 minutes in: arch -x86_64
bootstrap-installed/bin/arm-vita-eabi-gcc, Bad CPU type in executable.

The recipe restored here is the one #161 deleted, unchanged: the wrappers
from setup-macos-cross.sh in PATH and the toolchain file that pins the
triplet.

The test is what was missing. The lock and the case block are two halves of
one decision and nothing tied them together, so it runs the real case block
for every host in hosts.json and fails when one that names a build_host
gets no toolchain file, or one that names none gets a toolchain file.
VITASDK_HOST_NAME is what the artifacts are called; the toolchain triplet is
what they are. Nothing compared the two, so the build whose toolchain file
had gone missing named one host and produced another, and everything
downstream agreed with the name: the tarball, the package architecture, the
bootstrap, the provenance.

The check runs as soon as both are known, which costs seconds instead of the
18 minutes the bootstrap smoke test needed to reach the same conclusion by
running a binary. arm64 and aarch64 are one machine here: Apple writes the
first, config.sub the second, and both reach this line depending on who was
asked.

The name derivation moves to cmake/PublishedHostName.cmake so its test can
call it instead of keeping a second copy of the FreeBSD rule.
The format guard exists because a staged build assembles one SDK out of two
machines, and a file belonging to the producer can reach the consumer's tree
without anything else noticing. On Apple it could not see that: both
machines emit Mach-O, so arm64 and x86_64 were the same answer -- and the
one host pair staged across two architectures, x86_64-apple-darwin
cross-built on the arm64 runner, was exactly the blind spot.

The cputype sits four bytes behind the magic, in the byte order the magic
announces. A universal binary carries one per slice and none of its own, so
it stays unclassified rather than guessed at.

The test drives the check with the real cmake binary against the triplet of
the machine it is not, which exercises the Mach-O path on macOS and the ELF
one on Linux, and the helper it calls now takes the host to check against
instead of hardcoding Windows.
@frangarcj
frangarcj merged commit 0cefcc6 into master Aug 26, 2026
14 of 19 checks passed
@frangarcj
frangarcj deleted the next-macos-cross-recipe branch August 26, 2026 19:55
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