24. CI/CD & releases 🔴¶
Two GitHub Actions workflows, each with one clear job.
ci.yml — verify every push and pull request¶
flowchart LR
A[checkout] --> B[JDK 21 + Gradle cache] --> C[unit & screenshot tests] --> D[debug build]
CI answers one question: do the tests pass and does the app build? Nothing else runs, so it's fast and its failures are unambiguous. Test reports are uploaded only when something fails.
gradle/actions/setup-gradle caches dependencies and Gradle's configuration cache between runs.
release.yml — tags v* or a manual run¶
Release job
- Derive
versionNamefrom the tag (v1.2.0→1.2.0) andversionCodefrom the run number. - If signing secrets exist, decode the keystore to a temp file and point Gradle at it via environment variables.
- Run the tests, then build the release APK and AAB (R8-minified, resource-shrunk, with the Baseline Profile).
- Publish a GitHub Release with both files and auto-generated notes.
Docs job (runs alongside)
- Generate the API reference with Dokka into
docs/docs/api. - Build this guide with MkDocs Material in
--strictmode (a broken link fails the build). - Deploy the site to GitHub Pages.
A manual run with docs only ticked republishes the guide without cutting a release.
Signing lives in the environment¶
val keystorePath: String? = System.getenv("TTAC_KEYSTORE_PATH")
signingConfigs { if (keystorePath != null) create("release") { storeFile = file(keystorePath); … } }
buildTypes { release { if (keystorePath != null) signingConfig = signingConfigs.getByName("release") } }
No secrets in the repository, and local release builds still work (unsigned). Add KEYSTORE_BASE64,
KEYSTORE_PASSWORD, KEY_ALIAS and KEY_PASSWORD as repository secrets to sign.