From a0b3f2a3855d809da9afbde3c3fc5b00f022a832 Mon Sep 17 00:00:00 2001 From: YJack0000 Date: Thu, 20 Aug 2026 17:04:35 +0800 Subject: [PATCH] [feature] the Play track is configuration, and the release goes out on the tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things stood between `git tag` and a build reaching users. **The service account had no Play access, and the documented way to give it some no longer exists.** `Setup → API access` redirects to the app list; there is no "Release manager" role. Google folded service accounts into ordinary user management and roles into individual permissions, so the path is now Users and permissions → Invite new user, with the service account's email as the user. Granted on 2026-08-20, scoped to Parley alone: view app information, release to testing tracks, release to production. No Cloud-project link is involved anywhere in that flow, which is most of what the old §7 was about. **The track was hard-coded to `internal`.** It is now the `PLAY_TRACK` repository variable, defaulting to `internal`, with a `track` input to override one run. Moving the whole pipeline to production becomes one variable — no commit, no release cut to change a constant. `PLAY_USER_FRACTION` turns a production release into a staged rollout, which is the only undo Play offers once a build is live. The default stays `internal` rather than `production` because production is not reachable yet: the app dashboard reads 6 of 11 setup tasks done, and Play refuses a production release until content rating, data safety, target audience, login details and the app category are filled in. Those are declarations about the app rather than build config, so no amount of CI makes them go away. `versionCode` goes to 2. The first bundle was uploaded by hand on 2026-08-18 as `0.1.0 (1)`, which both clears the first-bundle-cannot-use-the-API problem and burns `versionCode` 1 — `android-v0.1.0` could never have published over the API, whatever else was fixed. --- .github/workflows/android-release.yml | 46 +++++++++++++++++-- android/RELEASING.md | 65 ++++++++++++++++++--------- android/app/build.gradle.kts | 4 +- 3 files changed, 89 insertions(+), 26 deletions(-) diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 260032d..588c0c0 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -43,6 +43,14 @@ on: description: "Existing tag to build, for example android-v0.1.0" required: true type: string + track: + description: >- + Play track to publish to. Overrides the PLAY_TRACK repository + variable for this run only. + required: false + default: "" + type: choice + options: ["", "internal", "alpha", "beta", "production"] skip_play_upload: description: >- Build and publish the GitHub release but do NOT upload to Play. @@ -215,7 +223,38 @@ jobs: workload_identity_provider: ${{ vars.PLAY_WIF_PROVIDER }} service_account: ${{ vars.PLAY_PUBLISHER_SERVICE_ACCOUNT }} - - name: Upload to the Play internal track + # Which track a tag ships to is configuration, not code: `PLAY_TRACK` moves + # the whole pipeline from testing to production without touching this file + # or cutting a release to change it. It stays `internal` until the app has + # actually launched — Play refuses a production release while the store + # listing, content rating, data safety and target-audience forms are + # outstanding, so defaulting to production would fail every release until + # someone finished them. + # + # `PLAY_USER_FRACTION` (e.g. 0.1) turns a production release into a staged + # rollout, which is the only undo Play offers once a build is live. + - name: Decide the track + id: play + if: ${{ !inputs.skip_play_upload }} + run: | + TRACK="${{ inputs.track }}" + [ -n "$TRACK" ] || TRACK="${{ vars.PLAY_TRACK }}" + [ -n "$TRACK" ] || TRACK="internal" + FRACTION="${{ vars.PLAY_USER_FRACTION }}" + if [ "$TRACK" = "production" ] && [ -n "$FRACTION" ]; then + STATUS="inProgress" + else + STATUS="completed" + FRACTION="" + fi + echo "track=$TRACK" >> "$GITHUB_OUTPUT" + echo "status=$STATUS" >> "$GITHUB_OUTPUT" + echo "user_fraction=$FRACTION" >> "$GITHUB_OUTPUT" + echo "### Play release" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Track \`$TRACK\`, status \`$STATUS\`${FRACTION:+, rolling out to $FRACTION of users}." >> "$GITHUB_STEP_SUMMARY" + + - name: Upload to Play if: ${{ !inputs.skip_play_upload }} uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb # v1.1.3 with: @@ -226,8 +265,9 @@ jobs: packageName: com.pathors.parley releaseFiles: android/app/build/outputs/bundle/release/app-release.aab mappingFile: android/app/build/outputs/mapping/release/mapping.txt - track: internal - status: completed + track: ${{ steps.play.outputs.track }} + status: ${{ steps.play.outputs.status }} + userFraction: ${{ steps.play.outputs.user_fraction }} # Deliberately loud. The run is green, so the summary is the only place # that says this build is not on Play yet. diff --git a/android/RELEASING.md b/android/RELEASING.md index f089165..df8ff31 100644 --- a/android/RELEASING.md +++ b/android/RELEASING.md @@ -226,26 +226,31 @@ release exists, and nobody finds out until someone asks where the build went. The check runs immediately after checkout, so a misconfigured release costs seconds and leaves no half-made release behind. -#### The two steps with no API +#### Granting the service account access (done 2026-08-20) -Linking a Cloud project to a Play account is what *enables* the Play Developer -API, so it cannot go through the API it enables. +**There is no "API access" page and no "Release manager" role any more.** Older +guides — including earlier versions of this one — send you to +`Setup → API access`, which now redirects to the app list. Google folded service +accounts into ordinary user management, and roles into individual permissions. -1. Play Console → **Setup → API access** → link the Cloud project `pathors-play`. -2. Same page, under *Service accounts*, - `parley-play-publisher@pathors-play.iam.gserviceaccount.com` appears → - **Grant access** → **Release manager** (upload and release to testing tracks - and production, but no access to payments or account settings) → restrict to - Parley under *App permissions* → **Invite user**. +The current path is **Users and permissions → Invite new user**, with the +service account's email as the user: -Permissions take minutes to hours to propagate on a fresh account. A -`403 The caller does not have permission` on the first run is usually just that, -not a misconfiguration. +1. Play Console → **使用者和權限 / Users and permissions** → **邀請新的使用者 / + Invite new user** +2. Email: `parley-play-publisher@pathors-play.iam.gserviceaccount.com` +3. **App permissions** tab (not Account permissions — account permissions apply + to every app in the developer account) → add **Parley** → grant: + - 查看應用程式資訊 (唯讀) / View app information — the read access every + other permission is layered on + - 將應用程式發布至測試群組 / Release to testing tracks + - 發布正式版… / Release to production +4. **Invite user** -**The first bundle for a package cannot go through the API at all** — Play -refuses until the app has a release created in the console. Run the workflow -with `skip_play_upload=true`, upload that `.aab` by hand once, and every release -after it goes through the API. +No Cloud-project link is involved anywhere in this flow. + +Permissions can take minutes to propagate. A `403 The caller does not have +permission` on the first run is usually just that, not a misconfiguration. ### 8. Dependency verification @@ -313,11 +318,29 @@ are easy to get wrong: ``` Verify: sign-in deep link, live meeting, file import, upload queue drain. -5. **The first bundle only.** Play refuses a package's first bundle over the - API, so run the workflow manually with `skip_play_upload=true`, then - Play Console → Release → (track) → Create new release → upload the `.aab` - from the GitHub release, release notes (en + zh-TW), review, roll out. Every - release after that goes through the API and needs no console step. +5. **Which track a tag ships to is configuration, not code.** The repository + variable `PLAY_TRACK` (default `internal`) decides; a `workflow_dispatch` + run can override it with the `track` input for one run. Moving the whole + pipeline to production is therefore one variable, with no commit and no + release cut to change it. + + `PLAY_USER_FRACTION` (e.g. `0.1`) turns a production release into a staged + rollout — worth having, because it is the only undo Play offers once a build + is live. + + **`PLAY_TRACK` stays `internal` until the app has actually launched.** Play + refuses a production release while the store listing, content rating, data + safety and target-audience forms are outstanding, so pointing at production + early would fail every release until someone finished them. As of 2026-08-20 + the app dashboard reads *已完成 6 項,共 11 項*; the five outstanding ones are + 登入詳細資料, 內容分級, 目標對象, 資料安全性, and the app category/contact + details. They are declarations about the app, not build config — nobody can + automate them, and the prepared answers are in `AppStore/`. + + The first bundle for the package was uploaded by hand on 2026-08-18 + (`0.1.0 (1)`, internal track), so the API can take over from `versionCode` + 2 onward. Play rejects a `versionCode` it has already seen, which is why + `android-v0.1.0` could never have been published over the API. 6. Keep the R8 `mapping.txt`. Every workflow run attaches it as an artifact and the Play upload step sends it along, so crash reports deobfuscate; a stack trace from a build whose mapping was lost is unreadable. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index c3f6888..60a2a9b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -84,8 +84,8 @@ android { // minSdk 29: MediaMuxer OGG output + MediaCodec Opus encoder both require API 29. minSdk = 29 targetSdk = 35 - versionCode = 1 - versionName = "0.1.0" + versionCode = 2 + versionName = "0.1.1" } signingConfigs {