Skip to content

Self-diagnosing toolchain setup for macOS 26.4+#468

Open
jeremybower wants to merge 4 commits into
supabitapp:mainfrom
jeremybower:feature/self-diagnosing-toolchain-setup
Open

Self-diagnosing toolchain setup for macOS 26.4+#468
jeremybower wants to merge 4 commits into
supabitapp:mainfrom
jeremybower:feature/self-diagnosing-toolchain-setup

Conversation

@jeremybower

Copy link
Copy Markdown

Why

A first-time / returning contributor on macOS 26.4+ (Tahoe) hits a chain of build failures, each surfacing only after the previous is fixed — and the worst one (the GhosttyKit Zig link failure) shows up as a ~200-line undefined symbol: _malloc, _free, _sigaction, … dump in build_zcu.o with no hint of the cause. The root cause: the pinned Zig (0.15.2, required exactly by ghostty) can't link the macOS 26.4+ SDK, which dropped the plain arm64-macos slice from libSystem.tbd (ziglang/zig#31658, fixed only in Zig 0.16+). The fix is to build against Xcode 26.3 (ships the macOS 26.2 SDK).

This PR makes the prerequisites self-diagnosing and documents the setup, so contributors get a clear, actionable error the moment something is missing, and can keep multiple Xcodes side-by-side without a global sudo xcode-select -s.

What (4 self-contained, individually-buildable commits)

  1. Pin dev tooling via miseswift-format (spm:swiftlang/[email protected], reproducible across contributors' Xcodes instead of the drifting toolchain swift format), [email protected] (was piped but unpinned → pipefail failures), [email protected]. make format uses the pinned formatter; xcbeautify pipe tolerates absence.
  2. Auto-pin a Zig-linkable Xcode via DEVELOPER_DIR — new scripts/select-developer-dir.sh scans candidate Xcodes and picks the first whose macOS SDK still has arm64-macos (probing with xcrun --sdk macosx, the form Zig uses). The ghostty/zmx build scripts and the Makefile build targets export it per-build — no global switch; DEVELOPER_DIR=… overrides. Adds the "Building on macOS 26.4+" docs.
  3. make doctor — new scripts/doctor.sh checks mise-on-PATH, submodules, a Zig-linkable Xcode, Xcode license/first-launch, the Metal Toolchain, and the pinned tools, printing the exact fix for each failure. Build targets run it as a quiet order-only preflight (SUPACODE_SKIP_PREFLIGHT=1 to skip).
  4. CI alignment — the setup-macos action reuses the shared selector instead of its inline scan.

Why no patches/ entry

The link bug is in Zig's own self-hosted linker (build_zcu.o), not ghostty source, so the patches/*.patch mechanism (which only patches the ghostty submodule) can't fix it — and ghostty pins Zig to exactly 0.15.2. The older-SDK + auto-DEVELOPER_DIR approach is the long-term fix until ghostty supports Zig 0.16+.

Verification

  • make build-app — full ghostty rebuild under the auto-pinned DEVELOPER_DIR + app → Build Succeeded
  • make formatzero .swift churn (pinned formatter agrees with the tree)
  • make doctor → all checks green; --quiet silent on success; failure path renders correct fixes
  • make lint → clean

🤖 Generated with Claude Code

jeremybower and others added 4 commits June 23, 2026 07:06
make format ran the Xcode toolchain's built-in `swift format`, which is
unpinned: a newer Xcode's formatter rewrites the whole tree (Swift
call-site trailing commas), so contributors on different Xcodes disagree
and produce spurious ~195-file churn. xcbeautify was piped via `mise
exec` but absent from mise.toml, so with pipefail `make build-app`/`test`
failed even when the build itself succeeded. swiftlint floated on
`latest`.

Pin all three in mise.toml (swift-format via the spm: backend, matching
how zig/tuist/swiftlint are already managed; tag 602.0.0 ↔ Swift 6.2),
point `make format` at the pinned `swift-format`, and make the xcbeautify
pipe tolerant of its absence as defense in depth.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
On macOS 26.4+ the GhosttyKit build dies with a wall of `undefined symbol:
_malloc, _free, _sigaction, ...` in build_zcu.o. The pinned Zig (0.15.2,
required exactly by ghostty) can't link the macOS 26.4+ SDK: that SDK's
libSystem.tbd dropped the plain arm64-macos target (keeping only
arm64e-macos), and Zig 0.15.2's linker won't match — ziglang/zig#31658,
fixed only in Zig 0.16+. The fix is to build against an Xcode <= 26.3
(ships the macOS 26.2 SDK, whose .tbd still has arm64-macos), without a
global `sudo xcode-select -s` that would disrupt other projects needing a
newer Xcode.

Add scripts/select-developer-dir.sh: it scans candidate Xcodes (current
selection, then versioned 26.3..26.0, then Xcode.app) and prints the first
whose macOS SDK is Zig-linkable — probing with the `xcrun --sdk macosx`
form Zig uses, not bare `xcrun --show-sdk-path` (which can resolve to the
CommandLineTools SDK and mislead). build-ghostty.sh and build-zmx.sh
self-resolve DEVELOPER_DIR (honoring an inherited value from the Makefile
or Xcode's foreignBuild), and the Makefile exports it for build-app /
test / run-app / archive so the app and its ghostty build share one
toolchain. An explicit DEVELOPER_DIR still wins as an override.

Document the macOS 26.4+ setup (Xcode 26.3, license/first-launch, Metal
Toolchain, submodules, the verification quirk, and why no patches/ entry
can fix a bug in Zig's own linker) in AGENTS.md and README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
A first-time / returning contributor on macOS 26.4+ hits a chain of build
failures and the worst (the Zig link failure) shows up as a 200-line
undefined-symbol dump with no hint of the cause. Add scripts/doctor.sh: it
checks every prerequisite in the order failures surface — mise on PATH,
submodules initialized, a Zig-linkable Xcode (reusing
select-developer-dir.sh), Xcode license/first-launch, the Metal Toolchain,
and the pinned mise tools — and prints the exact fix command for each
failure.

`make doctor` runs it verbose; the build targets (build-app, test,
build-ghostty-xcframework, build-zmx) gain a quiet `preflight` as an
order-only prerequisite so a missing prerequisite fails fast with an
actionable message instead of a linker dump. Set SUPACODE_SKIP_PREFLIGHT=1
to skip. Docs now point at `make doctor` as the front door.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The setup-macos action duplicated the "pick an Xcode <= 26.3" scan inline.
Replace it with a call to scripts/select-developer-dir.sh so CI and local
builds (and `make doctor`) share one source of truth for which Xcode can
link the pinned Zig — and probe by libSystem.tbd rather than by version
name. Still applies it via `sudo xcode-select -s`, which is fine on an
ephemeral runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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