|
| 1 | +# Publishes Bloom Reader to the Google Play Console (internal track). |
| 2 | +# |
| 3 | +# - Pushes to `master` -> alpha flavor -> internal track of the "BR Alpha" app |
| 4 | +# - Pushes to `release` -> production flavor -> internal track of the "Bloom Reader" app |
| 5 | +# |
| 6 | +# (The Play track is set to "internal" in app/build.gradle's `play {}` block; |
| 7 | +# promotion to other tracks is done manually in the Play Console or via the |
| 8 | +# promote*Artifact gradle tasks.) |
| 9 | +# |
| 10 | +# Required repository secrets: |
| 11 | +# KEYSTORE_BASE64 base64 of keystore_bloom_reader.keystore |
| 12 | +# (e.g. `base64 -w0 keystore_bloom_reader.keystore`) |
| 13 | +# KEYSTORE_STORE_PASSWORD storePassword from keystore_bloom_reader.properties |
| 14 | +# KEYSTORE_KEY_ALIAS keyAlias from keystore_bloom_reader.properties |
| 15 | +# KEYSTORE_KEY_PASSWORD keyPassword from keystore_bloom_reader.properties |
| 16 | +# PLAY_SERVICE_ACCOUNT_JSON contents of the Google Play service account json file |
| 17 | +# |
| 18 | +# Optional repository variable: |
| 19 | +# PLAY_DRY_RUN set to "true" to build, sign, and upload to a Play |
| 20 | +# edit WITHOUT committing it — nothing becomes |
| 21 | +# visible in the Play Console. Use while verifying |
| 22 | +# this workflow; delete the variable to go live. |
| 23 | +# |
| 24 | +# Versioning: the patch number (3.4.NNN) is the number of commits since |
| 25 | +# versionMajor/versionMinor last changed in app/build.gradle, so it resets to 0 |
| 26 | +# automatically when the version is bumped, and master (alpha) and release |
| 27 | +# (production) count independently. NOTE: build.gradle computes versionCode = |
| 28 | +# major*100000 + minor*1000 + patch, so patch must stay below 1000. |
| 29 | + |
| 30 | +name: Publish to Play Console |
| 31 | + |
| 32 | +on: |
| 33 | + push: |
| 34 | + # TEMP: play-publish-gha is included only to test this workflow before |
| 35 | + # merging; remove it (revert this commit) before merging to master. |
| 36 | + branches: [master, release, play-publish-gha] |
| 37 | + workflow_dispatch: |
| 38 | + inputs: |
| 39 | + flavor: |
| 40 | + description: "Flavor to publish (both go to the internal track)" |
| 41 | + type: choice |
| 42 | + options: [Alpha, Production] |
| 43 | + default: Alpha |
| 44 | + patch: |
| 45 | + description: "Override the patch number (default: commits since the last version bump, plus the +77 legacy alpha offset)" |
| 46 | + type: string |
| 47 | + required: false |
| 48 | + default: "" |
| 49 | + |
| 50 | +# Never run two publishes at once; Play edits would race. |
| 51 | +concurrency: |
| 52 | + group: play-publish |
| 53 | + cancel-in-progress: false |
| 54 | + |
| 55 | +# contents: write lets the workflow push the release tag. |
| 56 | +permissions: |
| 57 | + contents: write |
| 58 | + |
| 59 | +jobs: |
| 60 | + publish: |
| 61 | + runs-on: ubuntu-latest |
| 62 | + steps: |
| 63 | + - uses: actions/checkout@v7 |
| 64 | + with: |
| 65 | + fetch-depth: 0 # full history: the patch number is derived from it |
| 66 | + fetch-tags: true # the tag step checks for existing release tags |
| 67 | + |
| 68 | + - name: Determine flavor and build number |
| 69 | + id: config |
| 70 | + env: |
| 71 | + PATCH_INPUT: ${{ inputs.patch }} |
| 72 | + run: | |
| 73 | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then |
| 74 | + FLAVOR="${{ inputs.flavor }}" |
| 75 | + elif [ "${{ github.ref }}" = "refs/heads/release" ]; then |
| 76 | + FLAVOR="Production" |
| 77 | + else |
| 78 | + FLAVOR="Alpha" |
| 79 | + fi |
| 80 | + # A real publish must run from the flavor's own branch: the two |
| 81 | + # branches have different commit counts, so publishing a flavor |
| 82 | + # from the wrong branch corrupts that app's version numbering. |
| 83 | + # Dry runs commit nothing to Play and may run from any branch. |
| 84 | + if [ "${{ vars.PLAY_DRY_RUN }}" != "true" ]; then |
| 85 | + if [ "$FLAVOR" = "Production" ] && [ "${{ github.ref }}" != "refs/heads/release" ]; then |
| 86 | + echo "::error::A real Production publish must run from the release branch." |
| 87 | + exit 1 |
| 88 | + fi |
| 89 | + if [ "$FLAVOR" = "Alpha" ] && [ "${{ github.ref }}" = "refs/heads/release" ]; then |
| 90 | + echo "::error::A real Alpha publish must not run from the release branch." |
| 91 | + exit 1 |
| 92 | + fi |
| 93 | + fi |
| 94 | + # The patch number counts commits since the last version bump (the |
| 95 | + # last commit that changed the versionMajor/versionMinor lines in |
| 96 | + # app/build.gradle), so bumping the version resets it to 0 on its |
| 97 | + # own, and master (alpha) and release (production) count |
| 98 | + # independently on their own branches. |
| 99 | + if [ -n "$PATCH_INPUT" ]; then |
| 100 | + if ! [[ "$PATCH_INPUT" =~ ^[0-9]+$ ]]; then |
| 101 | + echo "::error::patch must be a plain number, got: $PATCH_INPUT" |
| 102 | + exit 1 |
| 103 | + fi |
| 104 | + BUILD_NUMBER="$PATCH_INPUT" |
| 105 | + else |
| 106 | + BUMP_COMMIT=$(git log -1 --format=%H -G'^def version(Major|Minor)' -- app/build.gradle) |
| 107 | + BUILD_NUMBER=$(git rev-list --count "$BUMP_COMMIT..HEAD") |
| 108 | + # Continuity with the old TeamCity build counter (alpha was at |
| 109 | + # 3.4.103 when this workflow took over). Keyed to the 3.4 bump |
| 110 | + # commit, so it expires by itself: the next version bump becomes |
| 111 | + # the new BUMP_COMMIT and this no longer applies. |
| 112 | + if [ "$BUMP_COMMIT" = "06dc6358a9a2ea410d5ce9bf6b39474177461618" ] && [ "$FLAVOR" = "Alpha" ]; then |
| 113 | + BUILD_NUMBER=$(( BUILD_NUMBER + 77 )) |
| 114 | + fi |
| 115 | + fi |
| 116 | + if [ "$BUILD_NUMBER" -gt 999 ]; then |
| 117 | + echo "::error::patch $BUILD_NUMBER would overflow into the minor-version digits of versionCode" |
| 118 | + exit 1 |
| 119 | + fi |
| 120 | + # Gradle task lists mirror the TeamCity builds ("clean" omitted: |
| 121 | + # the build dir doesn't exist on a fresh runner), plus the publish |
| 122 | + # step. TC's production build stops at assemble (publishing was a |
| 123 | + # separate manual step); here publishProductionRelease assembles |
| 124 | + # and publishes to the internal track. |
| 125 | + if [ "$FLAVOR" = "Alpha" ]; then |
| 126 | + TASKS="build publishAlphaRelease promoteAlphaReleaseArtifact" |
| 127 | + else |
| 128 | + TASKS="lintProductionRelease testProductionReleaseUnitTest publishProductionRelease" |
| 129 | + fi |
| 130 | + MAJOR=$(sed -n 's/^def versionMajor = \([0-9]*\).*/\1/p' app/build.gradle) |
| 131 | + MINOR=$(sed -n 's/^def versionMinor = \([0-9]*\).*/\1/p' app/build.gradle) |
| 132 | + echo "flavor=$FLAVOR" >> "$GITHUB_OUTPUT" |
| 133 | + echo "build_number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT" |
| 134 | + echo "version=$MAJOR.$MINOR.$BUILD_NUMBER" >> "$GITHUB_OUTPUT" |
| 135 | + echo "tasks=$TASKS" >> "$GITHUB_OUTPUT" |
| 136 | + echo "Publishing flavor $FLAVOR version $MAJOR.$MINOR.$BUILD_NUMBER (tasks: $TASKS)" |
| 137 | +
|
| 138 | + - uses: actions/setup-java@v5 |
| 139 | + with: |
| 140 | + distribution: temurin |
| 141 | + java-version: 17 |
| 142 | + |
| 143 | + - uses: gradle/actions/setup-gradle@v6 |
| 144 | + |
| 145 | + - uses: actions/setup-node@v6 |
| 146 | + with: |
| 147 | + node-version: 20 |
| 148 | + cache: yarn |
| 149 | + cache-dependency-path: app/yarn.lock |
| 150 | + |
| 151 | + - name: Install bloom-player |
| 152 | + working-directory: app |
| 153 | + run: yarn install --frozen-lockfile |
| 154 | + |
| 155 | + # Alpha builds always ship the latest alpha of bloom-player; |
| 156 | + # production builds use the version locked in yarn.lock. |
| 157 | + - name: Upgrade to latest bloom-player alpha |
| 158 | + if: steps.config.outputs.flavor == 'Alpha' |
| 159 | + working-directory: app |
| 160 | + run: yarn upgrade bloom-player@alpha |
| 161 | + |
| 162 | + - name: Set up signing and Play credentials |
| 163 | + env: |
| 164 | + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} |
| 165 | + KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} |
| 166 | + KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} |
| 167 | + KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} |
| 168 | + PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} |
| 169 | + run: | |
| 170 | + # app/build.gradle reads ~/keystore/keystore_bloom_reader.properties |
| 171 | + mkdir -p "$HOME/keystore" |
| 172 | + echo "$KEYSTORE_BASE64" | base64 -d > "$HOME/keystore/keystore_bloom_reader.keystore" |
| 173 | + printf '%s' "$PLAY_SERVICE_ACCOUNT_JSON" > "$HOME/keystore/play-service-account.json" |
| 174 | + cat > "$HOME/keystore/keystore_bloom_reader.properties" <<EOF |
| 175 | + storeFile=$HOME/keystore/keystore_bloom_reader.keystore |
| 176 | + storePassword=$KEYSTORE_STORE_PASSWORD |
| 177 | + keyAlias=$KEYSTORE_KEY_ALIAS |
| 178 | + keyPassword=$KEYSTORE_KEY_PASSWORD |
| 179 | + serviceAccountJsonFile=$HOME/keystore/play-service-account.json |
| 180 | + EOF |
| 181 | +
|
| 182 | + - name: Copy bloom-player assets |
| 183 | + run: ./gradlew copyBloomPlayerAssets |
| 184 | + |
| 185 | + - name: Build and publish to internal track |
| 186 | + run: > |
| 187 | + ./gradlew ${{ steps.config.outputs.tasks }} |
| 188 | + "-Pbuild.number=${{ steps.config.outputs.build_number }}" |
| 189 | + "-PplayDryRun=${{ vars.PLAY_DRY_RUN || 'false' }}" |
| 190 | +
|
| 191 | + # Only reached if the publish above succeeded. |
| 192 | + - name: Tag the released commit |
| 193 | + if: steps.config.outputs.flavor == 'Production' && vars.PLAY_DRY_RUN != 'true' |
| 194 | + run: | |
| 195 | + TAG="v${{ steps.config.outputs.version }}" |
| 196 | + if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then |
| 197 | + if [ "$(git rev-parse "refs/tags/$TAG^{commit}")" = "$(git rev-parse HEAD)" ]; then |
| 198 | + echo "Tag $TAG already exists on this commit (re-run); nothing to do." |
| 199 | + exit 0 |
| 200 | + fi |
| 201 | + echo "::error::Tag $TAG already exists on a different commit." |
| 202 | + exit 1 |
| 203 | + fi |
| 204 | + git tag "$TAG" |
| 205 | + git push origin "$TAG" |
| 206 | +
|
| 207 | + - name: Clean up credentials |
| 208 | + if: always() |
| 209 | + run: rm -rf "$HOME/keystore" |
| 210 | + |
| 211 | + - name: Upload APK artifact |
| 212 | + uses: actions/upload-artifact@v7 |
| 213 | + with: |
| 214 | + name: bloomreader-${{ steps.config.outputs.flavor }}-${{ steps.config.outputs.build_number }} |
| 215 | + path: app/build/outputs/apk/**/release/*.apk |
| 216 | + if-no-files-found: warn |
0 commit comments