Android OSSライブラリのCI環境をセットアップする

ほぼ初めてAndroid向けのCI環境をセットアップしたので、試行錯誤した作業手順を書いておこうと思う。
今回セットしたCIの内容は、JVMテストとカバレッジの計測で作業手順は以下のようになった。

  1. DockerfileでCI環境を定義
  2. Dockerfileとcloudbuild.yamlを含むレポジトリをgithubに作成
  3. Google Container Registory(GCR)にプロジェクトを作成
  4. レポジトリにpushするとGCR上でビルド&ホストされる
  5. wercker.ymlをライブラリのレポジトリに追加
  6. werckerにプロジェクトを作成
  7. pushすると自動でCIが走るようになる
  8. jacocoをセットアップ
  9. coverallsにプロジェクトを作成
  10. coveralls-gradle-pluginを導入し、coverallsのAPIキーをwerckerに設定
  11. wercker.ymlを編集しカバレッジをcoverallsに送信する

(本エントリのはAutomation with Wercker and Container Builderを元にして書かれています。 Keishin Yokomakuさんありがとうございました)

DockerfileでCI環境を定義する

AndroidのプロジェクトはCI as a Serviceが提供する環境を利用するのは難しい。理由はSDKに含まれるライブラリ/ツールの更新があった場合、CIサービス側のサポートを待つ必要が生まれるためだ。 その為Dockerの実行をサポートするCIサービスを使って、自分で定義した環境でCIを動かす選択肢を選んだ。
Dockerfileを一から書くのは大変なのでYokomakuさんが公開されているDockerfileをforkした。 Dockerは基本的にはshell scriptなので、すこし書き換えるくらいならDocker知らなくてもどうにかなる。

Dockerfileとcloudbuild.yamlを含むレポジトリを作成

先程作ったDockerfileとGCRの設定ファイル(cloudbuild.yaml)を含めたレポジトリを作る。cloudbuild.yamlはなくても動くのだけど、ほぼ確実にtimeout(default:10min)に引っかかる。

timeout: 40m
steps:
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '--tag=gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA', '.']
images: ['gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA']

参考: github.com

GCRにプロジェクトを作成

Google Cloud Platform(GCP)のコンソールを開き、新規プロジェクトを作成する。

プロジェクトを作ったらサイドメニューからContainer Registoryを選択する。初回は「Container Registry は有効でありません」と出るので有効にする。

トリガーを作成を選択 -> ソースを選択[github.com] -> 認証 -> レポジトリを選択[先程作ったレポジトリ] -> トリガー設定を行う。

トリガーを作成したら、試しに動かしてイメージが正常に作成されるか確認する。
(余談: Dockerのビルドは手元でやる選択もありますが、普段使わないのに手元にDocker環境をセットアップしたくない、適切にクリーンしないとストレージが圧迫されて辛い。などの理由でリモートビルドしています。)

wercker.yamlをレポジトリに追加する

以下を参考にwercker.yamlを作成する。

build:
  box: 
    id: gcr.io/{your_project}/{your_repository}
    username: _json_key
    password: $GCR_JSON_KEY_FILE
    registry: gcr.io
    tag: {your_container_hash}
  steps:
    - script:
        name: run test
        code: |
          ./gradlew --project-cache-dir=$WERCKER_CACHE_DIR {library_module}:testDebugUnitTest -PdisablePreDex

idやtagは、GCRのビルド履歴画面を参考に埋めることが出来る。(id: イメージ ターゲット, tag: イメージタグ)
DockerイメージをprivateにしているのでJSON_ファイルを利用した認証を行う。高度な認証方式 を参考にサービスアカウントからJSONキーを発行する。

JSONファイルを手に入れたら、wercker側の環境変数に追加する。

Werckerのプロジェクトを作成

werckerのトップから Create->Applicationを選択する。Werckerのプロジェクト作成は驚くほど簡単なので詳細な説明は省略するが、ポチポチしてれば勝手にhookが設定され自動でCIが走るようになる。

jacocoをセットアップする

jacocoはJavaのコードカバレッジツールの一つで、唯一Java8のコードがまともに計測できる。 Androidで使う場合にはけっこう大変だったが、Droidkaigiのconference-app-2017レポジトリが参考になった。
私がハマった点は、testCoverageEnabled true を忘れていた点とRobolectricを利用している場合古いjacocoを利用する必要がある点だった。これらを忘れるとカバレッジ0%でずっと悩み続けることになる。
参考までに対応した際のcommitを貼っておく。 kazy1991/PrefKit -Setting jacoco

coveralls-gradle-pluginのセットアップ

coverallsカバレッジ結果をwebUIで可視化してくれるサービスである。jacocoにも対応しているので生成したxmlをcoverallsに送る手順を追加する。
coveralls側でプロジェクトを作成するとAPIキーが手に入るので、werckerの環境変数に追加しておく。

プロジェクト直下のbuild.gradleにcoveralls-gradle-pluginを追加する。

dependencies {
  //..
  classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1'
}

ライブラリ側のbuild.gradleでxmlの場所を指定する。

coveralls {
    jacocoReportPath = "{your_xml_path}/{file_name}.xml"
    // jacocoReportPath = "build/reports/jacoco/xml/prefkit.xml"
}

wercker.ymlを編集して、カバレッジを送るようにする。

-   ./gradlew --project-cache-dir=$WERCKER_CACHE_DIR {library_module}:testDebugUnitTest  -PdisablePreDex
+   ./gradlew --project-cache-dir=$WERCKER_CACHE_DIR {library_module}:jacoco {library_module}:coveralls -PdisablePreDex

coveralls-gradle-pluginは、xmlファイルが見つからなくてもエラーにはならない。coveralls側に反映されないときはwerckerのログを読んでみると良いと思う。
このブログを書いていて気づいたが、coverallsよりcodecov.ioのほうがモダンな見た目なのでこっちに乗り換えるかもしれない

[追記]

codecovに乗り換えた。ハマリポイントとしてはcodecovが認識できるファイル名が-name 'jacoco*.xml' なのでファイル名をデフォルトから変更している場合はちゅういがひつようだった。対応PRはこちら。 Use codecov by kazy1991 · Pull Request #5 · kazy1991/PrefKit · GitHub

まとめ

AndroidのCI環境のセットアップはとにかく手順が多く、そんなに頻繁にはやる作業でもないので忘れてないうちにブログにまとめた。 環境に作ったことに満足してしまってまだテストは一つも書いてない。