LoginSignup
9
5

More than 5 years have passed since last update.

CircleCI 2.0でAndroidのアプリをビルドしつつ、コードレビューBOTも動かす

Posted at

動機

以前 Androidのコードを自動で解析し、GitHubのpull requestにコメントする という記事で、CircleCI 1.0にて Saddler を利用して自動レビューを行いました。

ただ、CircleCI 2.0が正式にリリースされ、 「CircleCI 2.0を使うようにするだけで、こんなに速くなるとは夢にも思わなかった! | Tokyo Otaku Mode Blog」という記事もあり、CircleCI 2.0を使ってみようと思いました。

うまくいきはしましたが、いくつかの壁があったので、そのあたりをまとめようかと思います。

TL;DR

https://github.com/noboru-i/SlideViewer/pull/72 のように変更したら、 https://circleci.com/workflow-run/fbc9c6bf-e7bd-4c8c-a2d0-65c98b9f92be のようにビルド・コードレビューが成功しました。

(実際には、その後に https://github.com/noboru-i/SlideViewer/pull/74 も入れ、 https://circleci.com/workflow-run/ae5fe2f5-8b45-47ef-a372-9532a421862e にてdeploy jobも含めて成功しました。)

いくつかの壁

壁1:Android SDKの再配布について

CircleCI側で用意されたdockerイメージはありますが、Saddlerを実行するにはRubyの実行環境が必要となります。

そのため、用意されているdockerイメージに、Rubyを追加インストールしたdockerイメージを作ることを思いつきました。

ただ、公開用DockerイメージにAndroid SDKを含めるのはライセンス違反という話 - Islands in the byte stream という話もあり、カスタムしたdockerイメージをpublicに公開するのは、やめたほうが良さそうです。
(gfxさんの「削除予定のイメージ」はまだ削除されてないのですが、実は状況が変わった。。。?)

そのため、privateイメージとして作成する必要がありそうです。

壁2:CircleCI 2.0でprivateイメージを使う

Using Private Images に記載があるのですが、publicなイメージを使うときとはやり方がかなり違っています。

カスタムイメージをDocker Hubにprivateとしてpushし、いろいろやってみたのですが、ビルドすら上手く行かなかったです。。。
このへんでいろいろやってみました。。)
私のDocker力が足りないのも原因かと思います。。。

壁3:複数Dockerイメージを指定する

上記のやり方は一旦諦め、CircleCI側が用意してくれたDockerイメージを利用しつつ、Rubyのイメージも同時に起動し、Rubyの処理はそちらのコンテナにやってもらおうと思いました。

Specifying Container Images などに複数imageを利用した例もありますが、データベースなどの外部からアクセスする例ばかりで、 docker exec のようなシェルの実行する例がなく、どのように処理させるかはわかりませんでした。。

やりたかったのは、下記のようなイメージ。

version: 2

jobs:
  build:
    working_directory: ~/code
    docker:
      - image: circleci/android:api-26-alpha
      - image: circleci/ruby:2.4.1
    steps:
      - checkout
      - run:
          name: Check lint
          command: ./gradlew :app:check -x test
      - run:
          name: Saddler
          command: docker exec -it circleci/ruby:2.4.1 sh scripts/saddler.sh

なのですが、成果物をコンテナ間で渡したり、execする際のコンテナの指定がよくわからなかったので、試してすらいないです。。。

今回の解決策

workflows を利用し、Saddlerの実行のみrubyのコンテナを利用するようにしました。

下記のような3つのjobを定義し、workflowで順序などを制御しました。

  • build: gradleのスクリプトを実行し、apkの作成、各種チェック結果のXML作成を行います。
  • check: Saddlerを実行し、前のjobで作成したXMLを読み込み、GitHubにコメントを追加します。
  • deploy: masterブランチのみ実行し、Releaseビルドのapkファイルをartifactsに保存します。

一つずつ見ていきます。

defaults

と、いきなりjobではないものです。。

buildとdeployでは同一のdockerイメージを利用します。
そのため、 defaults として共通化しておきます。

defaults: &defaults
  working_directory: ~/code
  docker:
    - image: circleci/android:api-26-alpha
  environment:
    JVM_OPTS: -Xmx3200m

おおよそ、CircleCIの公式 https://circleci.com/docs/2.0/language-android/ に記載があるとおりです。

build

前述の defaults を利用し、gradleタスクの実行などを行っていきます。

  build:
    <<: *defaults
    steps:
      - checkout
      - restore_cache:
          key: jars-{{ checksum "build.gradle" }}-{{ checksum  "app/build.gradle" }}
      - run:
          name: Download Dependencies
          command: ./gradlew androidDependencies
      - save_cache:
          paths:
            - ~/.gradle
          key: jars-{{ checksum "build.gradle" }}-{{ checksum  "app/build.gradle" }}
      - run:
          name: Decrypt google-services.json
          command: openssl aes-256-cbc -k $DECRYPT_KEY -d -in app/encrypted_google-services.json -out app/google-services.json -md md5
      - run:
          name: Check licenses
          command: ./gradlew :app:checkLicenses
      - run:
          name: Check lint
          command: ./gradlew :app:check -x test
      - run:
          name: Build apk
          command: ./gradlew :app:assembleDebug
      - store_artifacts:
          path: app/build/outputs/apk/app-debug.apk
          destination: app-debug.apk
      - store_test_results:
          path: app/build/test-results
      - persist_to_workspace:
          root: ~/code
          paths:
            - .

こちらも公式に記載のあるように、キャッシュ、gradleタスクの実行、必要なartifactsの保存などを行っています。

Decrypt google-services.json については、 Openssl 1.1.0でもファイルを復号化する - Qiita に記載していたように、「暗号化してGit管理しているファイル」を復号化しています。

persist_to_workspace は、次のcheck jobで成果物を利用するため、ワークスペースごと引き継ぎます。
参考: https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs

check

Rubyのコンテナを利用し、Saddlerを実行します。

  check:
    working_directory: ~/code
    docker:
      - image: circleci/ruby:2.4.1
    steps:
      - attach_workspace:
          at: ~/code
      - restore_cache:
          key: saddler-gems-{{ checksum "Gemfile.lock" }}
      - run: bundle install --path vendor/bundle
      - run:
          name: Run saddler
          command: sh scripts/saddler.sh
          environment:
              CIRCLE_ARTIFACTS: "/tmp/circle_artifacts"
      - save_cache:
          paths:
            - vendor/bundle
          key: saddler-gems-{{ checksum "Gemfile.lock" }}
      - store_artifacts:
          path: "/tmp/circle_artifacts"

まず、 build の結果を利用するために attach_workspace を記載しておきます。

また、 bundle install を高速化するため、キャッシュの設定などもしておきます。
(ただ、今回に関しては6sec程度でインストールが完了してしまうため、あまり効果はありませんでした。。。)

その後、Saddlerの実行手順などが記載されているシェルスクリプトを実行します。
シェルスクリプトの中身については、 https://github.com/noboru-i/SlideViewer/blob/71fbbf93f1981e2fa7f40ddd0b8153931a771d8d/scripts/saddler.sh を直接御覧ください。
ほぼ、 Androidのコードを自動で解析し、GitHubのpull requestにコメントする - Qiita で記載したものと同一ですが、gemのインストールやgradleタスクの実行は外に出してあります。

一応、利用したXMLファイルなどをartifactsに保存してあります。

deploy

gradleタスクを実行し、release用のapkの作成などを行います。

  deploy:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/code
      - run:
          name: Build apk
          command: ./gradlew :app:assembleRelease
      - store_artifacts:
          path: app/build/outputs/apk/app-release.apk
          destination: app-release.apk
      - store_artifacts:
          path: app/build/outputs/mapping/release/dump.txt
          destination: dump.txt

上で説明していた部分と、ほとんど同じかと思います。

workflows

build -> checkとシーケンシャルに実行していきます。
また、masterブランチの場合のみ、そのあとにdeploy jobも実行します。

YAMLファイルとしては、下記のようになります。

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - check:
          requires:
            - build
      - deploy:
          requires:
            - build
            - check
          filters:
            branches:
              only: master

まとめ

上で説明したYAMLファイルの全体像としては、 https://github.com/noboru-i/SlideViewer/blob/71fbbf93f1981e2fa7f40ddd0b8153931a771d8d/.circleci/config.yml にあります。

今回は、workflowを利用して、Android用のイメージとRubyのイメージを行ったり来たりすることで、Rubyのスクリプトを利用した自動レビューを行いました。

まだ全然安定していないですし、以前はdangerを使っており今回はSaddlerを使っているので評価しづらいですが、ビルド時間に関しては劇的にはやくなりました。
(以前は10分以上かかっていたものが、4分程度で終わるようになった。)

image.png

CircleCI 2.0で利用しているインフラの性能は、1.0のときより良さそうです。

ちなみに

dangerをCircleCI上で使ってAndroidのコードを精査する (FindBugs/Android Lint) ではdangerに言及してましたが、上記で書いたようにRubyからgradleのタスクを呼ぶのがめんどうな印象だったので、今回は避けました。

各プラグインで、gradleタスクを実行せず、受け取ったxmlファイルのパスから指摘できるようになったら、再度考えようかと思います。

9
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5