Skip to content
sysout.dev

Google Play delivery

The secure GitLab CI/CD and Google Play release process for treeger.

This runbook describes the repository, GitLab, and Play Console responsibilities for releasing treeger and maintaining its store listing. Regular releases publish the same signed bundle to Internal testing and closed Alpha together. Store-listing updates use an independent manual pipeline. Production promotion remains a separate Play Console action.

Pipeline modes

Mode Inputs Result

Regular release

PIPELINE_ACTION=release, RELEASE_TAG=v<versionName>

Verify, approve the release tag, build and verify a signed AAB, then publish that bundle and its version-specific release notes to Internal and closed Alpha in one Play edit.

Store listing

PIPELINE_ACTION=listing, empty RELEASE_TAG

Only the manual sync_play_listing job: update store text, icon, feature graphic, and managed screenshots from the repository.

Listing mode has no release-job dependency, Android build, or release tag. Regular release pipelines do not contain sync_play_listing. Listing mode is accepted only for a web pipeline on protected main, with RELEASE_TAG empty.

Adopt a shared-template update

  1. Publish the reviewed shared-repository commit first. A full commit SHA is an immutable pin; creating a new template tag is optional.

  2. Set both ref and template_revision to that resolved SHA in all four Treeger include-only adapters, then bring the consumer configuration onto protected main.

  3. Start a new pipeline for the intended mode. Retrying an existing job keeps its original source commit and saved CI configuration, including its old template pin.

The shared commit must be available in GitLab before Treeger can resolve its includes. There are no local shared-job overrides or copied executable publishing helpers in Treeger.

Source of truth

The long-form product release notes at product/releases/<version>.adoc are the canonical account of a release. Google Play also requires a concise, version-code-specific What’s new payload. That derived delivery field is stored at:

fastlane/metadata/android/en-US/changelogs/<versionCode>.txt

For example, treeger 1.8 has versionCode 9, so its Play text is changelogs/9.txt. Update the release note and this shorter Play field together as part of each release preparation.

The shared pipeline does not increment versionName or versionCode. Update both fields in app/build.gradle and commit them with the release notes before starting the release pipeline. Use a new version code greater than every version code already uploaded to Play; an uploaded code cannot be reused. The pipeline validates RELEASE_TAG against the committed versionName, then builds and uploads that commit’s version unchanged.

Before committing release preparation, build and verify the signed release APK and AAB, then install and launch that fresh release APK on the designated local AVD. Record the AVD, package, version, and result. Include an upgrade check from the previous signed release; passing debug tests alone does not validate the release APK.

The English title and descriptions live in fastlane/metadata/android/en-US. The Play icon, feature graphic, and phone screenshots remain under docs/play-store; the listing adapter supplies .gitlab/play-listing-assets.json to the shared template. That manifest maps those artwork files to Play metadata destinations. The shared job stages and uploads them. Treeger contains configuration and assets; executable CI publishing logic lives in sysout-gitlab-ci.

Build environment

Treeger uses its checked-in Gradle Wrapper (8.11.1) and JDK 17. The CI adapters pin mobiledevops/android-sdk-image@sha256:af970bbcf86bdbdb2ebb41ceb66aabcd388dd6bb4e43b6ee4e84c033cb210722; that Linux image supplies JDK 17, Android platform 36, Build Tools 36.1.0, platform-tools, Git, curl, and the image-provided Gradle distribution. Jobs deliberately invoke ./gradlew, so the checked-in 8.11.1 wrapper—not the image’s Gradle—is authoritative.

Local development must install platform 36 and Build Tools 36.1.0 and use JDK 17. The shared template checks the image’s SDK directories, JDK, wrapper, platform-tools, and release utilities before linting, tagging, signing, or publishing. It does not download or cache Android SDK packages at job runtime.

Credentials and signing

Neither the release keystore nor key.properties belongs in Git.

  • A developer can build a signed release locally only when their ignored key.properties and keystore are present.

  • GitLab downloads the treeger-upload.jks Secure File for a release job and creates an ephemeral key.properties that points to it.

  • GitLab variables ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and ANDROID_KEY_PASSWORD provide the remaining signing values. Mark them masked and protected. These generic names are the shared-template contract.

  • The Google Play integration supplies SUPPLY_JSON_KEY_DATA to Fastlane. Restrict it to protected branches and tags.

Each shared CI job first logs the immutable sysout-gitlab-ci revision that supplied it; the three verify job names (lint_debug, test_debug_unit_test, and assemble_debug) are supplied by that revision.

A fresh clone deliberately has no signing secret. A fresh protected GitLab runner has what it needs only after the Secure File, variables, and Google Play integration have been configured.

One-time service setup

  1. In Google Cloud Console, create or select the project that will own the release service account. Open APIs & Services > Library, find Google Play Developer API, and enable it.

  2. Open IAM & Admin > Service Accounts, create a service account, and create/download its JSON key. Keep the downloaded JSON out of the repository.

  3. In Play Console, open Users and permissions, invite the service-account email, and give it access to dev.sysout.treeger. Grant Release apps to testing tracks for Internal and closed Alpha releases, and Manage store presence for the independent listing workflow. Manage testing tracks and edit tester lists is needed when changing testing configuration or tester lists. Production-release permission is not required by either workflow.

  4. In Play Console, create the dev.sysout.treeger app if necessary, complete its required app setup, configure Internal testing and the closed Alpha track (API name alpha), and add their tester lists. Before the first Fastlane run, bootstrap a new Play app with its first signed AAB through Play Console.

  5. In GitLab, open Settings > CI/CD > Secure Files and upload the keystore using its retained filename treeger-upload.jks.

  6. In Settings > CI/CD > Variables, add protected and masked variables ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and ANDROID_KEY_PASSWORD.

  7. In Settings > Integrations > Google Play, activate the integration, set package name dev.sysout.treeger, upload the service-account JSON, and restrict it to protected branches and tags.

  8. In Settings > CI/CD > Job token permissions, enable Allow Git push requests to the repository. This permits a job from this project to push its release tag with CI_JOB_TOKEN; no project access token or tag API access is required.

  9. In Settings > Repository > Protected branches, protect the default branch (main) from which releases are permitted. In Protected tags, protect v* and allow the people authorized to run those protected-branch pipelines to create matching tags. A job token uses the permissions of the person who started its pipeline.

Perform a release

  1. Go to Build > Pipelines and select New pipeline.

  2. In Run for branch name or tag, choose the protected default branch (main) containing the already-prepared release commit.

  3. Leave PIPELINE_ACTION set to release.

  4. Enter RELEASE_TAG in the prefilled variable field. It must exactly match the current versionName with a v prefix, for example v1.7 for versionName "1.7".

  5. Select New pipeline. Lint, unit tests, and a debug build run against the exact source commit. The pipeline’s workflow:rules rejects every tag pipeline.

  6. After those checks pass, start the blocking create_release_tag manual job. It validates the tag format and the resolved Gradle release versionName, creates an annotated tag at CI_COMMIT_SHA, and pushes only that tag with CI_JOB_TOKEN.

  7. A CI job-token push does not start another pipeline. The same protected-branch pipeline, still checked out at the tagged SHA, then builds and validates the signed AAB. Its publish_play_testing job uploads the bundle once, checks the returned version code, prepares that version and its What’s new text for both Internal and closed Alpha, and commits the two track updates together. Store-listing text and artwork are untouched.

Both required tracks must already exist. A failure before the edit commits leaves both track updates uncommitted. The job attempts to discard that edit without masking the original error. If a commit response is lost, inspect Play before retrying. An identical existing bundle can be reused on retry only when its SHA-256 matches the current signed artifact. The version code returned by Play selects the matching release notes. Uploading different bytes with an already-used code is rejected by Play.

If any pre-tag check fails, no tag is created. If tag creation fails because the tag already exists, does not point at the pipeline SHA, or is not allowed by the protection rule, the pipeline stops before building or publishing.

The testing delivery follows explicit manual release-tag approval in a protected-default-branch pipeline. All four Treeger adapters include the same immutable shared-template revision. The shared publish template defaults to Internal plus Alpha, so Treeger supplies no Alpha opt-in. Treeger does not redefine shared jobs or maintain a publishing Fastfile. Existing consumers keep their current behavior until they adopt the new shared revision; the new default applies when they adopt it.

Update the store listing independently

  1. Commit the intended English listing text under fastlane/metadata/android/en-US and artwork under docs/play-store, then bring those changes onto protected main.

  2. Start a new web pipeline on main with PIPELINE_ACTION=listing and leave RELEASE_TAG empty.

  3. Start its manual sync_play_listing job.

The shared listing job uploads the English title, short and full descriptions, icon, feature graphic, and the four managed phone screenshots. Uploaded image sets replace the corresponding store images; ordered SHA-256 comparison avoids uploading unchanged image sets. The job calls the listing and image APIs directly, without reading or updating release tracks, uploading APKs/AABs, or changing release notes. After committing, it verifies the persisted text and ordered image checksums in a fresh Play edit and discards that verification edit. A mismatch fails the job. Play review and public-store propagation remain separate from this API read-back. The store listing applies to the app, rather than to a testing track.

Listing and testing-release jobs share one GitLab resource group so their Play edits cannot overlap within these workflows. The service account’s Manage store presence grant must cover dev.sysout.treeger.