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 |
|
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 |
|
Only the manual |
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
-
Publish the reviewed shared-repository commit first. A full commit SHA is an immutable pin; creating a new template tag is optional.
-
Set both
refandtemplate_revisionto that resolved SHA in all four Treeger include-only adapters, then bring the consumer configuration onto protectedmain. -
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.propertiesand keystore are present. -
GitLab downloads the
treeger-upload.jksSecure File for a release job and creates an ephemeralkey.propertiesthat points to it. -
GitLab variables
ANDROID_KEYSTORE_PASSWORD,ANDROID_KEY_ALIAS, andANDROID_KEY_PASSWORDprovide 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_DATAto 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
-
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.
-
Open IAM & Admin > Service Accounts, create a service account, and create/download its JSON key. Keep the downloaded JSON out of the repository.
-
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. -
In Play Console, create the
dev.sysout.treegerapp if necessary, complete its required app setup, configure Internal testing and the closed Alpha track (API namealpha), and add their tester lists. Before the first Fastlane run, bootstrap a new Play app with its first signed AAB through Play Console. -
In GitLab, open Settings > CI/CD > Secure Files and upload the keystore using its retained filename
treeger-upload.jks. -
In Settings > CI/CD > Variables, add protected and masked variables
ANDROID_KEYSTORE_PASSWORD,ANDROID_KEY_ALIAS, andANDROID_KEY_PASSWORD. -
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. -
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. -
In Settings > Repository > Protected branches, protect the default branch (
main) from which releases are permitted. In Protected tags, protectv*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
-
Go to Build > Pipelines and select New pipeline.
-
In Run for branch name or tag, choose the protected default branch (
main) containing the already-prepared release commit. -
Leave
PIPELINE_ACTIONset torelease. -
Enter
RELEASE_TAGin the prefilled variable field. It must exactly match the currentversionNamewith avprefix, for examplev1.7forversionName "1.7". -
Select New pipeline. Lint, unit tests, and a debug build run against the exact source commit. The pipeline’s
workflow:rulesrejects every tag pipeline. -
After those checks pass, start the blocking
create_release_tagmanual job. It validates the tag format and the resolved Gradle releaseversionName, creates an annotated tag atCI_COMMIT_SHA, and pushes only that tag withCI_JOB_TOKEN. -
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_testingjob 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
-
Commit the intended English listing text under
fastlane/metadata/android/en-USand artwork underdocs/play-store, then bring those changes onto protectedmain. -
Start a new web pipeline on
mainwithPIPELINE_ACTION=listingand leaveRELEASE_TAGempty. -
Start its manual
sync_play_listingjob.
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.