a73x

db06838a

build: make release-mac publishes the Mac tarball from MAC_BUILDER

a73x   2026-09-04 12:50

Commit message
build: make release-mac publishes the Mac tarball from MAC_BUILDER

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

Makefile
Old New
@@ -31,7 +31,7 @@ SHA256 ?= shasum -a 256
31 endif 31 endif
32 MUX_TARGET ?= x86_64-linux-musl 32 MUX_TARGET ?= x86_64-linux-musl
33 33
34 .PHONY: build check ci test e2e soak bench agent throughput vm coverage deps clean clean-deps xversion xversion-build install release mac-sdk mac xos provision-mac 34 .PHONY: build check ci test e2e soak bench agent throughput vm coverage deps clean clean-deps xversion xversion-build install release release-mac mac-sdk mac xos provision-mac
35 35
36 # The QUIC stack (deps/quic) is built on demand by build.zig, so no target 36 # The QUIC stack (deps/quic) is built on demand by build.zig, so no target
37 # here needs to depend on this one. It exists to make the one-time cost 37 # here needs to depend on this one. It exists to make the one-time cost
@@ -137,6 +137,16 @@ release:
137 @echo "remote: ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < $(RELTAR)" 137 @echo "remote: ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < $(RELTAR)"
138 @echo "publish: git collab release publish v$(VERSION) $(RELTAR)" 138 @echo "publish: git collab release publish v$(VERSION) $(RELTAR)"
139 139
140 # The Mac half of a release. `make release` above cuts whichever OS it is run
141 # on, and this is how the OTHER one gets cut from here: MAC_BUILDER names a
142 # Mac holding this repo — the same box and the same variable the macOS gates
143 # take — and tools/release-mac.sh pushes the tag, runs `make release` there
144 # and publishes the tarball from there. No target of this Makefile can
145 # produce a Mac binary on Linux; only a Mac can. The script states the rest
146 # of the contract, including why MAC_BUILDER has no default.
147 release-mac:
148 ./tools/release-mac.sh
149
140 test: mac-sdk 150 test: mac-sdk
141 $(ZIG) build test 151 $(ZIG) build test
142 152
tools/release-mac.sh
Old New
@@ -0,0 +1,96 @@
1 #!/bin/sh
2 # Cut and publish the macOS half of a release, from a Mac builder.
3 #
4 # There is no cross-compiling the Mac binary (README, "macOS"), so a Mac
5 # release is made on a Mac. This script is the whole of that: it checks that
6 # the tag exists here and is HEAD, pushes the tag, and then hands ONE remote
7 # shell script to the builder, which fetches that tag, detaches onto it, runs
8 # `make release` — the same recipe as everywhere, whose RELEASE_TARGET
9 # follows the host and so names the tarball aarch64-macos — and publishes it.
10 #
11 # The builder is MAC_BUILDER, the same box and the same name the macOS gates
12 # take (test/mac.sh, test/xos.sh), and MAC_BUILDER_REPO the path to this repo
13 # under its $HOME. There is no default for MAC_BUILDER: a release published
14 # to whatever machine happened to be in someone's ssh config is worse than a
15 # refusal.
16 #
17 # What this does NOT do: touch main, push anything but the tag, or run
18 # git-collab locally. The publish is the builder's, because the builder is
19 # the box holding the bytes.
20 #
21 # The remote script rides ssh's STDIN, so this ssh cannot take `-n` — that
22 # flag closes stdin and the remote `sh -s` would read EOF, do nothing and
23 # exit 0, which is a release that silently did not happen. box_lib.sh's
24 # `box_ssh` is the same shape for the same reason. The consequence is the
25 # rule that shape carries: nothing inside the remote script may read stdin,
26 # or it eats the lines after it. Nothing here does.
27 set -eu
28
29 die() {
30 echo "release-mac: $*" >&2
31 exit "${RC:-1}"
32 }
33
34 # Before any ssh, so an unset name costs no connection and no tag push.
35 if [ -z "${MAC_BUILDER:-}" ]; then
36 echo "release-mac: MAC_BUILDER is unset, and this recipe has no default for it (a Mac holding this repo with zig and deps/mac-sdk)" >&2
37 exit 2
38 fi
39 REPO=${MAC_BUILDER_REPO:-code/rad/mux}
40
41 # The same single source the Makefile greps, so the two can never disagree
42 # about which version is being cut.
43 V=$(sed -n 's/^[[:space:]]*const version = "\(.*\)";/\1/p' build.zig | head -1)
44 [ -n "$V" ] || die "no version found in build.zig"
45 TAG="v$V"
46 TAR="mux-$TAG-aarch64-macos.tar.gz"
47
48 # A release is cut from a tag, never from a branch tip: the builder checks
49 # out the tag, so anything not tagged is not what ships.
50 git rev-parse -q --verify "refs/tags/$TAG" >/dev/null 2>&1 ||
51 die "tag: $TAG does not exist here — tag the commit first (git tag $TAG)"
52 if [ "$(git rev-parse "refs/tags/$TAG^{commit}")" != "$(git rev-parse HEAD)" ]; then
53 die "tag: $TAG does not point at HEAD — tag this commit first (git tag $TAG)"
54 fi
55
56 # Pushing a tag already on the remote is a no-op, so this is safe to re-run;
57 # a tag that MOVED is refused by git itself, which is the check we want.
58 #
59 # `--no-follow-tags` and an explicit destination refspec, because ONE tag is
60 # the whole of what this script is allowed to push. A developer with
61 # `push.followTags = true` in their ~/.gitconfig — which is a common setting
62 # and was set on the box this was written on — otherwise sends every
63 # annotated tag reachable from what is being pushed, so a bare
64 # `git push origin refs/tags/vN` put five other tags on the remote in the
65 # hand check.
66 git push --no-follow-tags origin "refs/tags/$TAG:refs/tags/$TAG" ||
67 die "push: could not push $TAG to origin"
68
69 # One connection. `$V` and `$REPO` are expanded HERE (unquoted heredoc word)
70 # because the remote shell has neither.
71 ssh -o BatchMode=yes "$MAC_BUILDER" /bin/sh -s <<EOF || die "builder: $MAC_BUILDER refused or failed — see its output above"
72 set -eu
73 # git-collab lives in the product's own prefix, and a bare ssh to a Mac has
74 # a PATH of /usr/bin:/bin:/usr/sbin:/sbin and nothing else.
75 PATH="\$PATH:\$HOME/.local/bin"
76 cd "\$HOME/$REPO"
77 # A tag fetch and not a pull: the builder's checkout may be on any branch,
78 # and this must leave that branch exactly where it was.
79 git fetch -q origin "refs/tags/$TAG:refs/tags/$TAG"
80 git checkout -q --detach "$TAG"
81 make release
82 git-collab release publish "$TAG" "dist/$TAR"
83 EOF
84
85 # The URL the tarball is now at, derived from this checkout's own remote so
86 # a fork prints its own server and not this one's. Four strips, in order:
87 # the scheme, any `user@`, the scp-style colon that stands in for the path
88 # separator, and the `.git` suffix — what is left is what the release server
89 # serves under.
90 BASE=$(git remote get-url origin |
91 sed -e 's,^[A-Za-z][A-Za-z0-9+.-]*://,,' \
92 -e 's,^[^/@]*@,,' \
93 -e 's,^\([^/:]*\):,\1/,' \
94 -e 's,\.git$,,' \
95 -e 's,/*$,,')
96 echo "published: https://$BASE/releases/$TAG/$TAR"