CircleCIとDeployGateでAndroidアプリのリリース作業を自動化してみた

f:id:vasilyjp:20180927112657j:plain

 

みなさんはAndroidアプリのリリース作業を自動化していますか? 2014年GooglePlayベストアプリを受賞した弊社のファッションアプリ「iQON」では、リリース作業をCircleCIとDeployGateで自動化しています。今回、どのように自動化したのかを、昨年11月からVASILYで働き始めた堀江(@Horie1024)がご紹介しようと思います。

概要

iQONの開発フローは、PullRequestベースで行われており、開発が完了したコードをreleaseブランチに随時PullRequestを送りながら開発を進めています。

  1. releaseブランチを作成
  2. releaseブランチにPullRequest
  3. 問題が無ければPullRequestをマージ
  4. 1~3を繰り返す

そして、リリース作業は以下のような手順で行っていました。

  1. releaseブランチをmasterブランチへPullRequest
  2. PullRequestをマージ
  3. masterブランチからAPKを作成
  4. メールでAPKを社内配布
  5. 最終確認
  6. GooglePlayにアップロード
  7. リリース完了 

作業手順としては難しくないのですが、頻繁にあるリリースの度に作業が発生するのでとても面倒でした。

実現したいこと

今回、自動化することで以下の2項目を実現したいと思います。

  • APK作成の自動化
  • 社内への配布の自動化

どう実現するか

サンプルプロジェクト

解説用にサンプルプロジェクトを用意しました。 https://github.com/horie1024/CircleCISample

全体の流れ

  1. GitHubへ差分をPush
  2. CircleCIがビルドを開始
  3. Slackへビルドの成否を通知
  4. DeployGateへAPKをアップロード
  5. 検証端末にDeployGateアプリ経由でAPKを配信
  6. APKをインストール

APK作成の自動化

コマンドでのAPK作成

AndroidStudioでのプロジェクトのビルド

iQONの開発には、AndroidStudioを使用しています。AndroidStudioでは、IDEとビルドシステムが疎結合になっているため、コマンドでのビルドが容易に行えます。

ビルドシステムには、Gradleが使われており、Android Gradle Pluginを使用してAndroidプロジェクトのビルドを行います。Gradleについてのより詳しい情報は、Gradle 日本語ドキュメント、Android Gradle Pluginについては、Gradle Plugin User Guideをご覧ください。

ビルドスクリプト

AndroidStudioでプロジェクトを新規作成すると、以下の2つのbuild.gradleがあります。

  • Project直下のbuild.gradle
  • app module内のbuild.gradle

app module内のbuild.gradleにAndroidプロジェクトをビルドするための設定が含まれています。

assembleタスク

新規作成したAndroidStudioでプロジェクトには、gradlewという実行形式ファイルが含まれており、gradlewを使いGradleのビルドを実行することが推奨されています。

gradlewでビルドを実行する場合、Gradleが自動でダウンロードされ、それを使用してビルドが実行されます。より詳しい解説はこちらをご覧ください。 Gradleによるビルドは、プロジェクト単位で一つ以上のタスクで構成されており、プロジェクトが実行可能なタスクの一覧は、tasksで表示できます。

$ ./gradlew tasks

タスク一覧の中に、Build tasksがあり、APKを作成するには、assembleタスクを実行します。

    Build tasks
    ----------- 
    assemble - Assembles all variants of all applications and secondary packages. 
    assembleDebug - Assembles all Debug builds 
    assembleDebugTest - Assembles the Test build for the Debug build 
    assembleRelease - Assembles all Release builds build - Assembles and tests this project. 
    buildDependents - Assembles and tests this project and all projects that depend on it. 
    buildNeeded - Assembles and tests this project and all projects it depends on. 
    clean - Deletes the build directory. 

Androidプロジェクトは、デフォルトでdebug APKとrelease APKの2種類の出力を持っていて、assembleタスクを実行すると両方を出力します。出力を分割するには、assembleDebugassembleReleaseタスクを利用します。今回は、GooglePlayへアップロードできる状態でAPKを作成したいので、assembleReleaseタスクでAPKを作成します。

ターミナルからのAPK作成

実際にターミナルからassembleReleaseタスクを実行すると、app/build/outputs/apk以下にAPKが出力されるのが確認できます。このassembleReleaseタスクを自動的に実行することで、APKの作成を自動化します。

 $ ./gradlew assembleRelease

署名の設定

サンプルプロジェクトでは、設定していませんが、本来であればassembleReleaseでAPKを作成する場合には署名を付けます。Releaseビルドで署名を付けるには、app module内のbuild.gradleのコードを以下のようにします。詳細はこちらをご覧ください。

リリース作業手順の確認

リリース作業の手順を確認し、自動化する箇所を洗い出します。

  1. releaseブランチをmasterブランチへPullRequest
  2. PullRequestをマージ
  3. masterブランチからAPKを作成
  4. メールでAPKを社内配布
  5. 最終確認
  6. GooglePlayにアップロード
  7. リリース完了

目的はAPK作成の自動化ですので、1~3の手順に注目すると、「PullRequestのマージによってmasterブランチに変更が生じたらAPKを作成する」とまとめられます。これをCircleCIを利用して自動化します。

CircleCI

CiecleCIのようなCI as a Serviceでは、GitHubとの連携が前提になっているので、GitHubに差分がPushされたのをトリガーに、任意のコマンドを実行するといったタスクを簡単に定義できます。実際にCircleCIにどのような挙動をさせるかの設定は、circle.ymlという設定ファイルに書き、CI対象のリポジトリに設置します。

circle.yml

circle.ymlの構造は6つのセクションに分かれており、上から順に実行されます。また、これらのセクション全てについて設定を書く必要はありません。

  • machine : VMの設定の調整
  • checkout : gitリポジトリからのclone
  • dependencies : ビルドの依存関係の解決
  • database : テストのためのdatabaseの準備
  • test : テストの実行
  • deployment : デプロイの実行

各セクションは、bashコマンドのリストを持て、実行したいbashコマンドを指定していきます。もし特にコマンドを指定しない場合、CircleCIがリポジトリのコードから推測したコマンドが実行されます。コマンド実行時のオプションとして、以下3種類を選択できます。

  • pre : CircleCIが推測したコマンドより前に実行
  • override : CircleCIが推測したコマンドを独自に定義したコマンドで上書き実行
  • past : CircleCIが推測したコマンドより後に実行

これら6つのセクションと3種類のオプションを覚えておけば、circle.ymlは問題なく書けると思います。より詳しい解説は、Configuring CircleCIをご覧ください。 次に、実際にAndroidプロジェクトをビルドするcircle.ymlを書いてみます。

Androidプロジェクトのビルド

Test Android applicationsを参考に書いていきます。checkoutセクションとdatabaseセクションについては、必要が無いので設定を書きません。

▪︎VMの設定

ビルドを実行するには、ANDROID_HOMEの定義が必要になるため、circle.ymlのmachineセクションで定義します。environmentセクションで環境変数を定義でき、VMの/usr/local/android-sdk-linuxにAndroid SDKが用意されてるので、そのPathを指定します。また、Javaのバージョンをopenjdk7に指定します。

machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux

▪︎sdkのアップデート

VMに用意されているAndroid SDKは初期状態のため、必要に応じてアップデートします。例えば、CI対象Androidプロジェクトのbuild.gradleの設定が、以下のようになっていた場合

    android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"
}

circle.ymlにSDK 21とBuild-tools 21.1.2をインストールするよう追記します。overrideとしてるのは、CircleCIが推測して実行する、gradle dependenciesによってビルドが失敗するためです。

machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"

サポートライブラリや、GooglePlayServicesライブラリを使用する場合、filterに以下のidを追加します。

"extra-google-m2repository,extra-android-m2repository"

また、filterに指定できるidは、以下のコマンドで確認できます。

$ android list sdk --all --extended

▪︎testセクション

testセクションは、必ず実行されてしまうため、overrideでダミーの処理を行うようにします。

machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"
test:
  override:
    - echo "Nothing to do here"

Robolectric Gradle Pluginを導入している場合、testセクションでテストを実行します。

test: override: - ./gradlew test

▪︎APKの作成

deploymentセクションでAPKを作成します。deploymentセクションは、複数のサブセクションを持てます。サブセクションは、branchセクションを持ち、差分が生じたブランチが、指定したbranchにマッチした場合に、commandsを実行します。より詳しい解説は、こちらを参照してください。

    machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"
test:
  override:
    - echo "Nothing to do here"
deployment:
  master:
    branch: master
    commands:
      - ./gradlew assembleRelease

▪︎ビルドできるか試す

CircleCIで実際にAndroidプロジェクトをビルド出来るか確認します。

  • CircleCIにログイン・サインアップ   GitHubのアカウントでログインもしくはサインアップします。
  • Add Projectsをクリック   左側のメニューのAdd Projectsをクリックします。
  • CIの対象とするプロジェクトの選択   1でアカウントを選択し、2でCIの対象とするプロジェクトを選択します。
  • 初回ビルドの実行   プロジェクトを選択すると初回のビルドがスタートします。初回ビルドでは、circle.ymlが不正だったためビルドが失敗しました。
  • masterブランチに変更をPush
    masterブランチでcircle.ymlを修正し、再度Pushするとビルドがスタートします。circle.ymlが正しく修正されたため、無事ビルドが成功しました。

成果物

Androidプロジェクトがビルドでき、APKを作成できました。次にAPKをGooglePlayにアップロードできるよう、作成したAPKをダウンロードできるようにします。

CircleCIでは、ビルドを実行した結果発生する成果物は、Build artifactsとしてビルド毎にダウンロードできます。Build artifactsとしてダウンロードするには、ビルド成果物を保存する必要があります。 成果物を保存するには、generalセクションのartifactsセクションに成果物のパスを指定します。generalセクションは、6つのセクションとは別に、特定のセクションに属さない、ビルド全体に関する設定を書くことができるセクションです。

general:
  artifacts:
    - "app/build/outputs/apk/app-release-unsigned.apk"
machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"
test:
  override:
    - echo "Nothing to do here"
deployment:
  master:
    branch: master
    commands:
      - ./gradlew assembleRelease

masterブランチでのみタスクが実行されるようにする

ここまでで、GitHubでPullRequestをマージもしくは変更をPushすることで、CircleCIのタスクが実行されるようになりました。ですが、master以外のブランチへPushした場合でもビルドが実行されてしまいます。そこで、masterブランチへのPushでのみビルドが実行されるようにします。 Specifying branches to buildを参考に、circle.ymlに設定を追加します。generalセクションにbranchesを追加することで、masterブランチへのPushでのみビルドが実行されるようになります。

general:
  branches:
    only:
      - master
  artifacts:
    - "app/build/outputs/apk/app-release-unsigned.apk"
machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"
test:
  override:
    - echo "Nothing to do here"
deployment:
  master:
    branch: master
    commands:
      - ./gradlew assembleRelease

以下のように、testブランチをPushしても「Not Run」となります。

Slack連携

Slackと連携させることで、ビルドの成功・失敗を通知させることができます。

  1. SlackのConfigure IntegrationsからCircleCIを選択
  2. 通知したいchannelを選択 channelを選択するとWebhook URLが得られます。
  3. CircleCIのProject SettingsでWebhook URLを登録 Notifications > Chat NotificationsからSlackを選び、Webhook URLを入力後、Saveをクリックします。

OOM問題

自動化してしばらく運用した中で実際に起こった問題なのですが、build-toolsのバージョンを21に上げたタイミングでOut Of Memoryでビルドが頻繁に失敗するようになりました。これは既知の問題のようで、CircleCIのドキュメントにもOOM killer ranとして載っています。 Androidプロジェクトで発生した場合の対策ですが、プロジェクトによって原因が異なる可能性があるので確実ではありませんが、Gradleのバージョンを2系に上げることで改善できました。

社内への配布の自動化

DeployGateの利用

社内へのAPKの配布には、DeployGateを利用します。DeployGateは、APKをアップロードすることで、専用アプリを介して開発中のアプリを簡単に配布できるサービスです。

DeployGateでのアプリの配布方法

DeployGateでのアプリの配布方法は、サインアップ後のチュートリアルがわかりやすく、すぐに理解できました。また、こちらの資料も参考になりました。

DeployGate APIの利用

社内への配布を自動化するにあたり、DeployGate APIのPush APIを利用することで、cURLコマンドで簡単にAPKをアップロードできます。

$curl -F "file=@sample.apk" -F "token=YOUR_API_KEY" -F "message=sample" https://deploygate.com/api/users/YOUR_USER_NAME/apps

APIキーの取得

APIを利用するには、APIキーが必要になります。APIキーは、DeployGateへ新規登録することで発行されます。新規登録後に、https://deploygate.com/settings#apiKeyにアクセスすることで確認できます。

ターミナルでの実行

Macのターミナルから実行し、DeployGateにアップロードされることを確認します。DeployGateのダッシュボードにアップロードしたアプリが表示されれば成功です。

$ curl -F "file=@PATH_TO_YOUR_APK" -F "token=YOUR_API_KEY" -F "message=sample" https://deploygate.com/api/users/YOUR_USER_NAME/apps

CircleCIでのビルドから社内配布までの流れの詳細

実際の配布の流れは、以下の通りです。

  1. CircleCIがAPKを作成
  2. CircleCIがDeployGateへAPIを使いAPKをアップロード
  3. DeployGateがDeployGateアプリへ配信
  4. 検証端末でDeployGateアプリを起動し、APKをダウンロード・インストール

DeployGateからの配信を受けるには、DeployGateアプリをインストールしておく必要があります。

CircleCIとの繋ぎこみ

CircleCIからコマンドを実行し、APKをDeployGateへアップロードします。masterへのPushからAPKがDeployGateへアップロードされるまでの流れは、以下のようになるはずです。

  1. masterへ差分をPush
  2. CircleCIが検知し、APKを作成
  3. DeployGateへのアップロードを実行

したがって、circle.ymlを以下のように記述します。

general:
  branches:
    only:
      - master
  artifacts:
    - "app/build/outputs/apk/app-release-unsigned.apk"
machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"
test:
  override:
    - echo "Nothing to do here"
deployment:
  master:
    branch: master
    commands:
      - ./gradlew assembleRelease
      - curl -F "file=@app/build/outputs/apk/app-release-unsigned.apk" -F "token=YOUR_API_KEY" -F "message=sample" https://deploygate.com/api/users/YOUR_USER_NAME/apps

deploymentのcommandsで、APKを作成とAPKのDeployGateへのアップロードを実行しています。ここで、APIキーが直書きになってしまっているので、CircleCIの環境変数に登録し、それを参照するようにします。

CircleCIの環境変数への登録

Project SettingsのTweaks > Environment variablesから設定します。NameとValueというformがあるので、NameにDEPLOY_GATE_API_KEY、ValueにAPIキーを入力し、Save variableをクリックします。するとDEPLOY_GATE_API_KEYとして環境変数が登録されます。 登録した環境変数を使用するように書き換えます。

general:
  branches:
    only:
      - master
  artifacts:
    - "app/build/outputs/apk/app-release-unsigned.apk"
machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"
test:
  override:
    - echo "Nothing to do here"
deployment:
  master:
    branch: master
    commands:
      - ./gradlew assembleRelease
      - curl -F "file=@app/build/outputs/apk/app-release-unsigned.apk" -F "token=${DEPLOY_GATE_API_KEY}" -F "message=sample" https://deploygate.com/api/users/YOUR_USER_NAME/apps

このように、登録した変数は、circle.ymlから参照できます。

コマンドの別ファイルへの切り出し

circle.ymlを見やすくするために、コマンドを別ファイルにまとめてしまいます。

./gradlew assembleRelease
 
curl -F "file=@app/build/outputs/apk/app-release-unsigned.apk" -F "token=${DEPLOY_GATE_API_KEY}" -F "message=sample" https://deploygate.com/api/users/YOUR_USER_NAME/apps

その結果、最終的なcircle.ymlは、以下のようになります。

general:
  branches:
    only:
      - master
  artifacts:
    - "app/build/outputs/apk/app-release-unsigned.apk"
machine:
  java:
    version: openjdk7
  environment:
    ANDROID_HOME: /usr/local/android-sdk-linux
dependencies:
  override:
    - echo y | android update sdk --no-ui --filter "android-21,build-tools-21.1.2"
test:
  override:
    - echo "Nothing to do here"
deployment:
  master:
    branch: master
    commands:
      - ./deploy.sh

動作確認

うまくいけば、DeployGateアプリにビルドしたAPKが配信されます。

また、ArtifactsからAPKをダウンロードできます。

自動化した結果

APKの作成から社内配布まで自動化することができました。PullRequestのマージが行われるとAPKが配布されるようになり、リリース作業がかなり効率化されました。

自動化前

  1. releaseブランチをmasterブランチへPullRequest
  2. PullRequestをマージ
  3. masterブランチからAPKを作成
  4. メールでAPKを社内配布
  5. 最終確認
  6. GooglePlayにアップロード
  7. リリース完了

自動化後

  1. releaseブランチをmasterブランチへPullRequest
  2. PullRequestをマージ
  3. CircleCIがAPKを生成するタスクを実行
  4. DeployGateを介して社内配布
  5. 最終確認
  6. CircleCIからAPKをダウンロード
  7. GooglePlayにアップロード 7. リリース完了

まとめ

CircleCIとDeployGateを利用して、リリース作業を自動化してみました。昨年11月から実際のリリース作業に組み入れ運用していますが、思っていた以上にストレスが減り、快適に開発を行えるようになりました。チームメンバーからの評判も良く、やってみて良かったと思います。

しかし、まだまだ改善できる点があり、継続して改善していこうと考えています。現在、GooglePlayへのAPKのアップロード自動化を組み込んだ開発フローおよびリリース作業手順をテスト中です(実験した内容はQiitaに上げました)。また、今回紹介出来なかった内容もありますので、近日中にご紹介できればと思います。

最後に

VASILYでは、自動化や新しい技術が大好きエンジニアを大募集しています!少しでもご興味のある方は是非オフィスに遊びにいらしてください! また、1月28日にSmartNewsさん、Wantedlyさんとベストアプリ勉強会という勉強会を開催します。私も会場にいますので、お気軽にお声掛けください! 募集要項 連絡先:info[at]vasily.jp

カテゴリー