Skip to content

android cicd pipeline setup

Type Skill
Plugin awl-android · v0.0.19
Invoke /awl-android:cicd-pipeline-setup
Source plugins/awl-android/skills/cicd-pipeline-setup/SKILL.md

Sets up or updates the GitLab CI/CD pipeline of an Android or KMP app on the AWL android component from devops/ci-cd-templates (unit tests with Kover coverage, .aab bundles, CI-side release signing, Updraft, Play Store publish, CycloneDX SBOM with Vulnerability Tracker upload, secret detection). Use when the user says “add .gitlab-ci.yml”, “set up CI for the Android app”, “wire the android component”, “enable coverage in CI”, “sign release builds in CI”, “add SBOM to the pipeline”, or “the pipeline is missing a stage”. NOT for iOS-only pipelines (use the ios-lint and ios-fastlane components), for GitHub Actions, or for Kubernetes deployments.

Trigger phrases: add .gitlab-ci.yml · set up CI for the Android app · wire the android component · enable coverage in CI · sign release builds in CI · add SBOM to the pipeline · the pipeline is missing a stage

The android component at devops/ci-cd-templates is input-driven: a short include in .gitlab-ci.yml replaces hand-written jobs. The project has to live on gitlab.appswithlove.net, because $CI_SERVER_FQDN/devops/ci-cd-templates/... only resolves there.

Source of truth when anything here looks off: https://gitlab.appswithlove.net/devops/ci-cd-templates/-/blob/main/templates/android/README.md and template.yml next to it.

Step Do Detail
0 Ask which features to enable this file
1 Map flavors and build types to variants this file
2 Write .gitlab-ci.yml this file
3 Wire CI-side signing and CI variables references/signing.md
4 Configure Kover, SBOM, Updraft, Play publish as opted in references/sbom.md, other skills
5 Verify tasks and open an MR this file

Read references/component.md when you need the full input table, every job with its rules, or what signing_from_ci and enable_coverage do under the hood.

Stages test, build, validate, release. Jobs:

  • testUnit runs on merge requests into dev_branch. Defaults to test<dev_variant>UnitTest; widen with test_task. With enable_coverage: true it appends koverXmlReportCustom koverPrintCoverageCustom to the same Gradle invocation and reports the line percentage on the MR.
  • bundleDev and bundleProd build .aab files on push to dev_branch and prod_branch.
  • updraftDev and updraftProd upload on push to dev_branch; updraftDevManual and updraftProdManual are manual buttons on prod_branch.
  • publishStore is a manual publish<prod_variant>Bundle on prod_branch.
  • sbom runs cyclonedxBom in validate, and the included track-vulntracker-upload component uploads the JSON in release.
  • secret_detection runs GitLab’s built-in template on every pipeline unless enable_secret_detection: false.

All build, Updraft, and publish jobs decode the release keystore from CI variables when signing_from_ci: true.

Use askUserQuestionTool for a multi-select: Kover coverage on MRs, CI-side release signing, Updraft dev, Updraft prod, Play Store publish, SBOM plus Vulnerability Tracker. The AWL KMP template enables coverage, signing, and SBOM by default, so propose those three as preselected.

Only ask about features the project cannot already do. A missing Updraft or CycloneDX plugin turns a flag into setup work, so say so.

Without a production branch the prod jobs simply never fire. No action needed.

1. Map flavors and build types to variants

Section titled “1. Map flavors and build types to variants”

The component drives Gradle by variant name (bundle<Variant>, updraftBundle<Variant>, publish<Variant>Bundle). Variant is <Flavor><BuildType> in upper camel case.

Read productFlavors and buildTypes in the app module (androidApp/build.gradle.kts for KMP, app/build.gradle.kts for a plain app):

Project module_name dev_variant prod_variant test_task
KMP template, flavors dev/prod androidApp DevDebug ProdRelease "testDevDebugUnitTest :shared:testAndroidHostTest"
Plain app, no flavors app Debug Release leave empty
Flavors stage/prod app StageDebug ProdRelease leave empty

The KMP shared module has no bare test task under the AGP KMP library plugin, so list :shared:testAndroidHostTest explicitly. Confirm the bundle tasks exist:

Terminal window
./gradlew :<module_name>:tasks --all --no-daemon | grep -iE "^bundle(<DevVariant>|<ProdVariant>) "

Also confirm the Gradle wrapper sits at the repo root and a runner tagged android-dev exists.

This is the KMP template’s file, minus the template-only when: never overrides:

include:
- component: $CI_SERVER_FQDN/devops/ci-cd-templates/android@main
inputs:
module_name: androidApp
dev_variant: DevDebug
prod_variant: ProdRelease
enable_sbom: true
enable_coverage: true
signing_from_ci: true
test_task: "testDevDebugUnitTest :shared:testAndroidHostTest"
# iOS in a KMP repo: lint on MRs, TestFlight on merge to main.
- component: $CI_SERVER_FQDN/devops/ci-cd-templates/ios-lint@main
- component: $CI_SERVER_FQDN/devops/ci-cd-templates/ios-fastlane@main
# Top-level `stages` does not merge across includes; the last component wins.
# Declare the union so Android (build/validate/release) and iOS (deploy/warm) jobs both have stages.
stages:
- test
- build
- validate
- release
- deploy
- warm

Drop the two iOS includes and the deploy and warm stages for an Android-only app. Keep the explicit stages block whenever more than one component is included. The iOS side (Tuist, fastlane lanes, xcov coverage) belongs to the ios-lint and ios-fastlane components; this skill does not configure it.

Add enable_updraft_dev, enable_updraft_prod, or enable_publish_store only when the user opted in. Pin @main to a tag if the templates repo publishes one; reproducible pipelines beat floating includes.

With signing_from_ci: true the component decodes RELEASE_KEYSTORE to $CI_PROJECT_DIR/release-keystore.jks and exports RELEASE_STORE_FILE before Gradle runs. The Gradle signing config therefore has to read RELEASE_STORE_FILE, RELEASE_KEY_ALIAS, RELEASE_STORE_PASSWORD, and RELEASE_KEY_PASSWORD from the environment, with keystore.properties as the local fallback. Gradle snippet, variable table, 1Password flow, and versionCode handling: references/signing.md.

  • Coverage: needs the Kover custom variant in every module so koverPrintCoverageCustom and koverXmlReportCustom exist. Use the code-coverage skill.
  • SBOM: needs the CycloneDX plugin in the root build and a cyclonedxBom task. See references/sbom.md.
  • Updraft: needs the Updraft plugin with per-variant URLs read from UPDRAFT_URL_DEV and UPDRAFT_URL_PROD. Use the updraft-android plugin integration skill.
  • Play Store: needs a publish<prod_variant>Bundle task (Gradle Play Publisher) and service-account credentials. Use the play-console-service-account skill.
  • bundle<DevVariant> and bundle<ProdVariant> resolve (step 1).
  • With coverage: ./gradlew koverPrintCoverageCustom prints application line coverage for each module.
  • With SBOM: ./gradlew cyclonedxBom --no-daemon writes build/reports/project-sbom.json.
  • With signing: the four RELEASE_* variables exist, protected and masked, and RELEASE_KEYSTORE is a file variable.
  • Open an MR into dev_branch and confirm testUnit and secret_detection run. Push to dev_branch and confirm bundleDev, bundleProd, and, if enabled, sbom plus the vulntracker upload run.