AndroidプロジェクトをGitHub ActionsでCIする

# GitHub Actions

転職後GitHubを使うようになり、開発環境がGitLabからGitHub Enterpriseへと変化しました。
GitLabには、GitLab CIという非常に優秀なCIツールがあり、無償で非常に柔軟性の高いCIが組めたのですが、転職に伴ってCI環境もCircle CIになりました。
そして、今度はGitHub Actionsがリリースされ、Enterpriseでも使えるようになるのは時間の問題でしょう。
その時に備えて予習しておきましょう。

なお、今回使用するリポジトリはこちらです（試行錯誤の跡が残ってます…）
https://github.com/tetsukay/github-actions-for-android

## 設定

### Actionsの初期設定

GitHubのメニューから、Actionsを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/61cf8197-4308-aed5-da2e-9a122d46a2cd.png)

すると、Actions Workflowのテンプレートが表示されます。
初期表示にはAndroidのワークフローはないので、少しスクロールしてテンプレートを追加表示させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/5ace0c6c-f2d2-a16e-deeb-6de671be7aaa.png)

見ていくと「Android CI」のワークフローが見つかりますので、これを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/ce36dc87-4861-436c-4e94-0064d7562d55.png)

テンプレートの内容でひとまずコミットしてみます。

### エラー発生

コミットしたらすぐCIが始まっています。
Actionsのページで確認してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/2960e191-3dc9-497f-566e-8989b5c7c5d2.png)

しかし、失敗しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/90706aaf-bace-0881-752d-e20536e11b26.png)

内容を確認すると、 `./gradlew` への実行権限がないようなので、yamlを編集して実行権を与えてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/bb58f8f5-8eea-acdc-610e-211c47b41abf.png)

パーミッションを与えて再度実行を行ってみますが、今度は以下のエラーが発生しました。

```shell
Run chmod +x ./gradlew
Downloading https://services.gradle.org/distributions/gradle-5.4.1-all.zip
..............................................................................................................................
Unzipping /home/runner/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1-all.zip to /home/runner/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx
Set executable permissions for: /home/runner/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1/bin/gradle

Welcome to Gradle 5.4.1!

Here are the highlights of this release:
 - Run builds with JDK12
 - New API for Incremental Tasks
 - Updates to native projects, including Swift 5 support

For more details see https://docs.gradle.org/5.4.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)
File /home/runner/.android/repositories.cfg could not be loaded.
Checking the license for package Android SDK Build-Tools 29.0.1 in /usr/local/lib/android/sdk/licenses

License for package Android SDK Build-Tools 29.0.1 accepted.
FAILURE: Build failed with an exception.
Preparing "Install Android SDK Build-Tools 29.0.1 (revision: 29.0.1)".
Warning: Failed to read or create install properties file.

* What went wrong:
Could not determine the dependencies of task ':app:compileReleaseJavaWithJavac'.
> Failed to install the following SDK components:
      build-tools;29.0.1 Android SDK Build-Tools 29.0.1
  The SDK directory is not writable (/usr/local/lib/android/sdk)


* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 40s
##[error]Process completed with exit code 1.
```

本来SDKAndroid SDKはホームディレクトリ以下に展開されるはずなのですが、 `/usr/local/lib/android/sdk` 以下に配置しようとして書き込み権が無いと言われています。
[このあたり](https://github.com/actions/starter-workflows/issues/58) を確認すると、どうやらUbuntuのイメージで発生しているようで、最新の `latest` や `18.04` イメージで [改善するとの情報もありました](https://github.com/actions/starter-workflows/issues/58#issuecomment-535442683) が、現在も動作していませんでした。

仕方が無いので、ひとまず動作させることを目標として、 `sudo ./gradlew build` コマンドへ変更して、ちょっと無理矢理ですが動作させます。

### 正常完了

<img width="1004" alt="スクリーンショット 2019-12-10 9.20.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/93d083fa-d777-dd47-5fe0-5a6e148b496c.png">

若干のトラブルはあった物の、ひとまずビルドさせること自体は成功しました。
イメージ自体にSDK自体が含まれているのもあって、ほとんど空のプロジェクトで2，3分掛かっており、ビルド自体はそこまで高速ともいえない印象です。

## その他

### PR1の状態。ビルド成功

<img width="771" alt="スクリーンショット 2019-12-10 9.38.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/7f9172c0-ea0d-73e3-1e91-0f7eb063a1a0.png">

### PR2の状態。ビルド中とビルド失敗

どちらの状態でも、マージできないということは無いんですね。
個人的には、CI中もCI失敗した後もマージをブロックしてほしい気がします。
とはいえ前職でも、どうしてもCI自体の調子が悪くなって一時的にマージを許可することもあったのですが。

また、CIが無事成功したら自動的にマージする機能が欲しいですね。（ [GitLabにはある](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) ）
現プロジェクトは参加人数も多く、CIが通っていざマージしようとしてみたらコンフリクトしている…なんてこともしばしばです。

<img width="765" alt="スクリーンショット 2019-12-10 9.39.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/a430b0b5-cb4b-8a83-6f5a-79f9357aab84.png">

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/75e270d9-68c7-7591-1b90-649a5221d290.png)

## Runnerのスペック

この情報は常に更新される可能性がありますが、ひとまず cpuinfo と meminfo の試した限りの情報の一部です。

### CPU

```
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 79
model name	: Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz

processor	: 1
vendor_id	: GenuineIntel
cpu family	: 6
model		: 79
model name	: Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz
```

### RAM

```
MemTotal:        7112216 kB
MemFree:         4993948 kB
MemAvailable:    6146864 kB
Buffers:          108536 kB
Cached:          1143656 kB
SwapCached:            0 kB
Active:           710948 kB
Inactive:         938040 kB
Active(anon):     397304 kB
Inactive(anon):      292 kB
Active(file):     313644 kB
Inactive(file):   937748 kB
```

## Self hosted runners

GitLab CIでもできましたが、GitHub Actionsでも、利用者が用意したランナーマシンでActionsを実行することが可能なようです。

https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners

RunnerはWin, Mac, Linuxのクロスプラットフォーム対応で、クラウドサービスやローカルマシンをランナーとして動作させることが出来ます。
GitHub hostedなランナーでは性能不足だったり、同時に複数のCIを走らせたい場合にセルフランナーを追加すると良いでしょう。

### 試す

#### セルフランナーのセットアップ

まだセルフランナーについて、ドキュメントを読み込めてはいないので、できることできないことが曖昧な理解ですが、実際にセルフホストランナーを試してみます。
GCPで立ち上げたインスタンスに、GitHubのドキュメント通りにランナーをインストールします。

```shell
// Create a folder
$ mkdir actions-runner && cd actions-runner
// Download the latest runner package
$ curl -O https://githubassets.azureedge.net/runners/2.162.0/actions-runner-linux-x64-2.162.0.tar.gz
// Extract the installer
$ tar xzf ./actions-runner-linux-x64-2.162.0.tar.gz
```

```shell
// Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/tetsukay/github-actions-for-android --token ACA*********************SJA
```

そして、トークンを指定して（設定のドキュメントに自身のトークンがすでに埋め込まれています） `config` を実行するのですが

```
Libicu's dependencies is missing for Dotnet Core 3.0
Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies.
```

Dotnet Coreが必要というエラーが表示されました。
ご丁寧に解決法もエラーメッセージに含めてくれているので、Dotnetをインストールします。（sudo が必要です）

```shell
sudo ./bin/installdependencies.sh
```

その後、再度configとrunを実行します。

```shell
// Create the runner and start the configuration experience
$ ./config.sh --url https://github.com/tetsukay/github-actions-for-android --token ACA*********************SJA
// Last step, run it!
$ ./run.sh
```

ジョブを受け付ける状態になっており

```
yuta_tetsuka@github-actions-runner:~/actions-runner$ ./run.sh 
√ Connected to GitHub
                                                                                                                   
2019-12-12 01:33:41Z: Listening for Jobs
```

GitHubのランナーページにも表示されていました。

<img width="761" alt="スクリーンショット 2019-12-12 10.34.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/53308997-1015-6fd3-1d07-7c8704c773a5.png">

#### ワークフローでのランナーの指定

Workflowを記述しているyamlで `runs-on` の指定を `runs-on: [self-hosted, linux, ARM64]` のような形で記述します。
実行OSやアーキテクチャの指定が無ければ、単純に `runs-on: [self-hosted]` とするだけでも大丈夫です。

...と思って実行したのですが、

`2019-12-12 01:57:22Z: Running job: build` ジョブを検出してセルフランナーでジョブが動作したのは良い物の、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/49252781-a309-2f56-0318-0fd85945737d.png)

Gitが無いと怒られてしまいました。

そういえば、RunnerをGitHubとリンクさせたとき、どういうモードで動作するかの設定が特にありませんでした。
GitLab CIでは、紐付けの際に「Runner自身のシェル」「Docker」「VirtualBox」といった動作モードを選択することができ、 Dockerを選択した場合 `.gitlab-ci.yml(GitHub ActionsでいうWorkflow)` でDockerイメージを指定すると、そのイメージ上でビルドさせることが出来ます。
GitHubのセルフランナーではこの設定が無かったため、おそらくRunner自身のシェルで実行されているのでしょう。
今回作成したGCPインスタンスは、GitHub Runner以外の環境は導入していなかったため、Androidプロジェクトをビルドできる環境を整える必要があります。（が、この記事では割愛させていただきます）

Androidのビルド環境を設定しているとき、 [若干のトラブルにも遭いました](https://discuss.gradle.org/t/no-value-has-been-specified-for-property-lintclasspath/32203) が、無事ビルドをセルフランナーで実行させることが出来ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51949/d8068041-fe18-0908-c00a-8c72e7e44b50.png)

# 雑感

- 様々なワークフローのテンプレートが用意されているため、GitHub hostedなランナーを用いる場合はとても簡単
- GitHubのオプションはもう少し柔軟性がほしい
    - CI中のマージ不許可
    - CI完了後の自動マージがほしい
    - CIで生成されたテストレポートをGitHub Pagesなどにデプロイして閲覧したい
- セルフホストランナーは、まだ物たりない
    - Docker Executorがほしい
    - ジョブを並列で捌くため、オートスケールでVMを立てるような機能が欲しい

元々GitLab CIを使っていたところもあるのでどうしてもGitLabで出来たことが出来ないと物足りなさを感じてしまうのですが、まだまだActionsも出たばかりですので、これからの機能拡充を楽しみに待ちたいところです。

# 追記

@peaceiris さんにコメントでご指摘いただきました。ありがとうございます！

> CI中 / Fail時のマージ不許可 

[Status check](https://help.github.com/ja/github/administering-a-repository/enabling-required-status-checks) のブランチルールを設定することで、マージにCIの成功の制約を付けることが出来るようです。
~~しかし、実際に試したところ「Merge pull request」のボタンは有効化されたまま。
もう一度CIをFailさせてみたりしましたが無効化にはならず、実際にマージボタンをクリックしたらマージされてしまいました。
時間が取れ次第、もう少し詳しく探ってみるつもりです。~~

再度試したところ正常にマージをブロックできることを確認しました。

> CIで生成されたテストレポートをGitHub Pagesなどにデプロイして閲覧したい

[ActionsからGitHub Pagesにデプロイするツール](https://github.com/peaceiris/actions-gh-pages) がActionのWorkflowとして公開されているようです！