9449485dcf
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.
78 lines
2.5 KiB
Bash
Executable File
78 lines
2.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Render .github/homebrew/gortex.rb.tmpl into a concrete Homebrew cask on
|
|
# stdout. The template lives in its own file (rather than a heredoc inside
|
|
# release.yml) so it can be reviewed as Ruby and, crucially, parsed by a real
|
|
# `brew` before a release pushes it to zzet/homebrew-tap — see
|
|
# scripts/validate-cask.sh.
|
|
#
|
|
# Usage:
|
|
# render-cask.sh <version> <sha_darwin_amd64> <sha_darwin_arm64> \
|
|
# <sha_linux_amd64> <sha_linux_arm64>
|
|
#
|
|
# `version` is the bare version with no leading "v" (e.g. 0.63.8).
|
|
set -euo pipefail
|
|
|
|
if [ "$#" -ne 5 ]; then
|
|
echo "usage: $0 <version> <sha_darwin_amd64> <sha_darwin_arm64> <sha_linux_amd64> <sha_linux_arm64>" >&2
|
|
exit 2
|
|
fi
|
|
|
|
version="$1"
|
|
sha_darwin_amd64="$2"
|
|
sha_darwin_arm64="$3"
|
|
sha_linux_amd64="$4"
|
|
sha_linux_arm64="$5"
|
|
|
|
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
|
|
tmpl="$repo_root/.github/homebrew/gortex.rb.tmpl"
|
|
|
|
[ -f "$tmpl" ] || {
|
|
echo "FATAL: cask template not found at $tmpl" >&2
|
|
exit 1
|
|
}
|
|
|
|
case "$version" in
|
|
"")
|
|
echo "FATAL: version is empty" >&2
|
|
exit 1
|
|
;;
|
|
v*)
|
|
echo "FATAL: version must not carry a leading 'v': '$version'" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Every sha must be a full sha256. An empty or truncated value would publish a
|
|
# cask whose downloads all fail a checksum check, and the error the user sees
|
|
# ("SHA256 mismatch") points nowhere near the cause.
|
|
for pair in "darwin_amd64=$sha_darwin_amd64" "darwin_arm64=$sha_darwin_arm64" \
|
|
"linux_amd64=$sha_linux_amd64" "linux_arm64=$sha_linux_arm64"; do
|
|
sha="${pair#*=}"
|
|
if ! printf '%s' "$sha" | grep -Eq '^[0-9a-f]{64}$'; then
|
|
echo "FATAL: sha256 for ${pair%%=*} is not 64 lowercase hex chars: '$sha'" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# The template also contains `#{version}` — Ruby interpolation brew evaluates
|
|
# at install time. It must survive verbatim, which is why substitution is
|
|
# keyed on @@…@@ placeholders rather than shell expansion.
|
|
rendered="$(sed \
|
|
-e "s|@@VERSION@@|$version|g" \
|
|
-e "s|@@SHA_DARWIN_AMD64@@|$sha_darwin_amd64|g" \
|
|
-e "s|@@SHA_DARWIN_ARM64@@|$sha_darwin_arm64|g" \
|
|
-e "s|@@SHA_LINUX_AMD64@@|$sha_linux_amd64|g" \
|
|
-e "s|@@SHA_LINUX_ARM64@@|$sha_linux_arm64|g" \
|
|
"$tmpl")"
|
|
|
|
# A placeholder that survived rendering means the template grew a field the
|
|
# renderer doesn't know about — publishing that would ship a literal @@…@@
|
|
# into the tap.
|
|
if leftover="$(printf '%s\n' "$rendered" | grep -n '@@[A-Z0-9_]*@@')"; then
|
|
echo "FATAL: unsubstituted placeholder left in the rendered cask:" >&2
|
|
printf '%s\n' "$leftover" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf '%s\n' "$rendered"
|