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_VERSION holds the version that betas count toward. N is 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.

  1. Run Release (Beta). It builds main as the next beta and publishes it as a pre-release. Try that build.
  2. 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_VERSION to main.

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#

  1. scripts/build-release.sh builds 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.
  2. The workflow has Apple notarize the disk image and staples the ticket to it.
  3. scripts/generate-appcast.sh signs 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/release

Updates#

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

Edit this page on GitHub