[Android]GitHub Actions + gradle play publisher(GPP)でStoreに自動デプロイする

GitHub Actions + [gradle play publisher(GPP)](https://github.com/Triple-T/gradle-play-publisher)でStoreに自動デプロイする設定を説明する。
fastlaneなどでPlay Storeにデプロイした経験がある人向けの説明。初心者向けではない。
大体、公式のREADMEどおりに設定すれば良いが、少しだけハマりどころもある。

## 構成
アプリのソースコードはGitHub上に公開する。
第三者によるアプリのビルドは許可するが、Store上の自分のアプリを上書きする形でのアップロードは禁止する。

なので、keystore[^1]、keystoreのパスワードなどの情報は、リポジトリ管理対象のファイルには含めない。
Storeアップロードに使うGCPの認証情報もファイルに含めない。

Storeのアップロード先は内部テストトラックで、アップロード時点で完全公開とする。
アプリの形式はapkでなくaabとする。

## gradle play publisherの設定
### 事前設定
gradle play publisherのREADMEに書かれている設定をしておく。
https://github.com/Triple-T/gradle-play-publisher#prerequisites

Storeへの初回アップロード、署名用のkeystoreの準備、デプロイに使うGCPのサービスアカウントの設定。
GCPのサービスアカウントの認証用JSONは、環境変数ANDROID_PUBLISHER_CREDENTIALSに設定する方式を採用する。

### build.gradleの記述

```gradle:app/build.grale(関係のある部分を抜粋)

apply plugin: 'com.android.application'
apply plugin: 'com.github.triplet.play'

play {
    track = "internal"
    status = "completed"
    defaultToAppBundles = true
    enabled = (System.getenv("ANDROID_PUBLISHER_CREDENTIALS") != null)
}
```

trackとstausはデフォルト値のままなので本来は記述不要だが、いちいちデフォルト値を覚えていられないので記述する。

**enabledの設定が重要。**
GCPの認証情報が入った環境変数が未定義のときは、GPPを無効化するという設定。
この設定をしないと、普段の開発のときのビルドでもGPPのエラーが発生してビルドできない。


### PCからアプリをStoreにデプロイする

アプリのルートディレクトリから以下のコマンドを実行する。
設定が上手くいっていれば、Storeの内部テスト版にアップロードできる。

```shell
export ANDROID_PUBLISHER_CREDENTIALS=`cat <GCPのサービスアカウントの認証情報の入ったJSONファイルのパス>`
export STORE_PASSWORD=XXX
export KEY_ALIAS=XXX
export KEY_PASSWORD=XXX


./gradlew publishBundle \
            -Pandroid.injected.signing.store.file="<keystoreファイルのパス>" \
            -Pandroid.injected.signing.store.password=$STORE_PASSWORD \
            -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \
            -Pandroid.injected.signing.key.password=$KEY_PASSWORD
```

署名に必要な情報はbuild.gradleに記述するのではなく、コマンド実行時にプロジェクトプロパティとして渡すようにする。これは、CIを実行するときの布石。

## GitHub Actions + gradle play publisherでStoreにデプロイする
### GitHub ActionsのSecretsの定義

GitHubのリポジトリのページ -> Settings -> Serectsから以下を設定する。

- ANDROID_PUBLISHER_CREDENTIALS
  - GCPのサービスアカウントの認証情報のJSONファイル。テキストエディタで開いてコピペする。
- ENCODED_KEYSTORE
  - keystoreファイルをbase64でencodeした値を設定する。
  - 「base64 -i <Keystoreファイルのパス>」で値を生成する。
- STORE_PASSWORD
  - keystoreのパスワード。
- KEY_ALIAS
  - keystoreのalias。セキュアな情報でない気がするが、Secretsにまとめた方が分かりやすいと思っている。
- KEY_PASSWORD
  - keystoreのkeyのパスワード。

### GitHub Actionsのワークフローの設定
GitHub上でprereleaseをしたら、PlayStoreにアップロードするように設定する。
タグ名の先頭には「vX.Y.Z」を付けるものとする。

```yaml:.github/workflows/pre-release.yml
name: pre-release

on:
  push:
    tags:
      - "v[0-9].[0-9]+.[0-9]+*"

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: set up JDK 11
        uses: actions/setup-java@v1
        with:
          java-version: 11
      - name: Cache Gradle Dependencies
        uses: actions/cache@v2
        env:
          cache-name: cache-gradle-depen
        with:
          path: ~/.gradle
          key: ${{ runner.os }}-${{ hashFiles('build.gradle') }}-${{ hashFiles('app/build.gradle') }}
      - name: Download Dependencies
        run: ./gradlew androidDependencies
      - name: Test
        run: ./gradlew testReleaseUnitTest
      - name: Publish App Bundle
        run: |
          echo $ENCODED_KEYSTORE | base64 -d > "`pwd`/keystore.jks"
          ./gradlew publishBundle \
            -Pandroid.injected.signing.store.file="`pwd`/keystore.jks" \
            -Pandroid.injected.signing.store.password=$STORE_PASSWORD \
            -Pandroid.injected.signing.key.alias=$KEY_ALIAS \
            -Pandroid.injected.signing.key.password=$KEY_PASSWORD
        env:
          ENCODED_KEYSTORE: ${{ secrets.ENCODED_KEYSTORE }}
          ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
          STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
```

解説することはほぼなし。PCでデプロイしたときのコマンドをほぼそのまま実行しているだけ。
ENCODED_KEYSTOREは、「echo $ENCODED_KEYSTORE | base64 -d > "`pwd`/keystore.jks"」というコマンドで、keystoreファイルに戻している。
UnitTestの実行しないなら、./gradlew testReleaseUnitTestは除去でOK。

## 例：実際に使っている設定ファイル

- build.gradle
  - https://github.com/KamikazeZirou/shared-device-manager-android/blob/main/build.gradle
- GitHub Actionsのworkflow
  - https://github.com/KamikazeZirou/shared-device-manager-android/blob/main/.github/workflows/pre-release.yml

[^1]: keystoreは暗号化されていると思っている。これが合っていれば、普通にリポジトリ管理対象にして良い気がしている。しかし、Androidテスト全書だとAESで暗号化している。それにならって、生のkeystoreはをリポジトリ管理対象にしなかった。面倒だが安全ではある。