feat: deploy test releases to Google Play testing tracks

Test releases now automatically upload to Google Play's internal and
closed testing tracks by default, matching what regular releases do.
An opt-out flag (--no-play) remains available when Google Play should be
skipped.
This commit is contained in:
Chmouel Boudjnah 2026-08-30 09:44:08 +02:00
parent 6c25f748e2
commit bef378281f
No known key found for this signature in database
GPG key ID: 8E00E7B02807E02D
3 changed files with 36 additions and 24 deletions

View file

@ -46,15 +46,16 @@ jobs:
version_code=$(sed -nE 's/^[[:space:]]*versionCode = ([0-9]+)$/\1/p' \
app/build.gradle.kts)
# A test release (hack/release --test) is built and signed like
# any other; what it skips is the two stores. It updates no
# listing, so it carries no changelog, and nothing below asks
# it for one.
# any other; what it skips is F-Droid. It updates no
# F-Droid listing, so it carries no F-Droid changelog, and
# nothing below asks it for one.
if [[ "$version" == *-test.* ]]; then
printf 'prerelease=true\n' >> "$GITHUB_OUTPUT"
else
printf 'prerelease=false\n' >> "$GITHUB_OUTPUT"
test -s "fastlane/metadata/android/en-US/changelogs/$version_code.txt"
fi
printf 'version_code=%s\n' "$version_code" >> "$GITHUB_OUTPUT"
- name: Configure release signing
env:
@ -113,8 +114,8 @@ jobs:
printf 'A test build. It carries the release signature, so it\n'
printf 'installs over a copy that came from F-Droid, and F-Droid\n'
printf 'updates over it once the next real release reaches it.\n\n'
printf 'It is not on F-Droid or Google Play, and it is not the\n'
printf 'version to install if you are not looking for this one.\n\n'
printf 'It is on Google Play testing tracks (Internal/Testing) and\n'
printf 'GitHub prerelease, but not on F-Droid or Google Play production.\n\n'
printf '### Commits%s\n\n' "${previous:+ since $previous}"
git log --no-decorate --pretty='- %s' \
--invert-grep --grep='^chore: test release ' "$range"
@ -155,14 +156,10 @@ jobs:
- name: Check for the Play credential
id: play
env:
PRERELEASE: ${{ steps.version.outputs.prerelease }}
SERVICE_ACCOUNT: ${{ secrets.LISEUR_PLAY_SERVICE_ACCOUNT_JSON }}
run: |
set -euo pipefail
if [[ "$PRERELEASE" == true ]]; then
printf 'enabled=false\n' >> "$GITHUB_OUTPUT"
printf 'A test release; skipping Google Play.\n'
elif [[ -n "$SERVICE_ACCOUNT" ]]; then
if [[ -n "$SERVICE_ACCOUNT" ]]; then
printf 'enabled=true\n' >> "$GITHUB_OUTPUT"
else
printf 'enabled=false\n' >> "$GITHUB_OUTPUT"
@ -183,7 +180,10 @@ jobs:
if: steps.play.outputs.enabled == 'true'
continue-on-error: true
env:
PRERELEASE: ${{ steps.version.outputs.prerelease }}
RELEASE_TAG: ${{ github.ref_name }}
SERVICE_ACCOUNT: ${{ secrets.LISEUR_PLAY_SERVICE_ACCOUNT_JSON }}
VERSION_CODE: ${{ steps.version.outputs.version_code }}
run: |
set -euo pipefail
key="$RUNNER_TEMP/play-service-account.json"
@ -191,5 +191,9 @@ jobs:
# outlive this step whatever fastlane makes of it.
trap 'rm -f "$key"' EXIT
(umask 077 && printf '%s' "$SERVICE_ACCOUNT" > "$key")
changelog="fastlane/metadata/android/en-US/changelogs/$VERSION_CODE.txt"
if [[ "$PRERELEASE" == true && ! -f "$changelog" ]]; then
printf 'Test release %s\n' "$RELEASE_TAG" > "$changelog"
fi
gem install fastlane -v 2.238.0 --no-document
SUPPLY_JSON_KEY="$key" fastlane android internal

View file

@ -227,12 +227,14 @@ where a handful of people can install it from and nowhere else:
```bash
hack/release --test # the next patch: v0.9.4-test.1, then .2
hack/release --test 0.10.0 # a version you are working towards
hack/release --no-play --test # skip Google Play testing tracks
```
It publishes a GitHub **prerelease** with the signed APK attached, and
stops there: no F-Droid merge request, no Play upload, no changelog to
It publishes a GitHub **prerelease** with the signed APK attached, uploads
the app bundle to Google Play's **internal** and **closed** (`Testing`)
tracks, and stops there: no F-Droid merge request, no changelog to
write, and no local test, lint or reproducibility run. The point of a
test release is to be quick.
test release is to be quick. Pass `--no-play` to leave Google Play out.
The signature is what makes it worth doing. It is the one F-Droid
publishes under, through the dual-signing flow, so the APK installs

View file

@ -41,7 +41,7 @@ usage() {
Usage:
hack/release [--no-fdroid] [--no-play]
hack/release [-n] [--no-fdroid] [--no-play] VERSION RELEASE NOTES...
hack/release [-n] --test [VERSION]
hack/release [-n] [--no-play] --test [VERSION]
hack/release --fdroid-only VERSION
hack/release [--no-play] --sync-secrets
@ -56,6 +56,7 @@ Examples:
hack/release --no-play 0.2.2 "Leave Google Play out of this one."
hack/release --test
hack/release -n --test 0.3.0
hack/release --no-play --test 0.3.0
hack/release --fdroid-only 0.2.1
hack/release --sync-secrets
@ -67,12 +68,13 @@ answer that has no default: picking a version and writing a changelog
still need a terminal, so a run with no VERSION refuses either way.
--test builds the same signed APK and publishes it as a GitHub
prerelease, without telling F-Droid or Google Play about it. Because it
prerelease and uploads it to the Google Play testing tracks (unless
--no-play is given), without telling F-Droid about it. Because it
carries the release signature, it installs over a copy that came from
F-Droid, and F-Droid updates over it once a real release goes out above
its versionCode. VERSION is the version being worked towards (the next
patch by default) and the tag numbers the attempts: v0.3.0-test.1, then
v0.3.0-test.2. The version bump is committed but never pushed to a
F-Droid or Google Play, and F-Droid updates over it once a real release
goes out above its versionCode. VERSION is the version being worked towards
(the next patch by default) and the tag numbers the attempts: v0.3.0-test.1,
then v0.3.0-test.2. The version bump is committed but never pushed to a
branch — only the tag that points at it — so main keeps a history of
real releases. There is no changelog to write and no local test, lint or
reproducibility run: the point of a test release is to be quick.
@ -509,7 +511,12 @@ create_test_release() {
"$version" "$version_code" "$(git rev-parse --short HEAD)" \
"$(git branch --show-current || printf 'detached')"
printf 'It is signed with the release key and published as a GitHub\n'
printf 'prerelease. F-Droid and Google Play are not told about it.\n'
if [[ "$skip_play" == false ]]; then
printf 'prerelease, and uploaded to the Google Play testing tracks.\n'
printf 'F-Droid is not told about it.\n'
else
printf 'prerelease. F-Droid and Google Play are not told about it.\n'
fi
confirm
sync_release_secrets
@ -951,14 +958,13 @@ if [[ "$sync_secrets_only" == true ]]; then
exit 0
fi
# A test release goes nowhere near F-Droid or Play, so it needs neither
# their tooling nor their credentials, and none of the checks that keep
# main a straight line of releases apply to a tag that is not on it.
# A test release goes nowhere near F-Droid, so it needs none of its
# tooling or credentials, and none of the checks that keep main a
# straight line of releases apply to a tag that is not on it.
if [[ "$test_release" == true ]]; then
[[ "$fdroid_only" == false ]] ||
die "--test and --fdroid-only are mutually exclusive"
[[ $# -le 1 ]] || die "--test takes at most a version"
skip_play=true
for command in git gh jq sed awk pass curl python3; do
require_command "$command"