6.5 KiB
How to Release
- Make sure your local repository is in a clean state, nothing outstanding
- Change branch to
develop - When
developis ready to release, run:./gradlew prepareForRelease- Leave Auto-publish the GitHub release unchecked to publish by hand (see Auto-publishing)
- This will prepare your repo by doing the following:
- Increment app version
appinlibs.versions.toml - Add new changelog in
fastlane\metadata\android\en-US\changelogscalledn.txtwherenis the android version code - Bake the release notes into the app at
common/src/commonMain/composeResources/files/changelog.md— this is what the in-app "What's New" dialog shows, so never edit it by hand - Merge
developintorelease - Tag the latest commit to make the release from in the semvar format
of
v1.1.1 - Push to origin
- Increment app version
- This will trigger the
releaseaction on GitHub which will create a new Release, and build all the artifacts - Once the
releaseaction is complete open the new Release on GitHub - Click Edit
- Uncheck "Set as a pre-release" and instead check "Set as the latest release"
- Click the Publish Release button
- This will trigger the
publishaction which will upload artifacts to stores, deploy to hammer.ink, and notify the Discord channel of a new release - All done!
Auto-publishing
The release dialog's Auto-publish the GitHub release checkbox (off by default) does the
manual publish steps above for you. When it is checked prepareForRelease appends an
Auto-Publish: true trailer to the tag message; once every build job passes, set-release-body
strips the trailer from the release body and marks the release as the latest production release,
firing the same publish action.
This needs a RELEASE_PAT repository secret: a fine-grained PAT scoped to this repo with
Contents: read and write. GitHub does not start workflow runs from events triggered by the
default GITHUB_TOKEN, so publishing with that token would leave every store untouched. The step
fails with a clear error when the secret is missing.
Changelog tabs
The release dialog has four changelog tabs. Only Full has to be written; the rest mirror it and are there for when a store needs something different.
| Tab | Goes to | Limit |
|---|---|---|
| Full | CHANGELOG.md, the GitHub release, the tag message, and the in-app What's New |
none |
| App stores | Flathub, on releases to every store | none |
| Google Play | fastlane/metadata/android/.../changelogs/<versionCode>.txt, which F-Droid reads too |
500 |
| Apple | fastlane/metadata/ios/en-US/ and .../osx/en-US/release_notes.txt |
4000 |
Each tab follows the one above it until you type in it, after which it says Hand-edited and offers Re-sync. So the usual release is one piece of writing on Full; a tab is touched only when that store needs its own wording.
App stores is Full with the entries tagged for a non-app audience removed
([Web], Server:, a whole [Server operators] section, and so on), because Apple
rejects release notes describing anything but the app and Google Play has an equivalent
policy. The tab reports what it took out, since the tag matching is a heuristic and a
silent removal is how an app-facing change goes missing from a listing.
The two store tabs differ in more than length: Google Play's notes carry a link to the full notes on GitHub, and Apple's must not, because App Store review reads that link as offering the app outside the App Store and rejects the submission.
Google Play's 500 characters are a hard stop: over the limit, the counter turns red and Commit Changes is disabled. Trim to fit drops the auto-fitted text into the editor as a starting point, so what ships is text someone chose rather than a sentence cut off at a bullet boundary.
Tabs for stores outside the current publish scope are disabled, and their notes are
dropped on commit. A partial release leaves every notes file it skips untouched: the
iOS and macOS files are written separately even though they share the Apple tab, and
Flathub, which is published by hand with publishFlathub rather than by the release
tag, only gets a release entry when every client store is targeted.
Partial (single-store) releases
Use this when one store needs a hotfix and you don't want to ship the whole matrix.
- The Tag: being pushed will have the suffix of the stores to publish to:
- Full release:
v1.2.4 - Single store:
v1.2.4+google-play - Subset:
v1.2.4+google-play+fdroid - Server only:
v1.2.4+server
- Full release:
- Server only ships no client store at all. The server distribution is built and
attached to the release like always (and deployed to hammer.ink
out of band); the
+servertag simply matches none of the per-store publish jobs, so no app store upload runs. - The global version in
libs.versions.tomlis still bumped every time, so stores not included this cycle catch up on the next full release (their patch number will skip forward, which Apple/Google both accept since each store sees a monotonically increasing version code).
Backing out a release (before remote was updated)
If you ran prepareForRelease and it failed, leaving things in a half prepared state,
then this ones for you. Run:
./gradlew backoutLastRelease
- Resets
developback to before the "Prepared for release" commit. - Resets the local
releasebranch toorigin/release. - Deletes every local tag for this version — both the bare
vX.Y.Zand anyvX.Y.Z+platform+...partial-release variants. - Does NOT touch the remote. If the push already succeeded, use
revertLastReleaseinstead.
Reverting a release that was already pushed
If the prepare push hit origin and you need to undo it:
./gradlew revertLastRelease
- Force-pushes
releaseback to its pre-merge state. - Force-pushes
developback to before the "Prepared for release" commit. - Deletes the version's tags from both remote and local — bare
vX.Y.Zplus anyvX.Y.Z+platform+...partial-release variants. Remote is deleted first so a failure there doesn't leave a stale local tag that could re-push later. - Anyone who pulled in between will need to reset their local clone.
- Does NOT undo store uploads. If the publish workflow already shipped a build to a store, you have to roll that back through the store's UI (or by shipping a higher version code with the fix).