aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Geissler <peter@haxx.in>2026-08-26 23:37:41 +0200
committerPeter Geissler <peter@haxx.in>2026-08-26 23:37:41 +0200
commit626af41b693aa98cd7d86cabd7c6f1a94d01ec41 (patch)
tree272c7e602b59354512431a6c4d611e0c5332d3e7
parentmacos: sign and notarize on a mac that is not this one (diff)
downloadslosh-626af41b693aa98cd7d86cabd7c6f1a94d01ec41.tar.gz
slosh-626af41b693aa98cd7d86cabd7c6f1a94d01ec41.tar.xz
slosh-626af41b693aa98cd7d86cabd7c6f1a94d01ec41.zip
brew-release: cut a release from the Linux box it is cut from
Nothing about updating the formula needs a mac or needs brew -- it resolves a ref, fetches a tarball, hashes it, rewrites three stanzas. But it hashed with `shasum`, which is perl's and only usually present on Linux, where `sha256sum` is coreutils and always is. Take whichever exists, as macos-remote-build already did. The version count no longer falls back to 0 when the commit is not in this clone. `0.1.0-0-<short>` is older than every version published, so a release cut from a clone that had not fetched yet would have looked like a downgrade and never installed; say so and stop instead. Also written down: why the formula builds from source rather than shipping the notarized zip the mac builder makes -- brew's curl sets no quarantine attribute, so notarization gains a formula nothing, and the build is a minute with zig as its only dependency. If that ever changes the answer is bottles, which are additive and cost a per-macOS-version matrix, not a cask.
-rwxr-xr-xcontrib/brew-release23
-rw-r--r--contrib/homebrew/README.md38
-rw-r--r--contrib/homebrew/slosh.rb6
3 files changed, 62 insertions, 5 deletions
diff --git a/contrib/brew-release b/contrib/brew-release
index 603ed88..d3eadda 100755
--- a/contrib/brew-release
+++ b/contrib/brew-release
@@ -29,6 +29,18 @@ TAP=""
fail() { printf 'brew-release: %s\n' "$*" >&2; exit 1; }
say() { printf ' %s\n' "$*"; }
+# Neither brew nor a mac is needed to run this -- it resolves a ref, fetches a
+# tarball and hashes it -- so it has to work on the Linux box a release is cut
+# from. `shasum` is perl's and merely usually present there; `sha256sum` is
+# coreutils and always is. Take whichever exists.
+sha256_of() {
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
+ elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
+ elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 "$1" | sed 's/.*= *//'
+ else fail "no sha256sum, shasum or openssl here -- cannot hash the snapshot"
+ fi
+}
+
while [ $# -gt 0 ]; do
case "$1" in
--tap) [ $# -ge 2 ] || fail "--tap needs a directory"; TAP="$2"; shift 2 ;;
@@ -75,7 +87,14 @@ esac
mkver="$(sed -n 's/^VERSION *:*= *//p' "$root/Makefile" | head -1)"
[ -n "$mkver" ] || fail "no VERSION in $root/Makefile"
short="$(printf '%s' "$full" | cut -c1-7)"
-count="$(git -C "$root" rev-list --count "$full" 2>/dev/null || echo 0)"
+# The count must be a real count. Falling back to 0 when the commit is not in
+# this clone would mint "0.1.0-0-<short>", which brew reads as older than every
+# version already published -- so a release cut from a clone that had not
+# fetched yet would look like a downgrade and never install.
+count="$(git -C "$root" rev-list --count "$full" 2>/dev/null || true)"
+[ -n "$count" ] || fail "commit $full is not in this clone, so its position in
+history is unknown and the version would be a lie. Fetch it first:
+ git -C $root fetch origin"
case "$label" in
v[0-9]*.[0-9]*) version="${label#v}" ;;
[0-9]*.[0-9]*) version="$label" ;;
@@ -93,7 +112,7 @@ trap 'rm -rf "$tmp"' EXIT INT TERM
code="$(curl -sL -o "$tmp/snap.tar.gz" -w '%{http_code}' "$url")"
[ "$code" = 200 ] || fail "snapshot fetch returned HTTP $code -- is the ref pushed?"
tar tzf "$tmp/snap.tar.gz" >/dev/null 2>&1 || fail "snapshot is not a valid tarball"
-sha="$(shasum -a 256 "$tmp/snap.tar.gz" | cut -d' ' -f1)"
+sha="$(sha256_of "$tmp/snap.tar.gz")"
say "sha256 $sha"
# Rewrite exactly the three stanzas, leaving every comment in place.
diff --git a/contrib/homebrew/README.md b/contrib/homebrew/README.md
index 4f62299..56501f0 100644
--- a/contrib/homebrew/README.md
+++ b/contrib/homebrew/README.md
@@ -31,13 +31,51 @@ master instead of the pinned release.
higher bar: a stable tagged release, a LICENSE, notable usage, and no
network access during the build. See "known rough edges" below.)
+## Why source and not the signed binary
+
+The obvious thought, once there is a mac builder producing a signed, notarized
+zip, is to point brew at that zip instead. Three reasons not to:
+
+- **Notarization buys nothing here.** Gatekeeper only checks binaries carrying
+ the `com.apple.quarantine` attribute, and the curl that fetches a formula's
+ tarball does not set it. (Casks are different: they manage quarantine
+ deliberately.) Every bottle in homebrew-core is unsigned-but-for-ad-hoc and
+ runs fine. The notarized zip matters for the *web* download, where the
+ browser does set the attribute — not for `brew install`.
+- **The build is a minute and needs one thing.** zig. Formulae that take ten
+ minutes and drag in twenty dependencies are the ones that need binaries.
+- **Source is the only path that covers Linux**, which brew also runs on.
+
+If the build time ever does become the complaint, the answer is **bottles**,
+not a cask and not a hand-rolled binary url. A bottle is Homebrew's own binary
+format: a tarball of the installed keg, listed in a `bottle do` block with a
+`root_url` pointing at wherever we host them. It is *additive* — the formula
+keeps its source path, and anyone whose macOS version we have not bottled
+falls back to building. The cost is a matrix: bottles are tagged per macOS
+version *and* arch (`arm64_sequoia`, `arm64_sonoma`, ...), each one built on
+that version. With one builder mac that means one tag covered and everyone
+else compiling — which is most of the work for a fraction of the benefit,
+until there are more builders.
+
## Releasing
`slosh.rb` here is the source of truth; the tap repo gets a copy.
+**This needs no mac and no brew.** `brew-release` resolves a ref, downloads the
+snapshot and hashes it, so cutting a release works from the Linux box — which
+is the point, because that is where releases are cut. Signed macOS binaries are
+a separate pipeline (`make macos-dist`) that brew is not involved in.
+
```bash
+git fetch origin && git push origin v0.2.0 # brew-release pins what
+ # the server can serve
contrib/brew-release --tap ../homebrew-slosh # pin origin/master
contrib/brew-release --tap ../homebrew-slosh v0.2.0 # pin a pushed tag
+```
+
+Then, on a machine that has brew (either OS), prove it before pushing the tap:
+
+```bash
brew install --build-from-source contrib/homebrew/slosh.rb
brew test slosh
```
diff --git a/contrib/homebrew/slosh.rb b/contrib/homebrew/slosh.rb
index c71fa9a..ae0ed1c 100644
--- a/contrib/homebrew/slosh.rb
+++ b/contrib/homebrew/slosh.rb
@@ -17,9 +17,9 @@ class Slosh < Formula
# cgit serves a deterministic snapshot per ref: same ref, same bytes, so the
# sha256 below stays honest. Pinned to a full commit id rather than a branch,
# because a branch tarball changes under you and brew would keep the stale one.
- url "https://git.sl0p.foo/slosh.git/snapshot/slosh-b744a90859bf248bfbea97f990668301b5fa9537.tar.gz"
- version "0.1.0-185-b744a90"
- sha256 "facafd86fc656a1c4fd925faa90b56b6932f7fb816bcdf3a1c55f2f43cb91c10"
+ url "https://git.sl0p.foo/slosh.git/snapshot/slosh-4bd545a46891b6ac5bb7599911d370978b84c234.tar.gz"
+ version "0.1.0-194-4bd545a"
+ sha256 "c720a4e1606a6ed01fb5c3cda106f37c1d36d06d0bec1e9f84a6cb74e3f83c86"
head "https://git.sl0p.foo/slosh.git", branch: "master"
# zig is the whole toolchain: it compiles the C, builds the vendored