CircleCI 2.0でAndroidライブラリの自動デプロイ


最近リリースされたCircleCI 2.0でAndroidライブラリをデプロイするまでをやってみたので、そのまとめ。

##　概要
今回CircleCIに対応させたのは以下のライブラリ
[ChatMessageView](https://github.com/bassaer/ChatMessageView)

今回自動化したかったフローは
1. 修正をGitHubへPush
2. CircleCIでビルド＆テスト
3. Jfrog Bintrayにアップロード
4. GitHubに自動でタグをPush
5. READMEに記載しているライブラリのバージョンを書き換える

![circle-ci-deploy.png](https://qiita-image-store.s3.amazonaws.com/0/118567/3effd051-fa86-013d-9f6f-118c567d7c84.png)



## 手順

### 1. CircleCIに登録
GitHubのアカウントがあれば、連携は簡単。
https://circleci.com/

### 2. プロジェクトの追加
サインアップが完了すると、PROJECTSタブからGitHubにあるレポジトリが読み込まれるので選択して追加できる。
レポジトリを選択するとAndroidの場合、デフォルトでOSはLinux、Platformは2.0,LanguageはGradle(Java)になっているのでデフォルト設定のままでOK。

### 3. プロジェクトに```.circleci/config.yml```を追加
CircleCI 2.0では```.circleci```配下に```config.yml```を置いて、そこに設定を書く。
レポジトリ連携後にサンプルのymlファイルが書いてあるのでそれを雛形にするとよさそう。

今回の設定内容は以下
https://github.com/bassaer/ChatMessageView/blob/master/.circleci/config.yml

```yaml:config.yml
version: 2
jobs:
  build:
    branches:
      only:
        - master
        - develop
    docker:
      # specify the version you desire here
      - image: circleci/android:api-26-alpha

    environment:
      # Customize the JVM maximum heap limit
      JVM_OPTS: -Xmx3200m
      TERM: dumb

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          key: jars-{{ checksum "build.gradle" }}-{{ checksum  "chatmessageview/build.gradle" }}

      - run:
          name: update android sdk
          command: |
              echo y | android update sdk --no-ui --all --filter tool,extra-android-m2repository,extra-google-google_play_services,extra-google-m2repository,android-26
              echo y | android update sdk --no-ui --all --filter build-tools-26.0.0
      - run:
          name: gradle dependencies
          command: ./gradlew androidDependencies

      - save_cache:
          paths: ~/.gradle
          key: jars-{{ checksum "build.gradle" }}-{{ checksum  "chatmessageview/build.gradle" }}

      # run unit tests!
      - run:
          name: unit test
          command: |
              ./gradlew test
              ./gradlew lint test
      - run:
          name: Show list of system-images
          command: sdkmanager --list --verbose | grep system-images

      - run:
          name: Setup Emulator
          command: sdkmanager "system-images;android-24;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-24;default;armeabi-v7a"

      - run:
          name: Launch Emulator
          command: export LD_LIBRARY_PATH=${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/qt/lib && emulator64-arm -avd test -noaudio -no-boot-anim -no-window -accel on
          background: true

      - run:
          name: Wait emulator
          command: |
              circle-android wait-for-boot
              adb shell input keyevent 82
      - run:
          name: Run UI test
          command: ./gradlew example:connectedAndroidTest

      - run:
          name: Distribute Bintray
          command: |
              if [ $CIRCLE_BRANCH = "master" ]; then
                sh ./deploy.sh
              else
                echo "Bintray distribution was skipped."
              fi
      - run:
          name: Update version
          command: |
              if [ $CIRCLE_BRANCH = "master" ]; then
                sh ./version.sh
              else
                echo "Version update was skipped."
              fi
      - store_test_results:
          path: example/build/test-results

      - store_artifacts:
          path: example/build/reports
          destination: reports
```

#### 今回の主な設定
masterブランチとdevelopブランチ以外のプッシュではCircleCIが走らないようする。

```
branches:
  only:
    - master
    - develop
```
使用するAndroid APIのバージョンを設定

```
docker:
  # specify the version you desire here
  - image: circleci/android:api-26-alpha
```

ここからは実際に動作させたいタスクを以下のように記述していく

```
- run:
    name: タスク名
　　　　　　　　command: 実行したいコマンド

- run:
    name: 複数のコマンドがある場合
    command: |
       command1
       command2
      
```

sdkのアップデート
build.gradleで設定しているbuildToolsVersion等と合わせるるとよさそう。

```
- run:
    name: update android sdk
    command: |
      echo y | android update sdk --no-ui --all --filter tool,extra-android-m2repository,extra-google-google_play_services,extra-google-m2repository,android-26
      echo y | android update sdk --no-ui --all --filter build-tools-26.0.0
```

ユニットテストとLintチェックを実行

```
- run:
    name: unit test
    command: |
      ./gradlew test
      ./gradlew lint test
```

ここでは使えるsystem-imagesのリストを確認したかっただけなので本来は不要。

```
- run:
    name: Show list of system-images
    command: sdkmanager --list --verbose | grep system-images
```
emulatorを起動してEspressoでテストしたい場合は以下でemulatorの設定を行う。
今回は```armeabi-v7a```が使えるのは24までで、別のsystem-imageだと起動できないことがあるのでひとまず24でセットアップ。

```
 - run:
    name: Setup Emulator
    command: sdkmanager "system-images;android-24;default;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-24;default;armeabi-v7a"
```

エミュレータの起動。

```
- run:
    name: Launch Emulator
    command: export LD_LIBRARY_PATH=${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/qt/lib && emulator64-arm -avd test -noaudio -no-boot-anim -no-window -accel on
    background: true
```
エミュレータの起動に時間がかかるので待機。
スクリーンがロックされている場合があるのでadbのキーイベントを送って解除する。
[公式ページ](https://circleci.com/docs/1.0/android/)には、この方法よりテストコードにスクリーン設定を記述するほうがよさそうと書いてあるが、 テストコードに記述した場合にもemulatorに接続できず失敗することがあったので、ひとまず両方記述。

```
- run:
    name: Wait emulator
    command: |
      circle-android wait-for-boot
      adb shell input keyevent 82
```
UIテスト実行。結構時間かかる。

```
- run:
    name: Run UI test
    command: ./gradlew example:connectedAndroidTest
```
ここまででビルド&テストは終了。
あとはJfrog Bintrayにデプロイする。
このあたりの処理はシェルスクリプトにまとめておくと管理しやすい。
以下では、CircleCIで用意された環境変数 ```$CIRCLE_BRANCH``` で実行中のブランチがわかるので、masterでテストまで成功したら```deploy.sh```を実行する。

```
- run:
    name: Distribute Bintray
    command: |
      if [ $CIRCLE_BRANCH = "master" ]; then
        sh ./deploy.sh
      else
        echo "Bintray distribution was skipped."
      fi
```
最後に、デプロイまでできたら、READMEに記載されているバージョンの書き換えとタグをGitHubへプッシュする。
上記と同様に一連の処理をシェルスクリプトにまとめて実行するだけ。
バージョンは```build.gradle```でも読み込みやすいように```version.properties```というファイルで管理している。

```groovy:version.properties
version=1.4.0
```
READMEには以下のようにバージョン名を記述していたので、

```
dependencies {
    compile 'com.github.bassaer:chatmessageview:1.3.5'
}
```
バージョンの```1.3.5```の部分をsedコマンドで置換する。

```bash:version.sh
#!/bin/sh

VERSION=`cat version.properties | cut -d'=' -f2`

sed -i "/compile/s/[0-9]*\.[0-9]*\.[0-9]*/$VERSION/" ./README.md

```
以下は生成したタグとREADMEの変更をGitHutへプッシュする処理。
ポイントはコミットメッセージに```[ci skip]```を含めること。
これによってCircleCIが回り続けることを回避できる。

```bash:version.sh
# 続き

REPOSITORY="git@github.com:${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}.git"

git config --global user.name "CircleCI"
git config --global user.email "app.nakayama@gmail.com"

git add ./README.md
git commit -m "update version [ci skip]"

git tag -a $VERSION -m "version $VERSION"

git push origin master

git push origin --tags
```

### 4. SSHキーの設定
Circle CI連携時にGitHubの対象レポジトリに自動で読み取り専用の鍵が設定されるが、書き込みも行いたい場合は、
```PROJECTS``` > ```対象プロジェクトのSettings``` > ```SSH Permissions``` > ```Add SSH Key```
から鍵の追加を行うことができる。

### 5. Status Budge
```PROJECTS``` > ```対象プロジェクトのSettings``` > ```Status Budges```
からステータスバッジを生成してくれるのでREADMEなどに記載できる。
[![CircleCI](https://circleci.com/gh/bassaer/ChatMessageView.svg?style=svg)](https://circleci.com/gh/bassaer/ChatMessageView)

## 最後に
1.0からすこし書き方が変わっているので以下を参考にすると2.0へ移行しやすい。
https://circleci.com/docs/2.0/migrating-from-1-2/

emulator周りの設定で、同じ設定なのにemulatorが起動できなかったり、テストが失敗することがあった。
ダッシュボードからもう一度走らせると通ったりするので、謎。🤔
今回はひとまずデプロイまで自動でできる設定を行ったが、CircleCIはpushされたブランチをビルドするみたいなので、JenkinsみたいにPullRequestをトリガーに起動する方法もあるのか調査したい。