環境構築

CIを活用したAndroidアプリの開発

こんにちは、インフラエンジニアと紹介し続けてきたトシです。
実はAndroidアプリも担当しています。
インフラエンジニアとしての業務以外にも、PHPを使用したWeb側の実装や社内サーバーの運用、社内資産管理などのSE業務もしています。
社内の何でも屋と化している感があります。

さて今回はGitLab CIを活用して、Androidアプリ開発の面倒な作業を自動化したお話をご紹介いたします。

GitLab CIで何を行なうのか


Android開発でのCIを解説していく前に、まずは以前の私の記事をご確認ください。CIについての説明を書いています。

今回、われわれがAndroidアプリ開発にCIを導入しようという決断にいたった理由は、プログラムの品質を上げるためです。そこで、CIを活用して、以下のチェックを行なうことにしました。
結果的には、プログラミング以外の配布も自動化できて、その分プログラムに集中できるようになりました。

・ソースコードからのAPKビルドを自動化する
・AndroidLintにより、コーディング規約に沿ったコードになっているかどうかをチェックする
・JUnitを活用したユニットテスト(単体テスト)を自動化する
・配布の自動化

それでは、各項目について詳しく説明していきます。

ソースコードからのAPKビルドを自動化する


後述のLintやユニットテストはビルドが通らないと実行できないので、まずビルドできるようにします。
基本的には、GitLab CIのテンプレート集を参考に作成しましたが、Android SDKのダウンロードURLの変更があったため、苦労しました。
下記を.gitlab-ci.ymlに設定することでビルドを行なえる環境を構築できます。

image: openjdk:8-jdk

variables:
  # ビルドツールのバージョンなどを変数で持って変更しやすくします。
  ANDROID_SDK_TOOLS: "3859397"
  ANDROID_BUILD_TOOLS: "26.0.2"
  ANDROID_COMPILE_SDK: "26"
  SDK_ROOT: "/sdk"

before_script:
  # Android SDKのインストール開始
  - apt-get --quiet update --yes
  - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
  - wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip
  - unzip -qq android-sdk.zip -d ${SDK_ROOT}
  - mkdir -p /root/.android
  - touch /root/.android/repositories.cfg
  - mkdir -p ${SDK_ROOT}/licenses
  - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > ${SDK_ROOT}/licenses/android-sdk-license
  - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > ${SDK_ROOT}/licenses/android-sdk-preview-license
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "tools" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "extras;android;m2repository" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "extras;google;google_play_services" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "extras;google;m2repository" >/dev/null 2>&1
  - export ANDROID_HOME="${SDK_ROOT}"
  - export PATH="$PATH:${SDK_ROOT}/platform-tools/"
  - touch local.properties
  - echo "sdk.dir=${ANDROID_HOME}" >> local.properties
  # gradlewを実行できるようにしておきます。
  - chmod +x ./gradlew

cache:
  key: ${CI_PROJECT_ID}
  paths:
  - .gradle/

build:
  stage: build
  script:
    # Debug環境だけビルドする
    - ./gradlew assembleDebug
  artifacts:
    paths:
    # 作成したapkをGitLabからダウンロードできるようにする
    - app/build/outputs/

ただし上記のCI設定では、Android SDKを毎回ダウンロード・インストールするため、CIサーバーのスペック次第では非常に時間がかかります。
Dockerイメージを作成してDockerレジストリーサーバーに登録しておくことでCIの実行時間の短縮できます。

※ 実際に運用する際はライセンスにご注意ください

カタリストでは、社内にプライベートDockerレジストリーサーバーを構築し、そちらに構築済みのDockerイメージをアップロードして、CIの実行時間の低減をしています。

プライベートDockerレジストリーサーバーの構築方法は下記などを参照してください

Android Lintにより、コーディング規約に沿ったコードになっているかどうかをチェックする


Android LintはADT(Android Development Tools)16から導入された、ソースチェックツールです。
Androidアプリのソースチェックを行い、パフォーマンスに支障があるソースなどの問題を検出し警告してくれます。
下記を.gitlab-ci.ymlに設定することでLintを行なえます。

lint:
  script:
    # Lintの実行
    - ./gradlew lint
  artifacts:
    paths:
    # 作成したレポートをGitLabからダウンロードできるようにする
    - app/build/reports/

CI結果から、Lint結果ファイル(HTML、XML)をダウンロードして確認する形です。
ダウンロードしたファイルを解凍すると下記のように「lint-results.html」ファイルが入っていますので、こちらを確認するとモンダイテンを確認できます。


改善点として、Lint結果をマージリクエストに自動でコメントされるようにできればよいかなと思っていますが、今のところは都度ダウンロードする形としています。

JUnitを活用したユニットテスト(単体テスト)を自動化する


Android Studioでプロジェクトを作成しようとすると自動的にJUnitを組み込んだ状態で作成されます。
こちらもAndroid Lintと同じくgradlewから実行できますので、下記を.gitlab-ci.ymlに設定します。

unitTests:
  script:
    - ./gradlew test
  artifacts:
    paths:
    - app/build/reports

上記を設定しておくことによって、自動でユニットテストが実行されます。
今回は記事用のリポジトリでテストを書いていないため、NO-SOURCEと出てしまっていますが、下記のように出力されます。

配布の自動化

DeployGateの利用

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

署名の設定

今回は、署名済みのAPKをアップロードしますので、公式の案内の通り署名を作成してください。

今回はブログ用に上記のように作成し、build.gradleへ下記のように設定してCIで署名を行えるようにしました。

# build.gradle
android {
    signingConfigs {
        # Debug用の署名設定
        debug {
            storeFile rootProject.file("debug.keystore")
            storePassword "android"
            keyAlias "androiddebugkey"
            keyPassword "android"
        }
        # Release用の署名設定
        release {
            # 作成した署名を設定。
            storeFile rootProject.file("release.jks")
            # システム環境変数からパスワードなどを設定する
            storePassword System.getenv("CS_RELEASE_STORE_PASSWORD")
            keyAlias System.getenv("CS_RELEASE_KEY_ALIAS")
            keyPassword System.getenv("CS_RELEASE_KEY_PASSWORD")
        }
    }
    buildTypes {
        debug {
            signingConfig signingConfigs.debug
            debuggable true
            testCoverageEnabled true
        }
        release {
            signingConfig signingConfigs.release
            debuggable false
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

DeployGate APIの利用

今回DeployGateへのAPKのアップロードについては、DeployGate APIを利用することとしました。
下記を.gitlab-ci.ymlに設定します。

variables:
  # signingConfigsで設定した環境変数にパスワードなどを設定する。
  # GitLabのCI設定でSecret variablesを使用することによってここに書かないようにもできます。
  CS_RELEASE_STORE_PASSWORD: hogehoge
  CS_RELEASE_KEY_ALIAS: releaseKey
  CS_RELEASE_KEY_PASSWORD: fugafuga

deploygate:
  stage: deploy
  script:
    # リリース用のビルドを行う
    - ./gradlew assembleRelease
    - cd app/build/outputs/apk/release
    # DeployGateへapkのアップロード
    - curl -f -F "file=@app-release.apk" -F "token=YOUR_API_KEY" -F "message=${CI_PIPELINE_ID} is build" https://deploygate.com/api/users/YOUR_USER_NAME/apps
    # httpの400番台のエラーが帰ってきた場合は異常終了。
    # 内容がエラーでも200で返ってくるので、とりあえずOKになる。
    - if [ $? != 0 ]; then exit 1 ;fi
    # ユーザーの自動追加 既に追加されているユーザーはエラーになるので、エラーコードの確認などはしない
    - curl -F "users=ADD_USER_NAME" -F "role=1" -F "token=YOUR_API_KEY" https://deploygate.com/api/users/YOUR_USER_NAME/platforms/android/apps/YOUR_APP_ID/members
  only:
  - master

GitLabでCIを動かすための設定


それでは、AndroidのCIをGitLabで動かすための設定を解説します。
GitLab CIでのビルドから配布の自動化までの流れは下記の通りです。

  1. デバッグでAPKを作成
  2. Android Lintでソースチェックを実施
  3. JUnitを活用したユニットテストを実施
  4. リリースでAPKを作成し、DeployGateへアップロードする

カタリストにおけるAndroid CIの構成を、サンプルとして掲載いたします。

image: openjdk:8-jdk

variables:
  # signingConfigsで設定した環境変数にパスワードなどを設定する。
  # GitLabのCI設定でSecret variablesを使用することによってここに書かないようにもできます。
  CS_RELEASE_STORE_PASSWORD: hogehoge
  CS_RELEASE_KEY_ALIAS: releaseKey
  CS_RELEASE_KEY_PASSWORD: fugafuga
  # ビルドツールのバージョンなどを変数で持って変更しやすくします。
  ANDROID_SDK_TOOLS: "3859397"
  ANDROID_BUILD_TOOLS: "26.0.2"
  ANDROID_COMPILE_SDK: "26"
  SDK_ROOT: "/sdk"

before_script:
  # Android SDKのインストール開始
  - apt-get --quiet update --yes
  - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
  - wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip
  - unzip -qq android-sdk.zip -d ${SDK_ROOT}
  - mkdir -p /root/.android
  - touch /root/.android/repositories.cfg
  - mkdir -p ${SDK_ROOT}/licenses
  - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > ${SDK_ROOT}/licenses/android-sdk-license
  - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > ${SDK_ROOT}/licenses/android-sdk-preview-license
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "tools" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "extras;android;m2repository" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "extras;google;google_play_services" >/dev/null 2>&1
  - echo y | ${SDK_ROOT}/tools/bin/sdkmanager "extras;google;m2repository" >/dev/null 2>&1
  - export ANDROID_HOME="${SDK_ROOT}"
  - export PATH="$PATH:${SDK_ROOT}/platform-tools/"
  - touch local.properties
  - echo "sdk.dir=${ANDROID_HOME}" >> local.properties
  # gradlewを実行できるようにしておきます。
  - chmod +x ./gradlew

cache:
  key: ${CI_PROJECT_ID}
  paths:
  - .gradle/

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    # Debug環境をビルドする
    - ./gradlew assembleDebug
    # Lintを実行する
    - ./gradlew lint
  # 作成したapkとレポートをGitLabからダウンロードできるようにする
  artifacts:
    paths:
    - app/build/outputs/
    - app/build/reports/

unitTests:
  stage: test
  script:
    # ユニットテストを実行する
    - ./gradlew test
  # 作成したテスト結果をGitLabからダウンロードできるようにする
  artifacts:
    paths:
    - app/build/reports

deploygate:
  stage: deploy
  script:
    # リリース環境をビルドする
    - ./gradlew assembleRelease
    - cd app/build/outputs/apk/release
    - ls -l
    # DeployGateへapkのアップロード
    - curl -f -F "file=@app-release.apk" -F "token=YOUR_API_KEY" -F "message=${CI_PIPELINE_ID} is build" https://deploygate.com/api/users/YOUR_USER_NAME/apps
    # httpの400番台のエラーが帰ってきた場合は異常終了。
    # 内容がエラーでも200で返ってくるので、とりあえずOKになる。
    - if [ $? != 0 ]; then exit 1 ;fi
    # ユーザーの自動追加 既に追加されているユーザーはエラーになるので、エラーコードの確認などはしない
    - curl -F "users=ADD_USER_NAME" -F "role=1" -F "token=YOUR_API_KEY" https://deploygate.com/api/users/YOUR_USER_NAME/platforms/android/apps/YOUR_APP_ID/members
  # masterブランチへのプッシュの場合にのみ実行する
  only:
  - master

まとめと予告


いかがだったでしょうか。
以前のプロジェクトでは、DeployGateへのアップロードを手で行なっていましたが、Android Studioで行なうと負荷のある作業だと感じていました。
CIで自動化することによって、手作業で行なっていた時間を開発に回せます。
実際に他のプロジェクトで手作業を自動化することで生産性が上がってきました。これからも自動化を推進していきたいと思っています。


次回は、オガティーによる「JavascriptのCI(仮)」をお届けします。
お楽しみに!

引用元・出典
DeployGate API

LINEで送る
Pocket

この記事を書いた人・プロフィール
toshi
ニックネーム: toshi

2013年10月からカタリストシステムに在籍。以前は独立系SIerで監視業務を行っていました。
Androidアプリ、サーバサイド開発(PHP)、サーバインフラ関連等手広く対応しています。
コミケは毎回行ってます。

趣味:自作PC、バイク、萌え系、インディーズゲーム