Kotlin, LiveData, coroutine なんかを使って初めてのAndroidアプリを作る(10)リリースビルドとGithub Actions編

[前回](https://qiita.com/kasa_le/items/68098dd9fb04c542bdd5)の続きです。


# 今回の目標

予告はFirebaseをつかった認証とDatabaseとしていましたが、ちょっと方針転換で、CI(継続的インテグレーション)をやります。
テストが割と長くなってきたので、サーバーにお任せしたいな、と。
それと、apkファイルへの署名についてもやっていきます。

# CIとは

CIとは、継続的インテグレーションです。**Continuous Ingegration**の略です。
ざっくばらんに言うと、ビルドとテストを自動化してどっかクラウドとか別サーバーに繰り返しやらせましょうねってことです。
やらせる契機は、ブランチへのpushだったり、PR(プルリク)だったり、masterへのマージだったり、いろいろ指定できるものですが、今回は、**masterを除くブランチへのpushで毎回ビルドとデバッグテストを行う**というのをやってみようと思います。

## 1. CIツール

いくつかのCIツールについて紹介します。
今回やるのは、

- Github Actions

ですが、他にも、

- Jenkins
- CircleCI
- TravisCI
- bitrise
- Azure Pipelines
- GitLab CI/CD
- Bitbucket Pipelines

などがあります。
  
クラウドで出来るものと、オンプレミス(自分でサーバーやローカルマシンに環境構築が必要)なものとありますが、いずれも、オープンソースならば利用が無料なところが多いです。ただ、単にソースコードを公開しているだけではだめな場合もあるので、よく規約や料金プランを確認して下さいね。

## (2) CIツールでやること

基本的には、CIは以下のことを行うことが目的です。

- ビルド
  - 生成物(Androidの場合、apkファイル)の保存
- テスト(Unit Test/自動化されたUI Test)
  - 結果レポートの保存と閲覧

ビルドは、Androidの場合、デバッグビルドだけやるのを紹介している記事がほとんどです。
これには理由があります。

それは、リリースビルドのapkには「署名」が必要だからです。(実際にはデバッグビルドでも必要ですが、それについては後述)

で、署名に必要な情報をそのままリポジトリにアップロードしてしまうと、パスワードを平文で公開している状態となって非常に危険なので、通常、**署名関連の情報はリポジトリには登録しない**という運用を取ります。
そうすると、CIツールの方で、今度は、署名に必要な情報を参照することが困難になります。(CIツールは基本的にリポジトリをインターネット経由で参照します。リポジトリに上がっていないファイルは、どうやったって使えないですよね)

ということで、**CI**という範疇では、デバッグ用apkが出来ればいいだろうということで、そこまでしかやっていない紹介記事が多いのです。

リリース署名を行う、というのは、**CIの先**のステップとも言えるので、確かに十分ではあるんですよね。

ただ、今回せっかくCIツールについて調べるので、「リリースapkの署名まで自動化したいなあ」という思いがあり、一緒に調べることにしました。

ということで、CIツールのお試しに入る前に、apkへの署名についてもここでやってしまいましょう。

# アプリ署名について

Androidアプリは、最終的に以下のどちらかの形式でアプリファイルを配布します。

- apk (すべての端末向けの1ファイル)
- abb ([Android App Bundle](https://developer.android.com/guide/app-bundle?hl=ja)/端末仕様毎に必要なリソースを分けて配布) 

どちらにしても、証明書で署名する必要があります。

また、最近は、Googleがアプリに署名してくれる[Google Play アプリ署名](https://developer.android.com/studio/publish/app-signing?hl=ja#app-signing-google-play)というのが推奨になっていて、これは証明書をGoogleが保管してくれるというものなのですが、とはいえ、**アップロード鍵**としてやはり証明書を作って署名する、という手順は結局変わらず必要です。
違うのは、**アップロード鍵はGoogleさんに「失くした！」と泣きつけば、再発行できる**というメリットがあることですね。
以前の、「開発者が証明書を管理して署名する」だと、証明書が行方不明になると万事休すでした。二度と同じアプリをアップデート配信できなかったんですね。
そのリスクがなくなるという意味では、このGoogle App Signingというのを今後は使って行くべきでしょうね。

アップロード鍵での署名にせよ、アプリ署名にせよ、やることは同じです。
コマンドラインから作ることも出来ますが、ここではAndroid Studioでまず署名できることを確認しましょう。ただ、バージョン3.5から妙なエラーが出るんだけど、今のところ無視しても大丈夫なようです。

## (1) debugビルドへの署名

実は、debugビルドのアプリも、署名されてるんです。AndroidSDKが自動的に**デバッグ証明書**で署名しています。
デバッグ証明書(keystore)はデフォルトでは、下記の場所にあります。

- Mac/Linux

  ```consol
  ~/.android/debug.keystore
  ```

- Windows

  ```console
  C:¥Users¥<user>¥.android¥debug.keystore
  ```

デバッグ用証明書は、開発機毎に異なります。証明書が異なると、上書きインストールできません。従って、チームなどで開発用のスマホ実機を使い回していたりすると、他の人がデバッグ実行した後のスマホで、別の人がデバッグ実行しようとすると、アンインストールが一度必要になります。
また、今回、CIツールでapkをビルドし、それをダウンロードして使うことを考えると、クラウド型のツールを使うと、**毎回デバッグ用署名が違う**ことになってしまいます。
その度に、いちいち一度アンインストールしてから再インストールしなければならなくなり、ちょっと面倒そうです。
なので、デバッグ証明書も作って、自前で署名するステップを入れておきます。

### 1.debugビルドのパッケージ名を変える

さて、デバッグ証明書と、リリース用の証明書は変えなければなりません。となると、それぞれのapkの署名が一致しなくなってしまいます。
で、何が困るかというと、**debugビルドのアプリがインストール済の端末に、releaseビルドのアプリを上書きインストール出来ない**ということになります。
(署名が一致しないと同じアプリと見なされない)

回避する方法は、

- debugビルドとreleaseビルドで同じ署名を使う
  - ＝上書きインストール出来るようにする
- パッケージ名を変えて上書きインストールにならないようにする
  - ＝別のアプリとして認識させる
  
のどちらかになります。
で、個人的には、両方同時インストールできる後者のパターンの方が好きです。
ただし、アプリ内課金などがある場合には使えない手段なので、用心が必要ですが。
今回は、アプリ内課金は今のところ予定していないので、別パッケージにして、同時にインストールしておけるようにします。
テストを流すとデータ消えちゃうし、releaseビルドのアプリを本格的に使ってるとそれだと問題ですから^^;

`app/build.gradle`に以下の記述を追加します。

```app/build.gradle
android{
   ...
   
   buildTypes {
        debug{
            applicationIdSuffix ".debug"
            minifyEnabled false
        }
       ....
    }
    ....
}
```

これで、debugビルドの時のパッケージ名には、suffixとして".debug"が付くようになります。
つまり、`jp.les.kasa.sample.mykotlinapp`がデフォルトのパッケージ名なので、デバッグ時には`jp.les.kasa.sample.mykotlinapp.debug`となります。

### 2.アプリ名をビルドタイプ別に変更する

それと、両方同時インストール出来ると、今度は、「どっちがどっちのアプリか分からなくなる」問題が発生します。
分かりやすくするため、私はランチャーが表示するアプリ名を変更することをよくやります。

```app/build.gradle
  
buildTypes {
        debug{
            resValue "string", "app_name", "(d)歩数計記録アプリ"
            ...
        }
        release{
            resValue "string", "app_name", "歩数計記録アプリ"
            ...
        }
```

これは、文字列リソースを`build.gradle`で定義する方法です。

```res/values/strings.xml
   <string name="app_name">PedometerSample</string>
```

こんな感じで`app_name`という文字列リソースを定義していたと思いますが、これを`build.gradle`で定義できるんですね。便利です。
以前(大昔)は、debugビルドとreleaseビルドでリソースを分ける場合、`app/src/debug/res/values/strings.xml`と`app/src/main(or release)/res/values/strings.xml`とでリソースファイルを別に用意する必要がありましたが、gradleに移行したことでこういうことが出来るようになりました。リソースファイルをビルドタイプ別に用意するのは、同時に比較するのが難しく、ファイル数が増えて管理が煩雑になる、という問題点がありました。
gradleファイルに書いてあれば、debugビルドとreleaseビルドでの値の違いが、とても分かりやすく、管理も楽になりました。

ということで、リソースファイルの`app_name`は削除して大丈夫です。

Sync Nowしておきましょう。

### 3.デバッグ証明書を作成する

AndroidStudioから作成します。

- メニューの[Build]-[Generate Signed Bundle/APK...]を選択
  ![qiita10_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/c0058527-e513-b8ad-d74d-e1f2170f97ce.png)

- 続くダイアログは、いったんデフォルトのままで[Next]をクリック
  (証明書作成までしかしないので、今は無関係)
  
  ![qiita10_02_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/17f6cbf9-368c-aae6-c85a-3a5bb335a49c.png)

- [Create New]をクリック

  ![qiita10_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/fec291a8-12dc-5995-11f2-ec9ee1279b3f.png)

- フォルダアイコンをクリック

  ![qiit10_02_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/d7d2e54f-7dea-7c5f-76cc-237b22f5720a.png)

- 証明書を置くフォルダ(`app/`下)を選び、ファイル名は *debug.jks*とする(ファイル名は任意。デバッグ用だと分かりやすくしておく)
- [Save]をクリック

  ![qiita10_04_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/0b1d683c-2666-df47-25c0-572198708012.png)

- 以下の内容でセットする

|設定項目|設定内容|
|:--|:--|
|鍵ストア・パスワード|android|
|キー・エイリアス|androiddebugkey|
|鍵パスワード|android|
|First and Last Name|Android Debug|
|Organizatoin|Android|
|Country Code|US|

以前は、AndroidStudioが署名するのと同じパスワード、エイリアスで無いとデバッグ実行できませんでしたが、最近はその必要は無く、何でも良くなっています。
なので上記の通りに設定する必要は、本来はないです。

ただ、このデバッグ証明書のパスワードなどの情報はソース管理のリポジトリに上げてしまいますので、全世界に公開されます。デバッグ証明書で署名したAPKはPlayストアに登録できないのですが、パスワードやエイリアス情報を見ていると思われます。なので、ここは公開されているデバッグ鍵と同じ情報にしておくのが無難でしょう。

- [OK]をクリック
  - 以下のエラーが表示されるが、今は無視して大丈夫

  ![qiita10_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/a0ba2991-1194-5799-ba98-03d88c8c4981.png)

証明書が指定したフォルダに作成されます。
証明書が出来たら、今はここまでで良いので、[Cancel]をクリックして閉じます。

### 4.デバッグ署名を設定する

- メニューの[File] - [Project Structure...]を選ぶ

  ![qiita04_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/61897248-6387-cdac-946c-f56b0ab5d1cc.png)

- 左側のパネルで[Modules] を選び、右側のタブで [Signing Configs]を選ぶ

  ![qiita10_04_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/4d84a3a6-27c9-990d-a727-93297a4ccfad.png)
  
- 先ほど作成した`debug.jks`のパスを選ぶ
- パスワード、エイリアス情報を入力して [Apply]をクリック

![qiita10_04_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/5d724129-3404-4b2a-0c2b-66fcb6bd57c3.png)

- Syncが終わるのを待って、左側のパネルで[Build Variants]を選ぶ
- 右側の[Build Types]タブで debugを選び、下にスクロールして、**Signing Config**の右のドロップダウンから、**$signingConfigs.debug**を選ぶ

  ![qiita10_04_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/58a147f3-c663-aa3c-ade2-2dafbf73331f.png)

- OKをクリック

Gradle Syncが終わったら、`app/build.gradle`を開いてみましょう。

こんな項目が追記されているはずです。

```app/build.gradle
    signingConfigs {
        debug {
            storeFile file('debug.jks')
            storePassword 'android'
            keyAlias = 'androiddebugkey'
            keyPassword 'android'
        }
    }
```

`storeFile`のパスは、絶対パスだとチームの他の人やCIツールで一致しなくなって困るので、`app/`からの相対パスに変更しておきます。

また、`buildTypes`のdebugビルド内にも、以下のような設定が追記されています。

```app/build.gradle
    buildTypes {
        debug{
            ...
            signingConfig signingConfigs.debug
        }
```

これでデバッグ実行してみて下さい。今までと署名が変わったので、上書きインストールが出来ないはずです。ここは素直にアンインストールしてからインストールしましょう。
インストールが出来てアプリが起動すればデバッグ署名はOKです。

## (2) releaseビルドに署名する

### 1. 証明書を作ってビルドする

今度は証明書を作ってビルドまで、全部実行してみます。

- AndroidStudioのメニューから、[Build]-[Generate Signed Bundle/APK...]を選択

- [APK]を選ぶ

  ※最終的には App Bundleにするかどうかは任意です。
  
  ![qiita10_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/79423614-1e7e-c076-d349-6537d174630e.png)

- [Create New]を選ぶ

- `app/`下に任意のファイル名(例：`release.jks`)で作成する

  ![qiita10_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/18a48411-3c52-aff4-8165-a2e2889a432b.png)

- 任意の情報で設定する

  ![qiita10_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/80c784c3-fdd9-41af-e8dc-33c01757f26b.png)

ストアパスワード、キーエイリアス、エイリアスパスワード、国名(JP等)が必須項目です。
それ以外はなくてもいいですが、後で証明書を差し替えるのは大変なので、個人であっても氏名と地域情報は入れておいた方が良いでしょうね。

- [OK]をクリック

- 以下のエラーが表示されるが、今は無視して大丈夫

  ![qiita10_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/a0ba2991-1194-5799-ba98-03d88c8c4981.png)

- (任意)[Remember passwords]にチェックを入れて、[Next]をクリック

  ※ [Remember passwords]にチェックを入れても、パスワードは覚えておきましょう

  ![qiita10_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/7489a801-3019-7898-1cea-f706c603e117.png)

- [Build Variants]の`release`を選び、`V2 (Full APK Signature)`にチェックを入れる

  ![qiita10_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/41ba2626-caf0-905f-7843-2919b8c60c19.png)
  
  ※V1は、古い開発ツール時代から署名していたアプリ用のオプションです。
  
- [Finish]をクリック

ビルドが始まります。しばらく待ちましょう。
成功しましたか？

APKは、`app/release`下に出力されています。

![qiita10_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/1dd69912-7c9d-bbf1-8ce3-7f3d967e1773.png)

### 2. 署名情報の設定ファイルを作成する

さて、releaseビルド用の証明書のパスワードやらを`build.gradle`に書いてしまうわけにはいきません。他の人がアプリを改ざんしほうだいになってしまいます！

色々やり方はあると思いますが、[Googleさんも公式に推奨している](https://developer.android.com/studio/publish/app-signing?hl=ja#secure-shared-keystore)、Gradleのプロパティファイルにいったん逃がす方法を応用して使おうと思います。

- プロジェクトルートのディレクトリに`keystore.properties`というファイルを作り、各情報を設定する

```gradle:keystore.properties
    storePassword=ストアパスワード
    keyPassword=キーパスワード
    keyAlias=キーエイリアス
    storeFile=release.jks
```

ストアパスワード、キーパスワード、エイリアスは、先ほどリリース用証明書を作ったときに使ったものを入力して下さい。
(まさかもうパスワード忘れたとかないよね〜)

- `app/build.gradle`に以下を追記する

```app/build.gradle

apply plugin: xxx

def keystorePropertiesFile = rootProject.file("keystore.properties")

android {
    ...
    
    signingConfigs {
        debug {
            ...
        }
        release {
            if (keystorePropertiesFile.exists()) {
                def keystoreProperties = new Properties()
                keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
                keyAlias keystoreProperties['keyAlias']
                keyPassword keystoreProperties['keyPassword']
                storeFile file(keystoreProperties['storeFile'])
                storePassword keystoreProperties['storePassword']
            }
        }
    }
```

`keystore.properties`ファイルが無かったときにビルドが失敗しないようにしてあります。

-  `app/build.gradle`の`buildTypes{}`に以下を追記する
  
```app/build.gradle
    buildTypes {
        debug {
            ...
        }
        release {
            ...
            if (keystorePropertiesFile.exists()) {
                signingConfig signingConfigs.release
            }
        }
    }
```

こちらも、`keystore.properties`ファイルが無かったときにビルドが失敗しないようにしてあります。

※`keystore.properties`ファイルが無かったときは、未署名のapkが出力されます。

これでビルドが通るかどうかは、ちょっと後回し。

### 3. 無視ファイルの設定変更

まず、`output.json`がソースコード管理に入ってしまうと面倒なので、気になる人はプロジェクトルートの無視ファイル(`.gitignore`)に追加しておきましょう。

※Macの人は、Finderで**command + shift + .**とすると隠しファイルが表示されます。

```.gitignore
output.json
```

それから、デバッグ用証明書はコード管理に追跡させたいですが、リリース用証明書と、そのパスワード情報などが書かれた`keystore.properties`はGithubで公開されては困ります。
(Privateリポジトリなら上げてしまってもいいかも知れませんが、万が一の漏洩リスクを考えると、上げない方が良いように思います。)

ということで、これらについても`.gitignore`ファイルに追記します。

```.gitignore
release.jks
keystore.properties
```

ファイルを保存して、`git status`などで見てみて下さい。
無視する設定にしたファイルが表示されていないことをしっかり確認してから、コミットしましょう。

# コマンドラインからの実行

コマンドラインからビルド、テストが出来るのを確認します。なぜなら、CIツールではこれらを使うからです。先ほどのリリース証明書での署名が成功するかのテストにもなります。

## (1) ビルド

- debugビルド
  `$ ./gradlew assembleDebug`
  - 出力は、`app/build/outputs/apk/debug`
  
- releaseビルド
  `$ ./gradlew assembleRelease`
  - 出力は、`app/build/outputs/apk/release`
  - AndroidStudioから[Generate Signed Bundle/APK...]でやったときに出力されたフォルダとは違うので要注意

- 両方ビルド
  `$ ./gradlew assemble`


## (2) Lintチェック

言語的、あるいはAndroidのお作法的推奨事項を持っているか、無駄な変数、リソースが無いかのチェック等をしてくれます。

- debugビルドのLintチェック
  `$ ./gradlew lintDebug`

- releaseビルドのLintチェック
  `$ ./gradlew lintRelease`

- 両方Lintチェック
  `$ ./gradlew lint`

ログに出るので分かると思いますが、レポート結果の出力先は `app/build/reports/`です。
結構いろいろ出てますね(汗)
対応するかどうかはお任せします。

##  (3) UnitTest

JUnitテスト、Robolectricテストが実行されるテストです。

- debugビルドのUnitTest  
  `$ ./gradlew testDebugUnitTest`
  - 結果は、`app/build/reports/tests/`に出力
  
`./gradlew test`でもUnitTestが実行できるのですが、これはreleaseビルドのUnitTestまで実行してしまいます。今回、releaseビルドは事情があって実行できないので(動かしてみると理由が分かると思います)、debugビルドに限定するコマンドで実行します。

注意点としては、一度テストをした後は、コードが変わっていないと、`clean`するまでテストが実行されません。
クラウド型のCIならば、恐らく毎回clean状態なので大丈夫だとは思いますが、Jenkinsなどで行う場合は、cleanさせる設定を入れておく必要があるでしょうね。

なお、`./gradlew check`で、**Lintチェック + test**が実行出来ます。
また、`./gradlew build`で、**ビルド + Lintチェック + test**が実行出来ます。

リリースビルドでUnitTestが行われてしまっても大丈夫なときは、`build`コマンド1つが楽かも知れません。

## (4) Instumentationテスト

今回はエミュレーターで実行してみて下さい。ほとんどのCIでは必然的にエミュレーターを使うことになりますので。

API Level 28 のエミュレーターを作り、起動しておきます。
(※今現在、`compileSdkVersion`と `targetSdkVersion`を`28`にしているから。`29`に上げないと2020/8/3以降はPlayストアにアプリを登録することが出来ないので、要注意。なおアップデートの場合は、11/2以降不可になります)

初期設定が終わるのを待ちましょう。

その後、以下のコマンドでInstumentationテストが実行されます。

```console
$ ./gradlew connectedCheck
```

このコマンドは、debugビルドだけみたいです。
結果は、`app/build/reports/androidTests/connected/`に出力されます。

全部通過していると気持ちいいですね。

各タスクの実行コマンド、結果の保存場所や見方が分かったところで、いよいよ、CIツールの出番です。

# Github Actions

GithubがいよいよCIに乗り出してリリースされた機能です。
中身はAzureで動いているようですね。そういや、Microsoftに買収されたんだっけか・・・

パブリックリポジトリはGithub Actionsの利用は無料、と言っているので、自由に使わせて貰うことにします。
プライベートリポジトリの場合は、いろいろと制約、制限、課金があるようですので、ご注意下さい。

## (1) ActionsからWorkflowを作る

早速、workflowを作ってみましょう。
Githubの該当のリポジトリページを表示し、**master**ブランチになっているのを確認し、上のタブ(ちょっと分かりづらいですが)の**[Actions]**を選びます。

※後で無視するブランチなどを追加していきますが、その際、いったん`master`ブランチに入っていないとだめみたいなので、まずはmasterに追加する必要がありました。

|![qiita10_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/e37ebae2-3645-4e1a-4370-30d710a4a445.png)|
|:--|

### 1. デバッグビルド

まずはデバッグビルドが出来るWorkflowを作成します。

- [New workflow]をクリック
- 下にスクロールして、**[Workflows for Python, Maven, Docker and more...]**をクリック

  ![qiita10_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/bdd82133-a0d6-b782-0d46-7aed1caa7a78.png)


- **Android CI**をクリック

  ![qiita10_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/629aa60d-0f79-ac8a-b05a-b75258b4fd0e.png)

こんなファイルが表示されるはずです。

```android.yml
name: Android CI

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: 1.8
    - name: Build with Gradle
      run: ./gradlew build
```

これは、[push]がされたら、`ubuntu`上で、チェックアウトしてきて、JDK1.8の設定をして、`gradlew build`コマンドを実行する、という設定になっています。

relaseビルドでのUnitTestが実行されると困るので、まずはdebugビルドだけするように、`run:`のコマンドの部分を、`./gradlew assembleDebug`に変更します。

```android.yml
    - name: Build with Gradle
      run: ./gradlew assembleDebug
```

ページ右上にある、[Start commit]をクリックしてみて下さい。

![qiita10_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/cea7952d-c1b1-b336-f09c-4139f4165840.png)

コミットメッセージは任意で、[Commit directly to the `master` branch]を選択して、[Commit new file]をクリックします。

早速Workflowが流れるはずですが、私の場合、`master`ブランチは空っぽなので、ビルドエラーになります。
すでに`master`ブランチにコードがある方は、デバッグビルドが流れて成功するはずですね。

### 2. masterブランチをローカルにpullする

masterブランチをローカルにpullしてきます。
(どうでも良いですが私はGitのGUIツールでは、**Source Tree**が好きです)

これからメインにpushしていきたい開発ブランチ(私の場合、`feature/qiita_10`)に、マージしてpushします。

すると、pushしたブランチで、Workflowが走るはずです。
今度は私の環境でもビルドが成功しました。

## (2) 無視するブランチを設定する

とりあえず、私のように、`master`が空だったりするときや、実験用などで毎回Workflowが動くと困るようなブランチがある時のために、特定ブランチへのpushは無視するという設定が出来ます。

再び、`master`ブランチ上の`.github/workflows/android.yml`を編集します。

`push`の階層下に、`branches-ignore:`で追加できます。

```yml
name: Android CI

on:
  push:
    branches-ignore:
      - 'master'
      - 'feature/qiita_10_sub/**'
      
```

私の場合、今後、他のCIツールをお試しするときのブランチを`feature/qiita_10_sub/xxx`みたいに作っていく予定なので、それも設定しました。このように、正規表現が使えます。

これとは逆に、特定のブランチのみビルドしたい場合(devブランチのみで回すなど)は、`branches:`で指定できます。

先ほどと同じく、`master`ブランチにそのままコミットし、ローカルにpullしてきて開発ブランチにマージします。

このままpushしてもいいですが、せっかくなので次の設定も追加してからにしましょう。

## (3) APKをアーカイブする

ビルドしたAPKファイルを、**アーティファクト**として保存してダウンロード出来るようにしましょう。

ここからは、開発用ブランチで行って大丈夫です。

```.github/workflows/android.yml
    - name: Archive Debug Apk
      if: success()
      uses: actions/upload-artifact@v1
      with:
        name: debugApk
        path: app/build/outputs/apk/debug/app-debug.apk
```

インデントが重要なので、気をつけて下さいね。
` if: success()`は、前のタスクが成功したときのみ実行するという条件です。
`actions/upload-artifact@v1`というアクションを使うと、成果物(artifact)をアップロードできます。
ここでは、`debugApk`という名前で、`path:`で指定したファイルをアップロードしています。

ymlファイルを保存して、コミットし、pushしてみましょう。
ビルドが成功して、Workflowのページを見ると、**Artifacts**の所に、apkファイルへのリンクがあるはずです。クリックするとDL出来るかと思います。DLしてくるのはZipファイルになっているので、解凍すると、中に`app-debug.apk`が入っています。

上書きインストールをしてみて下さい。
デバッグ用証明書が共通化されたので、他のマシンでビルドしたapkでも上書きインストールが出来るはずです。

## (4) テストの設定を追加する

次はテストが実行されるようにWorkflowを編集します。

### 1. Checkタスクを追加

Lintと、UnitTest/ReobolectricTestが実行されるようにタスクを追加します。
ymlファイルの一番下に以下を追記します。

```android.yml
    - name: Check
      if: success()
      run: ./gradlew lint testDebugUnitTest
      
    - name: Archive results
      if: always()
      uses: actions/upload-artifact@v1
      with:
        name: test-reports
        path: app/build/reports
```

`Check`という名前のジョブで、`lint`と`testDebugUnitTest`のgradleコマンドを実行しています。
`Archive results`という名前のジョブで、Lint、テストの実行結果レポートをアップロードしています。

コミットしてpushしてみましょう。
Workflowのジョブリストに、`Check`というのが追加されているでしょうか？

終わると、**Artifact**のところに、**test-reports**というのが出来ているはずです。Zipファイルをダウンロードして、解凍して中身を確認しましょう。

ローカルで`testDebugUnitTest`としたときと同じレポートが見られるはずです。

### 2. 仮想マシンを変更する

さて、エミュレーターでのInstrumentationテストもやって欲しいですよね。
これが出来ると、フルテストを実行中、スマホが使えなくなってしまうことも無くなります。
テスト中に電話が掛かってきたりpush通知で画面占有されたりして「あああテストが失敗やり直し・・・」なんてことも無くなります。

Github Actionsにはちゃんとそのためのアクションを既に作って公開して下さっている方がいるのですが、1つだけその前に直す必要があるところがあります。

仮想マシンのOSタイプです。
デフォルトだと、**Ubuntu(Linux)**になっていますが、エミュレーターは、HAXMというのを使わないととてもじゃないけど重くて、これはUbuntu向けにはどうやら今は用意されていないようなのです。
なので、ymlファイルの`runs-on`にある設定を、MacOS用に変更します。

```android.yml
    runs-on: macOS-latest
```

これだけです。

※本来、WindowsでもエミュレーターはHAXMで動かせるはずですが、参考記事はみんなmacOSを指定してました。理由までは追ってないですが、Github側が用意しているWindows仮想マシンが、HAXMに対応していないのかも知れません。誰か理由をご存じの方や、試してみて動いたor動かなかった等あったら教えて下さい！

### 3. エミュレーターテストのタスクを追加する

`Github Actions android emulator test`等でググると、いくつか情報が見つかりますが、バージョンが正式リリースされていてメンテも継続されていそうな、こちらを使わせて頂くことにします。

Android Emulator Runner
https://github.com/marketplace/actions/android-emulator-runner

ymlファイルに以下を追記します。追加するのは、`Check`ジョブと`Archive results`ジョブの間です。

```android.yml
    - name: Android Emulator Runner
      if: always()
      uses: reactivecircus/android-emulator-runner@v2
      with:
        api-level: 28
        profile: Nexus 6
        script: ./gradlew connectedCheck
```

`profile`に大きめの画面のものを設定しないと、どうやら非表示のウィジェットが出来るようでテストが失敗するものがあるので、ここでは`Nexus 6`を指定しました。

ただ、「`$ANDROID_HOME/tools/bin/avdmanager list`でとれる端末リストの名前を指定できる」と書いてあるのですが、`Pixel`を指定してもそんなAVD無いと言われてしまいました。
なので、ここは無難にサンプルに上がっていた`Nexus 6`にしています。
他に何が使えるかは、試していません。

ymlファイルをコミットしてpushしましょう。
エミュレーターテストは時間かかるので、その間に他のことをして待ちましょう。
自分のスマホや開発マシン(PC)は空いたので、何でも出来ますね(笑)

## (5) 通知を設定する

Workflowが終わったら、どこかに通知して欲しいですよね。
Slackとか、チャットワークとか。
今回は、Slackにしてみます。

### 1. SlackにWebHookアプリを追加する

Slackに既に利用中のワークスペースが無い場合は、自分用に作っておきましょう。
この手順は割愛します。

Slackの**Incoming WebHookアプリ**のページへ行きます。
https://slack.com/apps/A0F7XDUAZ-incoming-webhooks

- [Slackに追加]をクリック

  ![qiita10_80.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/cde44ee9-fd67-fe6e-72c3-90190b0969a7.png)

- 投稿したいチャンネルを選ぶ
- [Incoming Webhook インテグレーションの追加]をクリック
- **Webhook URL**の下にある[Copy Url]をクリック

その他の設定は任意です。

- [設定を保存する]をクリック

これでSlackの準備は出来ました。

### 2. GithubにWebhook URLを設定

Githubに先ほどコピーした**Webhook URL**を**Secrets**として保存します。

Githubの自分のリポジトリページへ行きます。

- タブ[Settings]を選ぶ(分かりにくい)
- 左側の[Options]メニューの中から、[Secrets]を選ぶ

  ![qiita10_82.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/a811bc0d-a7c1-4fa4-e71d-60a17628519c.png)


- [Add a new secret]をクリック
- `Name`に任意の名前を入力
- `Value`に先ほどコピーしたWebhook URLを貼り付ける

  ![qiita10_83.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/444a7e50-3864-48b4-7827-a1d8d0560b55.png)

- [Add secret]をクリック

準備はこれで終わりです。

### 3. Actionsに追加

Slack通知してくれるGithub Actionは[Github Marketplace](https://github.com/marketplace?utf8=%E2%9C%93&query=slack)に多数上がっていて、いくつか試したのですが、ほとんどが`Linux(Ububntu)`上でしか動かせないものでした。
エミュレーターテストのため、`macOS`にしていますから、IncomingWebhookを使い、かつmacOSで動かせるものを探して、ようやく見つけたのが、こちらです。

 https://github.com/marketplace/actions/slatify

作者様、ありがとうございます！
日本の方のようです。Qiita記事からたどり着きました。最後に参考リンクを貼ってありますので興味ある方はご覧になってください。

ということで、上記のページのサンプルを参考に、ymlの最後に以下を追記しました。

```android.yml
    - name: Slack notification
      uses: homoluctus/slatify@master
      with:
        type: ${{ job.status }}
        job_name: '*Build and DebugCheck*'
        channel: '#general'
        url: ${{ secrets.SLACK_WEBHOOK_URL }}
```

`${{ job.status }}`で、ジョブの完了ステータスを教えてくれます。
Slackへの投稿は、見た目はこんな感じになりました。

<img width="643" alt="qoota10_88.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/4f91c1b5-cb11-496d-1b89-e160c210b7cd.png">

エラーだとこんな感じ。

![qiita10_85.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/c930f3e7-2a53-5552-49cd-5db5563a2ec4.png)

良い感じです！
これで、テストが終わったか何度もPCを覗きに来る必要がなくなりました。

※1 Slackへの通知は、単純に`curlコマンド`を書いても出来ないことはないので、「curlコマンド大好き」って方は、それでも良いでしょう(笑)
私はどなたか作ってくれて無いかなーと半分意地になって探しました^^;

※2 ジョブを分けて、そちらのジョブではOSにUbuntu使うようにすることで、`Linux(Ububntu)`上でしか動かせないアクションでも動かせます。`qiita_10_sub/multi_jobs_os_change`ブランチに`slack.yml`というのをサンプルで入れてありますのでご参照下さい。
https://github.com/le-kamba/qiita_pedometer/blob/feature/qiita_10_sub/multi_jobs_os_change/.github/workflows/slack.yml
でも、マシンのビルドや設定が走って遅いので、出来るなら同じOSの1つのマシンで動かしたいですよね。

## (6) ステータスバッジをつける

### 1. ステータスバッジをREADMEに追加する

せっかくなので、`README.md`ファイルにステータスバッジをつけようと思います。
ステータスバッジとは、CIツールなどでのビルドやテストが通っていることを示す画像のことです。
大きなGithubプロジェクトのページに行くと、よく貼ってあると思います。
いろんなCIツールがバッジを提供しています。

Github Actionsでは次の手順でつけられます。

- ActionsのページでバッジをつけたいWorkflowを選ぶ
- 右上の[Create status badge]というボタンをクリック

<img width="420" alt="qiita10_99.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/75e6f949-85dd-eff8-a3a3-51023a29bfb5.png">

- ブランチを選ぶ(任意)
- [Copy status badge Markdown]をクリック

<img width="331" alt="qiita10_100.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/d85caf2d-12a8-6aa6-0804-b35ae46a0f61.png">

- `README.md`ファイルを開く
  - Github上で開いてしまっても可
- 先ほどコピーしたテキストを貼り付けて保存
  - 場所は任意だけどだいたい先頭に置かれていることが多い
- コミット
- push(ローカルで編集した場合)

こんな風に表示されます。

<img width="633" alt="qiita10_101.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/ace7f2a6-5066-ae1f-1381-1debcb9c5f62.png">

なんかちゃんと運用されているプロジェクトっぽくなりました(笑)

### 2. トリガーを無視するファイルを設定する

さて、`README.md`だけを編集してコミット、pushしたときに、Workflowが動いてしまいますね。
ビルドには全く関係ないファイルの更新で、テストまで走ってしまうのはかなりの無駄です。
そこで、トリガーを無視するファイルの設定を行っておきます。
それには、`paths-ignore`を使います。

```android.yml
on:
  push:
    paths-ignore:
      - '**.md'
    branches-ignore:
      - master
      - 'feature/qiita_10_sub/**'
```

ドキュメント(マークダウンファイル(.md))はすべて無視するようにしました。
これで、コミットファイルが一致するものしか無い場合は、Workflowが起動しないはずです。

※確認するには、先に`android.yml`をコミットしてpushした後、`README.md`を編集してコミット&pushしてみてください。

## (7) リリースビルドを追加する

さて、リリースビルドも一緒に出来るようにしていこうと思います。
ただ問題は、署名ファイルです。
Githubに上げるわけにはいきません。
先ほどの`Secrets`を使って上手くいかないかなあと思っていたのですが、いわゆる「秘密ファイル」の登録は出来ないようで、悩みました。

解決策としては、以下の2つくらいしか無さそうです。

A. [Sign Android Release Action](https://github.com/marketplace/actions/sign-android-release)を使ってみる
B. 以下の内容を参考に、証明書を暗号化したファイルをコミットして使う
  https://help.github.com/ja/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets

せっかく作って下さっているので、Aを試してみようと思います。

[Sign Android Release Action](https://github.com/marketplace/actions/sign-android-release)のMarketページによると、以下の値が必須だと書いてあります。

|変数名|内容|
|:--|:--|
|releaseDirectory|署名したapkの出力フォルダへの相対パス|
|signingKeyBase64|証明書ファイル(`.jks`ファイル)をbase64エンコードした文字列|
|alias|証明書のエイリアス|
|keyStorePassword|証明書の鍵ストアパスワード|
|keyPassword|証明書の鍵パスワード|

`releaseDirectory`以外は、すべてGithubの**Secrets**に保存して使います。
**signingKeyBase64**の用意が必要ですね。でも親切にコマンドを書いてくれているので、そのまま実行させましょう。Macだと多分デフォルトで入っているはず。Windowsのかたはすみません、自分で調べて入れて下さい。

ターミナルなどで`release.jks`ファイルがあるフォルダに移動し、以下のコマンドを叩きます。

```console
$ openssl base64 < release.jks | tr -d '\n' | tee release.jks.base64.txt
```

`release.jks.base64.txt`が同じフォルダに出力されているので、ファイルを開いて中身をすべてコピーし、Secretsに登録します。

**Name** : SIGNING_KEY
**Value** : コピーした文字列を貼り付け

登録したら、`release.jks.base64.txt`がコミットされてしまわないよう、ファイルは削除しておきましょう。

後は`keystore.properties`に設定した情報を参考に、Secretsに1つずつ設定すればOK。

|Name|Value|
|:--|:--|
| ALIAS | 証明書のエイリアス|
| KEY_STORE_PASSWORD | 証明書の鍵ストアパスワード|
| KEY_PASSWORD | 証明書の鍵パスワード|

`android.yml`に以下のように追加します。場所はデバッグ用apkをアーティファクトに上げているタスクの後で良いでしょう。

```android.yml
    - name: Sign Release Apk
      if: success()
      uses: r0adkll/sign-android-release@v1
      with:
        releaseDirectory: app/build/outputs/apk/release
        signingKeyBase64: ${{ secrets.SIGNING_KEY }}
        alias: ${{ secrets.ALIAS }}
        keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
        keyPassword: ${{ secrets.KEY_PASSWORD }}
```

ファイルをコミット、pushして、Actionを流します。
署名が成功しましたか？

`keystore.properties`が完全にローカルビルド用になっちゃいますが、設定としてはすべてSecretsで対応でき、シンプルで良いですね。


Bの方法は、証明書ファイルと`keystore.properties`を暗号化した上でGithubに上げてしまい、Actionsのタスクの中でそれらを復号化して使う、ということになるかと思います。
結構煩雑そうなのでここではやりませんが、せっかく作った`keystore.properties`を活用できる方法かなとも思うので興味があればやってみても良いかも知れません。

## (8) リリースAPKをアーカイブする

さて、署名が出来たら、apkをアーティファクトに上げれば、実行後にActionsのページからダウンロードできるようになります。
先ほどの**Sign Android Release Action**が、

> Outputs
> signedReleaseFile
> The path to the signed release file from this action

と、署名済みファイル名を出力してくれているので、こんな風にアクセスして使えます。

```yml
   path: ${{ steps.<id>.outputs.signedReleaseFile }}
```

`<id>` は、タスクにidを付けておいて、そのタスクの出力を使う、という構文です。
なのでまず、署名タスクにidを付けます。
その上で、アーティファクト部分のタスクを書きます。

```android.yml
   - name: Sign Release Apk
      if: success()
      uses: r0adkll/sign-android-release@v1
      with:
        releaseDirectory: app/build/outputs/apk/release
        signingKeyBase64: ${{ secrets.SIGNING_KEY }}
        alias: ${{ secrets.ALIAS }}
        keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
        keyPassword: ${{ secrets.KEY_PASSWORD }}
      id: sign-apk # 追加
  
    - name: Archive Release Apk
      if: success()
      uses: actions/upload-artifact@v1
      with:
        name: releaseApk
        path: ${{ steps.sign-apk.outputs.signedReleaseFile }}
```

コミット、pushして、Actionを流します。
署名とアーティファクトのアップロードが成功しました。

ただ、ここで出来た署名済みapkをDLしてから、ふと思いました。

**publicなリポジトリで、署名済みのリリース用apk上げちゃったら、ダウンロードし放題だよね？**

もしかしたら、悪意のある誰かが、そのapkを自分のapkとしてPlayストアにアップロードしてしまうかもしれません。
今は公開しようと思うような出来のアプリではありませんが(汗)、実際にストア公開しようと思っているアプリならば、publicなリポジトリに署名済みapkがあるのはまずい状態ですね。アップロード鍵とする場合には、後から変更する手段はありますが・・・でも同じパッケージ名のアプリはPlayストアに登録できないので、誰かが先に登録してしまうともう登録できないというのもあります。

privateなリポジトリでやるか(従量課金なので気をつける必要あり)、releaseビルドはローカルで行う運用にする、自分でCIサーバーを立ててそこで動かすなどすべきでしょう。
もしくは、`/app/build/outputs/apk`全体をパスワード付きzipとかにして別の場所に格納し、それをアーティファクトとして保存する、というのも良さそうですね。

ひとまず、リリースビルドについては、CD(継続的デプロイ)の範疇になってくるので、今回のところは、いったんアーティファクトに上げるところをコメントアウトしておきます。既に上がってしまっている物は逐一削除で^^;


# まとめ
GithubActionsでビルド、テストの一連を自動化することが出来ました。生成物(apk)やテスト結果レポートをアーカイブ、ダウンロード出来るようになりました。
個人的には、基本的なことは出来るので、これで十分な気がします。
テスト結果をいちいちDLして解凍してからじゃ無いと見られないのが、他のツールと比べて少し面倒でしょうかね。

ここまでのソースは以下のブランチにアップしてあります。
https://github.com/le-kamba/qiita_pedometer/tree/feature/qiita_10

# おまけ

`"[ci skip] 〜〜"`というコメントでコミットすると、CIツールがトリガーされないように出来る機能がCircleCI等にあるようで、GithubActionsでもないかなと調べたのですが、ないようです。
で、こちらの方が紹介している、`Jobs.if`という構文を使う方法で入れてみてあります。

- GitHub Actions で [ci skip] できるようにしました  
https://srz-zumix.blogspot.com/2019/10/github-actions-ci-skip.html

# 予告

CIツールの第2回目をやります。

- Jenkins
- CircleCI

についても、一応やってみたので、次回、ご紹介します。

 
# 参考ページなど

- About billing for GitHub Actions    
https://help.github.com/ja/github/setting-up-and-managing-billing-and-payments-on-github/about-billing-for-github-actions
- GitHub ActionsでGradleのユニットテストの結果を取得する  
https://qiita.com/tkymato/items/6b498b66687b2b0d641d  
- GitHub Action - Android Emulator Runner  
https://github.com/marketplace/actions/android-emulator-runner
- GitHub Actionsでエミュレータテストをする  
https://satoshun.github.io/2019/12/github-actions-emulator-test/
- 【GitHub Actions】Slack通知用のActionsをTypeScriptで開発してみた  
https://qiita.com/homines22/items/0bc6c17e038b35fc8113