c8ff232e
build: a Mac builds natively through a shadow SDK, and the dep script follows the host
a73x 2026-09-03 16:52
Commit message
.gitignore
| Old | New | ||
|---|---|---|---|
| @@ -18,3 +18,6 @@ dist/ | |||
| 18 | deps/zig | 18 | deps/zig |
| 19 | deps/quic/out | 19 | deps/quic/out |
| 20 | deps/quic/work | 20 | deps/quic/work |
| 21 | |||
| 22 | # Shadow SDK from deps/mac-sdk.sh, Darwin only. | ||
| 23 | deps/mac-sdk | ||
Makefile
| Old | New | ||
|---|---|---|---|
| @@ -2,7 +2,18 @@ | |||
| 2 | # default zig is 0.17-dev. Override with ZIG=... if yours lives elsewhere. | 2 | # default zig is 0.17-dev. Override with ZIG=... if yours lives elsewhere. |
| 3 | ZIG ?= $(CURDIR)/deps/zig/zig | 3 | ZIG ?= $(CURDIR)/deps/zig/zig |
| 4 | 4 | ||
| 5 | .PHONY: build check ci test e2e soak bench agent throughput vm coverage deps clean clean-deps xversion xversion-build install release | 5 | # On a Mac the pinned zig cannot link against Xcode 26.4+'s SDK (see |
| 6 | # deps/mac-sdk.sh). The shim dir goes FIRST on PATH for every recipe here, | ||
| 7 | # and `mac-sdk` builds it; both are no-ops on Linux and on a Mac whose SDK | ||
| 8 | # is linkable. MUX_TARGET follows the host: a Mac installs a Mac binary. | ||
| 9 | UNAME_S := $(shell uname -s) | ||
| 10 | ifeq ($(UNAME_S),Darwin) | ||
| 11 | export PATH := $(CURDIR)/deps/mac-sdk/bin:/opt/homebrew/bin:$(PATH) | ||
| 12 | MUX_TARGET ?= aarch64-macos | ||
| 13 | endif | ||
| 14 | MUX_TARGET ?= x86_64-linux-musl | ||
| 15 | |||
| 16 | .PHONY: build check ci test e2e soak bench agent throughput vm coverage deps clean clean-deps xversion xversion-build install release mac-sdk mac | ||
| 6 | 17 | ||
| 7 | # The QUIC stack (deps/quic) is built on demand by build.zig, so no target | 18 | # The QUIC stack (deps/quic) is built on demand by build.zig, so no target |
| 8 | # here needs to depend on this one. It exists to make the one-time cost | 19 | # here needs to depend on this one. It exists to make the one-time cost |
| @@ -13,7 +24,7 @@ deps: | |||
| 13 | ./deps/quic/build-deps.sh native | 24 | ./deps/quic/build-deps.sh native |
| 14 | ./deps/quic/build-deps.sh musl | 25 | ./deps/quic/build-deps.sh musl |
| 15 | 26 | ||
| 16 | build: | 27 | build: mac-sdk |
| 17 | $(ZIG) build | 28 | $(ZIG) build |
| 18 | 29 | ||
| 19 | # Only the ONE user-facing binary: everything else in zig-out/bin | 30 | # Only the ONE user-facing binary: everything else in zig-out/bin |
| @@ -43,8 +54,8 @@ INSTDIR ?= dist/install | |||
| 43 | # | 54 | # |
| 44 | # The target the installed and released binaries are built for. Static | 55 | # The target the installed and released binaries are built for. Static |
| 45 | # musl on Linux, for the reason above; another OS names its triple here. | 56 | # musl on Linux, for the reason above; another OS names its triple here. |
| 46 | MUX_TARGET ?= x86_64-linux-musl | 57 | # The `?=` at the top of this file is what sets it, so a Mac gets its own. |
| 47 | install: | 58 | install: mac-sdk |
| 48 | $(ZIG) build -Dtarget=$(MUX_TARGET) -Doptimize=ReleaseSafe -p $(INSTDIR) | 59 | $(ZIG) build -Dtarget=$(MUX_TARGET) -Doptimize=ReleaseSafe -p $(INSTDIR) |
| 49 | install -d $(BINDIR) | 60 | install -d $(BINDIR) |
| 50 | install -m755 $(INSTDIR)/bin/mux $(BINDIR)/ | 61 | install -m755 $(INSTDIR)/bin/mux $(BINDIR)/ |
| @@ -86,7 +97,7 @@ release: | |||
| 86 | @echo "remote: ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < $(RELTAR)" | 97 | @echo "remote: ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < $(RELTAR)" |
| 87 | @echo "publish: git collab release publish v$(VERSION) $(RELTAR)" | 98 | @echo "publish: git collab release publish v$(VERSION) $(RELTAR)" |
| 88 | 99 | ||
| 89 | test: | 100 | test: mac-sdk |
| 90 | $(ZIG) build test | 101 | $(ZIG) build test |
| 91 | 102 | ||
| 92 | e2e: | 103 | e2e: |
| @@ -135,16 +146,23 @@ vm: | |||
| 135 | ./test/vm.sh $(VMDIR)/bin/mux $(VMDIR)/bin/ptyclient | 146 | ./test/vm.sh $(VMDIR)/bin/mux $(VMDIR)/bin/ptyclient |
| 136 | 147 | ||
| 137 | THRUDIR ?= dist/throughput | 148 | THRUDIR ?= dist/throughput |
| 138 | throughput: | 149 | throughput: mac-sdk |
| 139 | $(ZIG) build -Doptimize=ReleaseSafe -p $(THRUDIR) | 150 | $(ZIG) build -Doptimize=ReleaseSafe -p $(THRUDIR) |
| 140 | ./test/throughput.sh $(THRUDIR)/bin/mux $(THRUDIR)/bin/ptyclient | 151 | ./test/throughput.sh $(THRUDIR)/bin/mux $(THRUDIR)/bin/ptyclient |
| 141 | 152 | ||
| 153 | mac-sdk: | ||
| 154 | sh ./deps/mac-sdk.sh | ||
| 155 | |||
| 156 | # The `make vm` of the macOS port: user journeys on the Mac, over ssh. | ||
| 157 | mac: build | ||
| 158 | sh ./test/mac.sh | ||
| 159 | |||
| 142 | # `zig build check` grades the tree; bans.sh grades the grader. The folder | 160 | # `zig build check` grades the tree; bans.sh grades the grader. The folder |
| 143 | # rules are the one gate whose failure mode is silence — a needle list | 161 | # rules are the one gate whose failure mode is silence — a needle list |
| 144 | # edited down to nothing, or a folder dropped from a rule's list, leaves a | 162 | # edited down to nothing, or a folder dropped from a rule's list, leaves a |
| 145 | # green tree asserting rules that no longer bite — so one planted needle per | 163 | # green tree asserting rules that no longer bite — so one planted needle per |
| 146 | # rule runs here, after the gate it is checking. Two seconds. | 164 | # rule runs here, after the gate it is checking. Two seconds. |
| 147 | check: | 165 | check: mac-sdk |
| 148 | $(ZIG) build check | 166 | $(ZIG) build check |
| 149 | sh test/bans.sh $(ZIG) | 167 | sh test/bans.sh $(ZIG) |
| 150 | 168 | ||
deps/mac-sdk.sh
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,55 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # deps/mac-sdk.sh — a shadow macOS SDK for zig 0.15.2, built on a Darwin host. | ||
| 3 | # | ||
| 4 | # Xcode 26.4 and later ship a libSystem.B.tbd whose `targets:` line lists | ||
| 5 | # arm64e-macos and no arm64-macos. zig 0.15.2's Mach-O linker matches the | ||
| 6 | # bare arm64 slice only, so against that SDK every libSystem symbol is | ||
| 7 | # undefined — for the build runner too, which is why `zig build` cannot | ||
| 8 | # even start (ziglang/zig#31658 on Codeberg; fixed in 0.16, which ghostty's | ||
| 9 | # pin cannot use). zig's own lib/libc/darwin/libSystem.tbd is what a | ||
| 10 | # `-target aarch64-macos` build links against, and it works. This script | ||
| 11 | # builds an SDK that is the real one in every path but usr/lib/libSystem*, | ||
| 12 | # where zig's stub stands in, and an `xcrun` shim that answers | ||
| 13 | # --show-sdk-path with it — zig finds the SDK by running `xcrun` off PATH | ||
| 14 | # (std/zig/system/darwin.zig), and so does ghostty's apple_sdk helper. | ||
| 15 | # | ||
| 16 | # Self-retiring: when the real SDK's stub lists arm64-macos again, no shadow | ||
| 17 | # is built and the shim, if a stale one is on PATH, passes straight through. | ||
| 18 | # Idempotent and cheap: the marker check is the first thing it does. | ||
| 19 | # | ||
| 20 | # ./deps/mac-sdk.sh builds deps/mac-sdk/{sdk,bin/xcrun} | ||
| 21 | set -eu | ||
| 22 | [ "$(uname)" = Darwin ] || exit 0 | ||
| 23 | SELF="$(cd "$(dirname "$0")" && pwd)" | ||
| 24 | OUT="$SELF/mac-sdk" | ||
| 25 | ZIG_STUB="$SELF/zig/lib/libc/darwin/libSystem.tbd" | ||
| 26 | REAL="$(/usr/bin/xcrun --sdk macosx --show-sdk-path)" | ||
| 27 | if grep -m1 '^targets:' "$REAL/usr/lib/libSystem.B.tbd" | grep -q 'arm64-macos'; then | ||
| 28 | rm -rf "$OUT" # the SDK is linkable again; leave nothing to shadow it | ||
| 29 | exit 0 | ||
| 30 | fi | ||
| 31 | [ -f "$ZIG_STUB" ] || { echo "deps/mac-sdk: no zig stub at $ZIG_STUB (is deps/zig in place?)" >&2; exit 1; } | ||
| 32 | # Marker: the shim exists AND points at this SDK. A new Xcode moves REAL. | ||
| 33 | if [ -x "$OUT/bin/xcrun" ] && [ "$(readlink "$OUT/sdk/usr/include")" = "$REAL/usr/include" ]; then | ||
| 34 | exit 0 | ||
| 35 | fi | ||
| 36 | rm -rf "$OUT" | ||
| 37 | mkdir -p "$OUT/sdk/usr/lib" "$OUT/bin" | ||
| 38 | for e in "$REAL"/*; do b=$(basename "$e"); [ "$b" = usr ] || ln -s "$e" "$OUT/sdk/$b"; done | ||
| 39 | for e in "$REAL"/usr/*; do b=$(basename "$e"); [ "$b" = lib ] || ln -s "$e" "$OUT/sdk/usr/$b"; done | ||
| 40 | for e in "$REAL"/usr/lib/*; do | ||
| 41 | b=$(basename "$e") | ||
| 42 | case "$b" in libSystem.tbd | libSystem.B.tbd) ;; *) ln -s "$e" "$OUT/sdk/usr/lib/$b" ;; esac | ||
| 43 | done | ||
| 44 | cp "$ZIG_STUB" "$OUT/sdk/usr/lib/libSystem.tbd" | ||
| 45 | cp "$ZIG_STUB" "$OUT/sdk/usr/lib/libSystem.B.tbd" | ||
| 46 | cat > "$OUT/bin/xcrun" <<EOF | ||
| 47 | #!/bin/sh | ||
| 48 | # Shim from deps/mac-sdk.sh: only --show-sdk-path is answered here. | ||
| 49 | case "\$*" in | ||
| 50 | *--show-sdk-path*) [ -d "$OUT/sdk" ] && { echo "$OUT/sdk"; exit 0; } ;; | ||
| 51 | esac | ||
| 52 | exec /usr/bin/xcrun "\$@" | ||
| 53 | EOF | ||
| 54 | chmod +x "$OUT/bin/xcrun" | ||
| 55 | echo "deps/mac-sdk: shadow SDK over $REAL (zig 0.15.2 cannot link the Xcode 26.4+ stub)" >&2 | ||
deps/quic/build-deps.sh
| Old | New | ||
|---|---|---|---|
| @@ -29,7 +29,11 @@ case "$T" in | |||
| 29 | esac | 29 | esac |
| 30 | 30 | ||
| 31 | SELF="$(cd "$(dirname "$0")" && pwd)" | 31 | SELF="$(cd "$(dirname "$0")" && pwd)" |
| 32 | ZIG="${ZIG:-$HOME/Downloads/zig-x86_64-linux-0.15.2/zig}" | 32 | # The repo's pinned toolchain first: every box that builds mux has it at |
| 33 | # deps/zig, and a Mac has no other 0.15.2 (brew ships 0.16). The Downloads | ||
| 34 | # path is the original dev box's spelling, kept so an old rig still runs. | ||
| 35 | ZIG="${ZIG:-$SELF/../zig/zig}" | ||
| 36 | [ -x "$ZIG" ] || ZIG="$HOME/Downloads/zig-x86_64-linux-0.15.2/zig" | ||
| 33 | OUT="$SELF/out/$T" | 37 | OUT="$SELF/out/$T" |
| 34 | W="$SELF/work" | 38 | W="$SELF/work" |
| 35 | 39 | ||
| @@ -60,9 +64,15 @@ mkdir -p "$W/src" "$W/bin" | |||
| 60 | # v3 (AVX2) costs nothing measurable at terminal bandwidth, and the musl | 64 | # v3 (AVX2) costs nothing measurable at terminal bandwidth, and the musl |
| 61 | # release target below is baseline anyway, so nothing shipped changes. | 65 | # release target below is baseline anyway, so nothing shipped changes. |
| 62 | # Zig-style CPU name (underscores): zig cc rejects clang's x86-64-v3. | 66 | # Zig-style CPU name (underscores): zig cc rejects clang's x86-64-v3. |
| 67 | # The flag is x86's: on an arm64 host (an Apple Silicon Mac is the one | ||
| 68 | # that builds) zig cc rejects it, so a native build there is plain zig cc. | ||
| 69 | case "$(uname -m)" in | ||
| 70 | x86_64) NATIVE_FLAGS="-march=x86_64_v3" ;; | ||
| 71 | *) NATIVE_FLAGS="" ;; | ||
| 72 | esac | ||
| 63 | cat > "$W/bin/zigcc-native" <<EOF | 73 | cat > "$W/bin/zigcc-native" <<EOF |
| 64 | #!/bin/sh | 74 | #!/bin/sh |
| 65 | exec $ZIG cc -march=x86_64_v3 "\$@" | 75 | exec $ZIG cc $NATIVE_FLAGS "\$@" |
| 66 | EOF | 76 | EOF |
| 67 | cat > "$W/bin/zigcc-musl" <<EOF | 77 | cat > "$W/bin/zigcc-musl" <<EOF |
| 68 | #!/bin/sh | 78 | #!/bin/sh |
| @@ -112,6 +122,16 @@ case "$T" in | |||
| 112 | # "Findings the design rests on". | 122 | # "Findings the design rests on". |
| 113 | XTRA="-DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=arm64 -DCMAKE_FIND_ROOT_PATH=$OUT -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY" | 123 | XTRA="-DCMAKE_SYSTEM_NAME=Darwin -DCMAKE_SYSTEM_PROCESSOR=arm64 -DCMAKE_FIND_ROOT_PATH=$OUT -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY" |
| 114 | WOLF_XTRA="-DWOLFSSL_SYS_CA_CERTS=no" ;; | 124 | WOLF_XTRA="-DWOLFSSL_SYS_CA_CERTS=no" ;; |
| 125 | native) | ||
| 126 | # A native build ON a Mac wants the same two fences for the same | ||
| 127 | # two reasons, minus the cross-compile words: brew carries a | ||
| 128 | # libngtcp2 that ngtcp2's own cmake would otherwise find, and the | ||
| 129 | # CA path still wants Security.framework linked into a PSK-only | ||
| 130 | # binary that never verifies a certificate. | ||
| 131 | if [ "$(uname)" = Darwin ]; then | ||
| 132 | XTRA="-DCMAKE_FIND_ROOT_PATH=$OUT -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY" | ||
| 133 | WOLF_XTRA="-DWOLFSSL_SYS_CA_CERTS=no" | ||
| 134 | fi ;; | ||
| 115 | esac | 135 | esac |
| 116 | 136 | ||
| 117 | # wolfSSL. Three flags are load-bearing, all found by link failure in the | 137 | # wolfSSL. Three flags are load-bearing, all found by link failure in the |