Release pipeline#85
Draft
graeme wants to merge 21 commits into
Draft
Conversation
Split the build/sign/package machinery out of release.yml into a dedicated Build workflow that runs on every push to main (and as a signed-but-not-notarised dry run on pull requests). On main it produces a fully signed, notarised and stapled .pkg and uploads it as a fixed-name `BrewUI-pkg` artifact so the promote workflow can attach the most recent successful build to a release, without ever rebuilding at release time. The marketing version is read from the project build settings (MARKETING_VERSION, sourced from Configurations/Version.xcconfig) rather than from a tag input. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the fragile "create tag then build" release with the robust pattern from the blogpost: the binary is already built and notarised by build.yml on merge to main, and Release just promotes it. On the manual "Run workflow" trigger it reads the marketing version from Configurations/Version.xcconfig, creates the tag locally, downloads the most recent successful main build artifact, verifies it matches the version, then pushes the tag and creates the GitHub Release with the notarised .pkg attached. Pushing the tag happens last, so a failed run never leaves a dangling tag on GitHub. Pushing a change to this workflow triggers a dry run that exercises permissions without creating a tag or release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After a successful release, bump the marketing version in Configurations/Version.xcconfig (minor by default, e.g. 1.0 -> 1.1) and open a PR against main. main is branch-protected, so GITHUB_TOKEN cannot push to it directly; a PR is the supported path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After the GitHub Release exists, run `brew bump-cask-pr` to open a PR against Homebrew/homebrew-cask bumping the brewui cask to the released version, pinning the notarised package's sha256. Requires a HOMEBREW_GITHUB_API_TOKEN PAT that can fork homebrew-cask and open a PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add docs/RELEASING.md covering the build-on-merge + manual-promote pipeline: how to cut a release, the required secrets (including the new HOMEBREW_GITHUB_API_TOKEN), immutable-tag and first-time-cask caveats, and a stub for the future patch-release workflow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pkgbuild --root'd the entire xcodebuild export directory, which also contains the exporter's own DistributionSummary.plist, ExportOptions.plist and Packaging.log — so the installed .pkg dropped those stray files into /Applications next to Brew.app. Use --component on the app bundle instead, so only Brew.app is packaged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The postinstall script installed Homebrew by curling install.sh at install time: it needs network access during installation, runs as the console user via su, and fails silently on headless/MDM installs. Instead, download the official Homebrew.pkg and ship it as a nested sub-package in the distribution: productbuild --synthesize builds a distribution that installs Homebrew first, then Brew.app. This drops the --scripts postinstall from pkgbuild and deletes scripts/postinstall. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment on lines
+23
to
+27
| - name: Checkout repository | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
|
|
||
| # Creates a short-lived keychain isolated to this job. The 6-hour | ||
| # timeout (21600 s) exceeds any reasonable build duration. |
Version.xcconfig is a repo source file, not part of the published docs site, so the relative link failed htmlproofer's internal link check. Reference it as inline code instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The app target is named Homebrew, so PRODUCT_NAME builds Homebrew.app, not Brew.app. The packaging steps inherited the stale Brew.app paths from the old workflow, which would have failed the signature check and pkgbuild. Point --component and the signature verification at export/Homebrew.app, and name the archive Homebrew.xcarchive to match. The scheme name (Brew) and bundle identifier (sh.brew.app) are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the Homebrew.app rename across the deliverables: the component and distribution packages become Homebrew-component.pkg / Homebrew-<version>.pkg, the build artifact becomes Homebrew-pkg, and the cask bumped by bump-cask-pr becomes homebrew. Left unchanged: the BrewUITests target and the Tools/BrewUILint linter (unrelated to the app bundle), and the repo/project name in docs prose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace `ls dist/Homebrew-*.pkg | head` with a nullglob array so the package is located without parsing ls output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
productbuild cannot nest Homebrew.pkg: it is a product archive (a finished installer with its own scripts), and productbuild only assembles component packages, so --synthesize failed with "is a product archive, not a component package". Instead, stage the official signed Homebrew.pkg into the pkgbuild scripts directory and run it from a postinstall via `installer -pkg` when Homebrew is not already present. This preserves Homebrew's own install logic and signature, and is still offline (no curl | bash). The distribution package goes back to wrapping the single app component. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cask install path drops a bare Homebrew.app, which needs its own stapled notarisation ticket — the pkg's ticket does not travel with the extracted app. Notarise and staple the app bundle before packaging (so the copy embedded in the pkg is stapled too), then ditto the stapled app into Homebrew-<version>.zip and upload it as a second fixed-name artifact (Homebrew-app) alongside the pkg. Attestation now covers both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The promote workflow now downloads both build artifacts, checks each matches the version, and attaches both to the GitHub Release: the .pkg (website download) and the .zip (bare app, for the future cask PR). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cask installs the bare app (the zip), so bump-cask-pr must pin the zip's sha256, not the pkg's. Hash $APP_ZIP instead of $PKG_PATH. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Confirmed with a Homebrew lead maintainer that the cask will be named homebrew-app. Update the bump-cask-pr token and the docs to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Do not merge — revert this commit before opening for review. - build.yml: notarise/staple/zip/attest/upload also fire on this branch, so it produces real artifacts here (normally main-only). - release.yml: triggers on pushes to this branch, downloads this branch's build artifacts, and runs the tag/release/version-bump steps for real. - The cask PR step stays dispatch-only, so it is skipped here. Every change is tagged with a THROWAWAY comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The archive step failed with "A timestamp was expected but was not found" during codesign of Homebrew.app. Developer ID signing requires a secure Apple timestamp, and notarisation requires the hardened runtime; neither was pinned, so codesign behaviour on CI was non-deterministic. Add --timestamp --options runtime to OTHER_CODE_SIGN_FLAGS, and verify the archived app carries a Timestamp= line so this fails fast with a clear message instead of surfacing later at notarisation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The notarise/staple/upload steps are guarded by event_name == 'push', but build.yml's push trigger was scoped to main, so feature-branch runs came through the pull_request event and the guards were skipped. Add this branch to the push trigger so it fires a real push event and the existing guards run — the same push-event path main uses. Revert before review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Automated build, notarise and release pipeline
Summary
Adds a two-stage macOS release pipeline. Every merge to
mainbuilds, signs and notarises the app; a manual "Run workflow" step promotes the latest build to a GitHub Release and opens the downstream PRs. With the version now at0.2.0, the first tag will bev0.2.0.Changes
build.yml(new) — on merge tomain: archive with Developer ID signing (secure timestamp + hardened runtime), notarise and staple both the app and the.pkg, then upload two fixed-name artifacts:Homebrew-<version>.pkg— website download; bundles the official signedHomebrew.pkgand runs it from a postinstall (offline, nocurl | bash).Homebrew-<version>.zip— the bare, stapledHomebrew.appfor the cask.release.yml(rewritten) — manualworkflow_dispatchpromote: reads the version fromVersion.xcconfig, tags locally, downloads the latestmainartifacts, pushes the tag only on success, creates the Release with both assets, opens a version-bump PR, and opens ahomebrew-appcask PR viabump-cask-pr.docs/RELEASING.md(new) — process, required secrets, and caveats.Why this split
Building and notarising on merge means nothing ships that hasn't already built successfully — the promote step never rebuilds, so a late failure can't leave a broken tag.
Testing
bash -n.homebrew-appcask to exist and theHOMEBREW_GITHUB_API_TOKENsecret.PR checklist
Follow-ups
HOMEBREW_GITHUB_API_TOKENsecret (a PAT that can forkhomebrew-cask).homebrew-appcask submission is manual;bump-cask-prautomates every bump after that.