Release
Current state (honest)
Amply is pre-launch (0.1.0-beta1). It uses CAPod's versioning system: a version.properties source of truth,
a VERSION mirror, a tools/release/bump.sh bump/validate tool, and release-prepare / release-tag workflows. The
tooling exists but no release has been cut. Fastlane provides store-listing metadata + screenshots
(fastlane/metadata/android/en-US/) plus manual supply lanes in fastlane/Fastfile. Those lanes are not wired
into CI and are gated (see below), so release-tag.yml still publishes a FOSS GitHub release only (see CI below).
Store listing & screenshots
Metadata: fastlane/metadata/android/en-US/{title,short_description,full_description}.txt + changelogs/default.txt,
validated in CI by check_metadata_length.sh (title 30 / short 80 / full 3800 / changelog 500 chars).
Screenshots: generated from @Preview composables on the JVM (no device) via the Compose Preview Screenshot
Testing plugin (com.android.compose.screenshot, enabled by android.experimental.enableScreenshotTest=true in
gradle.properties). The store composables live in app/src/debug/.../screenshots/ScreenshotContent.kt; the
capture entry points (@PreviewTest) and locale annotations live in app/src/screenshotTest/.../screenshots/.
# 1. Render (writes to app/src/screenshotTestGplayDebug/reference/, which is gitignored)
./fastlane/generate_screenshots.sh
# 2. Normalize (flatten alpha → opaque 1080x1920) + sort into the committed metadata tree
./fastlane/copy_screenshots.sh
Committed output lands in fastlane/metadata/android/en-US/images/phoneScreenshots/ as 1_dashboard_light.png … 6_reconnect_gesture.png (names come from copy_screenshots.sh's screen_file map). Both scripts fail loudly on
any count/dimension/format mismatch and copy_screenshots.sh requires ImageMagick. Needs the JDK 21 build
toolchain like everything else. CI compiles these sources (compileGplayDebugScreenshotTestKotlin) but does
not render — layoutlib output differs across machines — so regenerating screenshots is a manual pre-release
step.
supply lanes (beta / production / listing_only / screenshots_only): every lane that mutates Play state
refuses to run unless AMPLY_PLAY_PUBLISH_APPROVED=1, keeping publication behind the specialUse gate below.
Prerequisites (all manual, none automated): an existing Play app for eu.darken.amply, a service-account JSON at the
Appfile path (or AMPLY_SUPPLY_JSON_KEY), gplay signing material, and the specialUse review passed.
Versioning
- Single source:
version.properties at the repo root, parsed at configuration time by the buildSrc
ProjectConfigPlugin (CAPod's scheme): versionName = "major.minor.patch-type{build}",
versionCode = major*10000000 + minor*100000 + patch*1000 + build*10. type must be rc or beta (bump.sh
rejects anything else). A root VERSION file (<name> <code>) mirrors it as a drift check.
- Constraints (enforced by both
ProjectConfig's requires and bump.sh): minor, patch, build are 0..99 —
overflow collides with the next-higher field (e.g. patch=0,build=100 equals patch=1,build=0). Changing only
type does not change the versionCode, so a store update needs another field bumped. versionCode stays
monotonic (bump.sh refuses a non-increasing code).
- Bumping: don't hand-edit — run
./tools/release/bump.sh --mode=plan --bump-kind=<build|patch|minor|major>
to preview, --mode=write to apply (rewrites both files and re-verifies). --mode=check validates consistency
(also run in CI). Prefer the release-prepare workflow, which wraps bump.sh with tag-collision guards.
- No build-type or flavor suffixes: every variant installs as
eu.darken.amply with the same versionName. Because
signing certificates differ (debug key vs foss key vs gplay upload key), installed variants are mutually exclusive
on a device — switching requires an uninstall.
Signing
Release/beta signing loads per flavor (releaseFoss, releaseGplay) from either:
- Environment variables:
STORE_PATH, STORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD (checked first), or
- Properties files under
~/.config/projects/eu.darken.amply/:
signing-foss.properties and signing-gplay-upload.properties, using keys release.storePath,
release.storePassword, release.keyAlias, release.keyPassword.
If signing material is absent, the flavor simply builds unsigned — the config degrades gracefully rather than failing.
Never commit signing material or point these at repo paths.
CI
.github/workflows/code-checks.yml runs on push to main and on PRs as matrix jobs (CAPod parity): wrapper
validation (in the shared common-setup action), lintVital{Foss,Gplay}{Beta,Release}, assemble{Foss,Gplay}Debug,
test{Foss,Gplay}DebugUnitTest, a fastlane metadata length check, and a release-tooling check (shellcheck +
bats on bump.sh, plus bump.sh --mode=check). It skips version-bump-only pushes (paths-ignore: VERSION, version.properties). CI does not run R8/minified packaging (lintVital only compiles the beta/release sources) —
run ./gradlew assembleFossRelease assembleGplayRelease locally before tagging a release.
Release workflows
release-prepare.yml (workflow_dispatch): computes the next version with bump.sh, guards tag collisions,
and — when dry_run=false — commits version.properties+VERSION, tags v<name>, and pushes. The push job needs
the RELEASE_APP_CLIENT_ID / RELEASE_APP_PRIVATE_KEY GitHub App secrets; the dry-run plan needs no secrets.
A manual workflow_dispatch (dry-run/build-only) must be launched from a v* tag ref, not a branch — the
tag-validation job intentionally fails on a branch ref so the publish job can never run against a non-tag.
release-tag.yml (on a v* tag): validates the tag against version.properties, builds assembleFossBeta
(for -beta tags) or assembleFossRelease, and attaches the versioned APK to a GitHub (pre-)release. Signed only
when SIGNING_KEYSTORE_BASE64 + STORE_PASSWORD/KEY_ALIAS/KEY_PASSWORD secrets are present; otherwise the APK
is unsigned (its filename carries an -UNSIGNED marker).
- Deliberately omitted vs. CAPod: no Google Play upload job (the manual
supply lanes exist but are gated on
AMPLY_PLAY_PUBLISH_APPROVED and never wired into CI, and Play
publication must stay gated — see below) and no Pages deploy (the project website is deferred).
Google Play gate
Google Play publication must remain gated on approval of the declared specialUse foreground-service use case (see
privileged-access.md). Do not treat a green CI build as clearance to publish. release-tag.yml intentionally does
not upload to Play — that step is added only after the use-case approval lands.
1---2name: release3description: Amply's release process — versioning via version.properties/bump.sh, signing material, the release-prepare/release-tag workflows, store-listing metadata, Play Store screenshot generation, and the Google Play publication gate. Use when cutting a release, bumping a version, regenerating store screenshots, or touching signing/release CI.4---56# Release78## Current state (honest)910Amply is **pre-launch** (`0.1.0-beta1`). It uses CAPod's versioning system: a `version.properties` source of truth,11a `VERSION` mirror, a `tools/release/bump.sh` bump/validate tool, and `release-prepare` / `release-tag` workflows. The12tooling exists but no release has been cut. Fastlane provides **store-listing metadata + screenshots**13(`fastlane/metadata/android/en-US/`) plus manual `supply` lanes in `fastlane/Fastfile`. Those lanes are **not** wired14into CI and are gated (see below), so `release-tag.yml` still publishes a **FOSS GitHub release only** (see CI below).1516## Store listing & screenshots1718- **Metadata**: `fastlane/metadata/android/en-US/{title,short_description,full_description}.txt` + `changelogs/default.txt`,19 validated in CI by `check_metadata_length.sh` (title 30 / short 80 / full 3800 / changelog 500 chars).20- **Screenshots**: generated from `@Preview` composables on the JVM (no device) via the Compose Preview Screenshot21 Testing plugin (`com.android.compose.screenshot`, enabled by `android.experimental.enableScreenshotTest=true` in22 `gradle.properties`). The store composables live in `app/src/debug/.../screenshots/ScreenshotContent.kt`; the23 capture entry points (`@PreviewTest`) and locale annotations live in `app/src/screenshotTest/.../screenshots/`.2425 ```bash26 # 1. Render (writes to app/src/screenshotTestGplayDebug/reference/, which is gitignored)27 ./fastlane/generate_screenshots.sh28 # 2. Normalize (flatten alpha → opaque 1080x1920) + sort into the committed metadata tree29 ./fastlane/copy_screenshots.sh30 ```3132 Committed output lands in `fastlane/metadata/android/en-US/images/phoneScreenshots/` as `1_dashboard_light.png …33 6_reconnect_gesture.png` (names come from `copy_screenshots.sh`'s `screen_file` map). Both scripts fail loudly on34 any count/dimension/format mismatch and `copy_screenshots.sh` requires ImageMagick. Needs the JDK 21 build35 toolchain like everything else. CI compiles these sources (`compileGplayDebugScreenshotTestKotlin`) but does36 **not** render — layoutlib output differs across machines — so **regenerating screenshots is a manual pre-release37 step**.38- **`supply` lanes** (`beta` / `production` / `listing_only` / `screenshots_only`): every lane that mutates Play state39 refuses to run unless `AMPLY_PLAY_PUBLISH_APPROVED=1`, keeping publication behind the `specialUse` gate below.40 Prerequisites (all manual, none automated): an existing Play app for `eu.darken.amply`, a service-account JSON at the41 `Appfile` path (or `AMPLY_SUPPLY_JSON_KEY`), gplay signing material, and the `specialUse` review passed.4243## Versioning4445- Single source: `version.properties` at the repo root, parsed at configuration time by the buildSrc46 `ProjectConfigPlugin` (CAPod's scheme): `versionName = "major.minor.patch-type{build}"`,47 `versionCode = major*10000000 + minor*100000 + patch*1000 + build*10`. `type` must be `rc` or `beta` (bump.sh48 rejects anything else). A root `VERSION` file (`<name> <code>`) mirrors it as a drift check.49- Constraints (enforced by both `ProjectConfig`'s `require`s and `bump.sh`): `minor`, `patch`, `build` are `0..99` —50 overflow collides with the next-higher field (e.g. `patch=0,build=100` equals `patch=1,build=0`). Changing only51 `type` does **not** change the versionCode, so a store update needs another field bumped. versionCode stays52 monotonic (`bump.sh` refuses a non-increasing code).53- **Bumping**: don't hand-edit — run `./tools/release/bump.sh --mode=plan --bump-kind=<build|patch|minor|major>`54 to preview, `--mode=write` to apply (rewrites both files and re-verifies). `--mode=check` validates consistency55 (also run in CI). Prefer the `release-prepare` workflow, which wraps bump.sh with tag-collision guards.56- No build-type or flavor suffixes: every variant installs as `eu.darken.amply` with the same versionName. Because57 signing certificates differ (debug key vs foss key vs gplay upload key), installed variants are mutually exclusive58 on a device — switching requires an uninstall.5960## Signing6162Release/beta signing loads per flavor (`releaseFoss`, `releaseGplay`) from either:6364- Environment variables: `STORE_PATH`, `STORE_PASSWORD`, `KEY_ALIAS`, `KEY_PASSWORD` (checked first), or65- Properties files under `~/.config/projects/eu.darken.amply/`:66 `signing-foss.properties` and `signing-gplay-upload.properties`, using keys `release.storePath`,67 `release.storePassword`, `release.keyAlias`, `release.keyPassword`.6869If signing material is absent, the flavor simply builds unsigned — the config degrades gracefully rather than failing.70Never commit signing material or point these at repo paths.7172## CI7374`.github/workflows/code-checks.yml` runs on push to `main` and on PRs as matrix jobs (CAPod parity): wrapper75validation (in the shared `common-setup` action), `lintVital{Foss,Gplay}{Beta,Release}`, `assemble{Foss,Gplay}Debug`,76`test{Foss,Gplay}DebugUnitTest`, a fastlane metadata length check, and a **release-tooling check** (shellcheck +77bats on `bump.sh`, plus `bump.sh --mode=check`). It skips version-bump-only pushes (`paths-ignore: VERSION,78version.properties`). CI does **not** run R8/minified packaging (lintVital only compiles the beta/release sources) —79run `./gradlew assembleFossRelease assembleGplayRelease` locally before tagging a release.8081## Release workflows8283- **`release-prepare.yml`** (`workflow_dispatch`): computes the next version with `bump.sh`, guards tag collisions,84 and — when `dry_run=false` — commits `version.properties`+`VERSION`, tags `v<name>`, and pushes. The push job needs85 the `RELEASE_APP_CLIENT_ID` / `RELEASE_APP_PRIVATE_KEY` GitHub App secrets; the dry-run plan needs no secrets.86 A manual `workflow_dispatch` (dry-run/build-only) must be launched **from a `v*` tag ref**, not a branch — the87 tag-validation job intentionally fails on a branch ref so the publish job can never run against a non-tag.88- **`release-tag.yml`** (on a `v*` tag): validates the tag against `version.properties`, builds `assembleFossBeta`89 (for `-beta` tags) or `assembleFossRelease`, and attaches the versioned APK to a GitHub (pre-)release. Signed only90 when `SIGNING_KEYSTORE_BASE64` + `STORE_PASSWORD`/`KEY_ALIAS`/`KEY_PASSWORD` secrets are present; otherwise the APK91 is unsigned (its filename carries an `-UNSIGNED` marker).92- **Deliberately omitted vs. CAPod**: no Google Play upload job (the manual `supply` lanes exist but are gated on93 `AMPLY_PLAY_PUBLISH_APPROVED` and never wired into CI, and Play94 publication must stay gated — see below) and no Pages deploy (the project website is deferred).9596## Google Play gate9798Google Play publication must remain gated on approval of the declared `specialUse` foreground-service use case (see99`privileged-access.md`). Do not treat a green CI build as clearance to publish. `release-tag.yml` intentionally does100**not** upload to Play — that step is added only after the use-case approval lands.