Files
Andrey Kumanyaev 9449485dcf release: fix the homebrew cask so it loads at all
The generated cask used `pre_install` / `post_install`. Those are formula
DSL methods; a cask has no such stanzas, so brew rejected the file at load
time with

  Error: Cask 'gortex' definition is invalid: undefined method 'pre_install'

Every brew command that touches the cask parses it, so `brew install` and
`brew upgrade` both failed for every macOS user on both arches, starting
with v0.63.7. Closes #639.

Two further defects made a straight rename insufficient:

  * The cask DSL's `system_command` is `SystemCommand.run!`, which raises on
    a non-zero exit. `gortex daemon status` exits 1 exactly when no daemon
    is reachable, so `next unless status.success?` could never run — brew
    would have aborted the install outright on any machine without a live
    daemon. The probe now passes `must_succeed: false`.

  * `preflight` was the wrong hook for stopping the daemon. On upgrade brew
    unlinks the old cask's binary (`start_upgrade` -> `uninstall_artifacts`)
    before installing the new cask's artifacts, so by the time a preflight
    block runs there is no gortex on disk to ask. The stop half could never
    fire on the path it was written for.

`postflight` handles both halves instead: after the new binary is linked,
probe for a daemon and, only if one answers, `daemon restart` — which stops
the old process (blocking until it exits, releasing the store lock) before
starting the new one, so a store migration still runs alone. A fresh install
and CI have no daemon answering and skip it.

The cask body moves out of the release.yml heredoc into
.github/homebrew/gortex.rb.tmpl, rendered by scripts/render-cask.sh. That
makes it reviewable as Ruby, removes the shell-expansion hazards of an
unquoted heredoc, and — the point — lets a real brew load it before
publication. The renderer also refuses a malformed sha256, a version with a
leading "v", or a placeholder that survived substitution. Rendered output is
byte-identical to the published cask apart from the hook block.

Nothing could have caught this: the file was valid Ruby, valid YAML, and the
tap has no CI, so the first machine to evaluate the cask was a user's.
scripts/validate-cask.sh renders the template with dummy values and loads it
through a real brew. It runs on every PR that touches the cask
(.github/workflows/homebrew-cask.yml) and again in build-darwin as the last
gate before the release job pushes to the tap. Both are macOS-only —
Homebrew on Linux cannot load casks. Verified by reintroducing `pre_install`
in the template: the validator fails with the exact error users reported.
2026-08-20 21:16:46 +02:00

59 lines
2.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Prove the generated Homebrew cask actually loads before a release pushes it
# to zzet/homebrew-tap.
#
# This exists because issue #639 shipped a cask that was valid Ruby but used
# `pre_install` / `post_install` — formula-only DSL methods that the cask DSL
# rejects at load time. Nothing in the Go test suite or a YAML lint could see
# it, and the tap has no CI of its own, so every macOS user got
#
# Error: Cask 'gortex' definition is invalid: undefined method 'pre_install'
#
# on install AND upgrade. Only a real `brew` evaluating the DSL catches that
# class of bug, which is why this renders the template and loads it through
# brew rather than checking it with a linter.
#
# macOS only: Homebrew on Linux cannot load casks at all.
#
# Usage: validate-cask.sh (no arguments — renders with dummy values)
set -euo pipefail
if [ "$(uname -s)" != "Darwin" ]; then
echo "SKIP: casks can only be loaded by Homebrew on macOS (got $(uname -s))"
exit 0
fi
command -v brew >/dev/null 2>&1 || {
echo "FATAL: brew not on PATH; cannot validate the cask" >&2
exit 1
}
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
# Dummy but well-formed inputs. The DSL is evaluated, not downloaded, so the
# shas only have to satisfy render-cask.sh's shape check.
DUMMY_SHA="$(printf '0%.0s' $(seq 1 64))"
rendered="$("$repo_root/scripts/render-cask.sh" 0.0.0 \
"$DUMMY_SHA" "$DUMMY_SHA" "$DUMMY_SHA" "$DUMMY_SHA")"
# Load the cask the way a user's machine will: from a tap on disk. A scratch
# tap keeps the real zzet/tap (which may be installed on a dev machine)
# untouched, and the trap removes it even when brew fails.
tap_user="gortexcaskcheck"
tap_dir="$(brew --repository)/Library/Taps/$tap_user/homebrew-tap"
cleanup() { rm -rf "$(brew --repository)/Library/Taps/$tap_user"; }
trap cleanup EXIT
cleanup
mkdir -p "$tap_dir/Casks"
printf '%s\n' "$rendered" >"$tap_dir/Casks/gortex.rb"
echo "==> loading the rendered cask through brew"
# `brew info --cask` evaluates every stanza, so an unknown one (the #639 bug)
# fails here. Output is kept: a reviewer should see the artifact list change
# when the template changes.
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ENV_HINTS=1 \
brew info --cask "$tap_user/tap/gortex"
echo "==> cask loads cleanly"