Releasing
How a release is made, and how updates reach people.
flowchart LR
MAIN["main"] -->|"Release (Beta)"| BETA["v0.2.0-beta.57<br/>pre-release"]
BETA -->|"Release"| STABLE["v0.2.0<br/>release"]
BETA --> BETAFEED["Beta appcast"]
STABLE --> FEED["Appcast"]
STABLE --> TAP["Homebrew tap"]
Channels#
| Channel | Version | Who gets it |
|---|---|---|
| Beta | X.Y.Z-beta.N |
People who installed a beta build |
| Stable | X.Y.Z |
Everyone else, and Homebrew |
BETA_VERSIONholds the version that betas count toward.Nis the number of commits, which is also the build number.- A stable release is always a beta that was promoted: the beta's tag is built again under the stable version.
Making a release#
Both workflows are started by hand, under Actions on GitHub.
- Run Release (Beta). It builds
mainas the next beta and publishes it as a pre-release. Try that build. - Run Release with the beta's tag, the stable version, and the version
the next betas should count toward. It publishes the release, updates the
Homebrew tap, and writes the new
BETA_VERSIONtomain.
Workflows#
| Workflow | What it does |
|---|---|
| Checks | On pull requests: builds the app and runs the tests |
| Release (Beta) | Builds main as a beta, publishes a pre-release, and adds it to the beta appcast |
| Release | Promotes a beta to stable, publishes the release with its appcast, and bumps BETA_VERSION |
| Update Homebrew Tap | Writes the cask to swiftvm-app/homebrew-tap. Runs after a release, or by hand |
| Republish Beta Appcast | Puts a beta back into the beta appcast |
| Build Release | The build that both releases share |
They run on GitHub's xcode-27 image, which has the macOS 27 SDK.
What a release build does#
scripts/build-release.shbuilds the app, gives it its version and build number, tells it where its updates are, and signs it with the Developer ID certificate and the USB provisioning profile. Then it packs the app into a disk image.- The workflow has Apple notarize the disk image and staples the ticket to it.
scripts/generate-appcast.shsigns the disk image with the update key and adds it to the appcast.
To try the build on your Mac:
scripts/build-release.sh --version 0.2.0-beta.1 # into build/releaseUpdates#
The app updates itself with Sparkle.
| Channel | Appcast |
|---|---|
| Stable | appcast.xml of the latest release |
| Beta | appcast-beta.xml of the beta-channel pre-release. Don't delete that release |
- A release build carries the address of its appcast and the public half of the update key. Other builds carry neither and don't update.
- The app only installs updates that are signed with the update key. If the key is lost, the apps that are out there can't be updated any more.
- The app has no Dock icon, so an update that is found in the background is announced in the panel and not in a window.
- Installing an update quits the app the usual way, with the usual question about running machines.
Secrets#
The release workflows read these from the repository's secrets.
| Secret | What it is |
|---|---|
DEVELOPER_ID_APPLICATION_CERTIFICATE |
The Developer ID Application certificate with its private key, as a .p12 file in Base64 |
DEVELOPER_ID_APPLICATION_PASSWORD |
The password of that .p12 file |
KEYCHAIN_PASSWORD |
Any password. It locks the temporary keychain on the runner |
DEVELOPER_ID_PROVISIONING_PROFILE |
The Developer ID provisioning profile for com.swiftvm.app with Claim USB Accessory, in Base64 |
APPLE_ID |
The Apple ID that notarizes |
APPLE_APP_SPECIFIC_PASSWORD |
An app-specific password of that Apple ID |
APPLE_TEAM_ID |
The team's ID |
SPARKLE_PRIVATE_KEY |
The update key, from swift scripts/generate-sparkle-key.swift |
HOMEBREW_TAP_TOKEN |
A token that can push to swiftvm-app/homebrew-tap |
GH_RELEASE_PAT |
Optional. A token that can push to main, if main is protected |