diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 40236fd..535ef8a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -202,10 +202,54 @@ homebrew_casks: # The binary is unsigned (Go only ad-hoc-signs darwin/arm64 so it *runs*); # casks are quarantined on download, so strip the quarantine flag on install # or launch dies with "koc cannot be verified". - hooks: - post: - install: | + # + # This is a raw `custom_block` rather than GoReleaser's `hooks.post.install` + # because that hook is emitted as Homebrew's `postflight do … end`, which + # Homebrew 6.0.16 deprecated in favour of the declarative `postflight_steps`: + # every `brew` command touching the cask printed + # Warning: Calling `postflight` is deprecated! Use `postflight_steps` instead. + # and Homebrew deprecations turn into errors a few releases later. GoReleaser + # cannot emit the `*_steps` stanzas yet (goreleaser/goreleaser#6870, PR #6873, + # milestone v2.19.0) and has no way to emit both spellings, so the cask picks + # one at load time and stays installable on every Homebrew that ever shipped + # this cask. When GoReleaser grows `hooks.post.install_steps` it still cannot + # express the fallback, so this block outlives that. + # + # Four things about the form below: + # - The probe is the *steps DSL*, not `respond_to?(:postflight_steps)`. The + # stanza landed in Homebrew 6.0.0 but the `run`/`on_macos` steps it needs + # only arrived in 6.0.13, so the obvious probe would pick the new branch + # on 6.0.0–6.0.12 and then die inside the block. + # - `{{ "{{staged_path}}" }}` renders as the literal `{{staged_path}}`. + # GoReleaser runs the whole generated cask through its template engine as + # a final pass, so the Homebrew token has to survive it escaped; Homebrew + # expands it in step args at install time. The steps DSL has no Ruby + # interpolation — that was the point of deprecating `postflight` — which + # is why the two branches spell the same path differently. + # - `run` is the steps DSL's escape hatch for a command; the sandbox it + # runs under already grants write access to the caskroom, which is where + # `staged_path` lives, so no `writable_paths` is needed. + # - `custom_block` is injected right after `cask "koc" do`, so the stanza + # order in the generated file is unconventional. It is still correct: + # Homebrew orders artifacts by class (Binary, then PostflightSteps — see + # Cask::Artifact::AbstractArtifact.sort_order), never by file position, + # so this runs after the binary is staged and linked. + custom_block: | + # Homebrew >= 6.0.13 takes the declarative steps; older clients keep the + # `postflight` block, which is only deprecated from 6.0.16 on. + steps_dsl = defined?(Homebrew::InstallSteps::DSL) && Homebrew::InstallSteps::DSL + if steps_dsl && steps_dsl.method_defined?(:run) && steps_dsl.method_defined?(:on_macos) + postflight_steps do + on_macos do + run "/usr/bin/xattr", + args: ["-dr", "com.apple.quarantine", "{{ "{{staged_path}}" }}/koc"] + end + end + else + postflight do if OS.mac? system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/koc"] end + end + end diff --git a/AGENTS.md b/AGENTS.md index 4ab31dc..f271dd5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -540,6 +540,34 @@ completions`**, run in its own secret-free step before GoReleaser. Locally, run `make completions` before `goreleaser release`/`build` or the archives will be missing `completions/`. +**The cask's quarantine step is a `custom_block`, not a GoReleaser hook.** The +binary is unsigned, so the cask has to strip `com.apple.quarantine` from the +staged `koc` or macOS refuses to launch it. GoReleaser emits +`homebrew_casks.hooks.post.install` as Homebrew's `postflight do … end`, which +Homebrew 6.0.16 deprecated in favour of the declarative `postflight_steps` — +every `brew` command touching the cask printed a deprecation warning, and +Homebrew deprecations become errors a few releases later. GoReleaser cannot emit +the `*_steps` stanzas yet (goreleaser/goreleaser#6870, PR #6873, milestone +v2.19.0) and has no way to emit *both* spellings, so `.goreleaser.yaml` writes +the stanza itself through `custom_block`: the cask probes at load time and picks +`postflight_steps` on Homebrew >= 6.0.13, `postflight` below that, so it installs +on every Homebrew and warns on none. + +The three Homebrew versions that matter, because they are three releases apart +and the obvious probe gets it wrong: the `postflight_steps` **stanza** landed in +6.0.0, the `run`/`on_macos` **steps** the block actually calls landed in 6.0.13, +and `postflight` was **deprecated** in 6.0.16. So the probe asks +`Homebrew::InstallSteps::DSL.method_defined?(:run)`, not +`respond_to?(:postflight_steps)` — the latter is true on 6.0.0–6.0.12, where the +block body would then die. Two more things, both spelled out next to the block: +the Homebrew path token has to be escaped as `{{ "{{staged_path}}" }}` because +GoReleaser runs the whole generated cask through its own template engine as a +final pass (the legacy branch spells the same path `#{staged_path}`, since the +steps DSL has no Ruby interpolation); and `custom_block` lands right after +`cask "koc" do`, which is fine because Homebrew orders artifacts by class rather +than by file position. `hooks.post.install_steps`, when GoReleaser ships it, +still cannot express the fallback branch, so this block outlives it. + **Builds are byte-reproducible, and it takes more than one setting.** `builds: mod_timestamp: {{ .CommitTimestamp }}` fixes the binary, but a tar/zip records every *member's* mtime, so `LICENSE`, `README.md` and the generated completions diff --git a/README.md b/README.md index 2c33335..d4369f8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,10 @@ brew install ftarasenko/tap/koc No `--cask` flag is needed — nothing else in the tap shares the name. The binary is unsigned, so the cask strips the macOS quarantine flag on install; on Apple -Silicon Go already ad-hoc-signs the binary so it runs. +Silicon Go already ad-hoc-signs the binary so it runs. That step picks its own +spelling at load time — Homebrew's declarative `postflight_steps` on 6.0.13 and +newer, the older `postflight` block below that — so the cask installs on any +Homebrew version and prints a deprecation warning on none. ### Shell completion