db06838a
build: make release-mac publishes the Mac tarball from MAC_BUILDER
a73x 2026-09-04 12:50
Commit message
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" | ||