今やってるAndroidプロジェクトにgitlabciを導入してみた

# 前回やったことと今回やること
前回は今回の事前準備として、手動でDockerコンテナからAndroidアプリのビルドを行いました

今回は

- gitlabciを使ってCommit時に自動ビルドを実行する
- ブランチごとにビルドタイプとフレーバーを指定する
- VersionCodeにgitlabciのJobIDを割り当てる

ここまでやっていきたいと思います

# .gitlab-ci.yml作成
まずはgitlabciのCI/CDの設定ファイルである**.gitlab-ci.yml** を作ります

作り方はとてもかんたんで、

- リポジトリのファイル一覧のページから，「+」→「New File」を選択
- Templateで「.gitlab-ci.yml」「Android」を選択

すると、下記のファイルが作成されます

```yml:.gitlab-ci.yml
# This file is a template, and might need editing before it works on your project.
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.

image: openjdk:8-jdk

variables:
  ANDROID_COMPILE_SDK: "28"
  ANDROID_BUILD_TOOLS: "28.0.2"
  ANDROID_SDK_TOOLS: "4333796"

before_script:
  - 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 -d android-sdk-linux android-sdk.zip
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null
  - export ANDROID_HOME=$PWD/android-sdk-linux
  - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/
  - chmod +x ./gradlew
  # temporarily disable checking for EPIPE error and use yes to accept all licenses
  - set +o pipefail
  - yes | android-sdk-linux/tools/bin/sdkmanager --licenses
  - set -o pipefail

lintDebug:
  stage: build
  script:
    - ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint

assembleDebug:
  stage: build
  script:
    - ./gradlew assembleDebug
  artifacts:
    paths:
      - app/build/outputs/

debugTests:
  stage: test
  script:
    - ./gradlew -Pci --console=plain :app:testDebug
```

実はこれでほぼほぼできています笑
というか、前回手動でやったときもここを参考にしてもらいました

とはいえいくつか変えないといけないところがあるので、そちらの解説をしていきます

## イメージ
```yml:.gitlab-ci.yml
image: openjdk:8-jdk
```

ここはそのままで大丈夫です

変わった処理をするので自分で作ったDockerfileを使いたい場合や、すでに誰かが作っているものを流用したい場合は変更してください

今回は定石のコマンドしか叩かないのでここはそのまま

## 変数
```yml:.gitlab-ci.yml
variables:
  ANDROID_COMPILE_SDK: "28"
  ANDROID_BUILD_TOOLS: "28.0.2"
  ANDROID_SDK_TOOLS: "4333796"
```

ここはビルドするアプリの設定によって変更する必要があります
一番下はビルド時に使うツールのバージョンなのでそのままで行きますが、古すぎると使えないコマンドがあるので注意してください

## 準備コマンド
```yml:.gitlab-ci.yml
before_script:
  - 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 -d android-sdk-linux android-sdk.zip
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null
  - export ANDROID_HOME=$PWD/android-sdk-linux
  - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/
  - chmod +x ./gradlew
  # temporarily disable checking for EPIPE error and use yes to accept all licenses
  - set +o pipefail
  - yes | android-sdk-linux/tools/bin/sdkmanager --licenses
  - set -o pipefail
```

ツールのインストールや環境変数の設定を行います

今回は署名の際にビルドツールに入っているライブラリを使用するので下記を追記します

```yml:.gitlab-ci.yml
- export PATH=$PATH:$PWD/android-sdk-linux/build-tools/${ANDROID_BUILD_TOOLS}/
```

# 実装部
ここからがメインです

Templateでは

1. リントチェック
2. ビルド
3. テスト

と行っていましたが、今回は

1. ビルド
2. 署名

の順番で実施します（処理としては一まとまりです）

## ビルド処理

- ここではdevelopフレーバーでビルドを行っています

```yml:.gitlab-ci.yml
test_assemble:
  stage: build
  script:
    - ./gradlew assembleRelease
  artifacts:
    paths:
      - app/build/outputs/
```

- stage
    - デフォルトでbuildとtestが用意されており、それ以外を使用する場合はこの前に「stages」で定義する必要があります
今回はbuildのみを使用するため割愛します

- script
    - 今回は署名付きのリリースビルドを行います
    - ひとまず全フレーバーでビルドを行います**（runnerのスペックが足らない場合は落ちる可能性あり）**

- artifacts
    - デフォルトではフォルダが出力されます。今回はそのままです

## 署名処理

- scriptに署名処理を追記します

```yml:.gitlab-ci.yml
test_assemble:
  script:
    - ./gradlew assembleRelease
    - echo [jksファイルをbase64でエンコードしたもの] | base64 -d > my.keystore
    - apksigner sign --ks my.keystore -v --v2-signing-enabled true --ks-key-alias [エイリアス名] --ks-pass pass:[キーストアのパスワード] --key-pass pass:[キーのパスワード] --out app-develop_-release.apk $(pwd)/app/build/outputs/apk/develop_/release/app-develop_-release-unsigned.apk
```

- jksファイルをbase64でエンコードしたものをデコードし、keyファイルとして出力します
- apksignerというライブラリを使ってビルドしています。必要な情報をオプションで記載しています

apksignerに関しては[公式](https://developer.android.com/studio/command-line/apksigner?hl=ja)と[こちら](https://www.366service.com/jp/qa/d2c21594b4b1efdc28435c17aff26a6e)を参考にさせていただきました

**※署名前のファイルは末尾が〇〇-unsigned.apkとなるので注意（ここでだいぶつまづきました（泣））**

## 環境変数化
パスワードや署名ファイルを直書きするのはセキュリティ的に問題ですので、gitlabciの設定にあるVariables機能を使います

横のメニューから「Setting」→「CI/CD」→「Variables」の「Expand」をクリック
![SnapCrab_NoName_2020-11-5_14-55-14_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553852/7974a958-b08b-745e-c1b5-b78d0a8152d6.png)

このような画面が開きます（すでに変数設定済み）

緑色の「Add Variable」を押すと登録画面が開きます

![SnapCrab_NoName_2020-11-5_14-57-11_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553852/232cb1ab-84d4-b597-1a5f-83f654e12d32.png)

- Key
    - 環境変数名
- Value
    - 環境変数の値
- Type
    - 変数orファイル(今回はデフォルト)
- Environment scope
    - スコープの設定(今回はデフォルト)
- Flags
    - Protect variable
        - プロテクトブランチでのみ有効になります(今回はチェックしない)
    - Mask variable
        - ログに出力されなくなります(今回はチェックしない)

上記で

- KEY_ALIAS
    - エイリアス名
- KEY_PASSWORD
    - キーのパスワード
- KEYSTORE_FILE
    - jksファイルをbase64でエンコードしたもの
- KEYSTORE_PASSWORD
    - キーストアのパスワード

をそれぞれ設定します

設定後、.gitlab-ci.ymlに戻り、scriptを変更します

```yml:.gitlab-ci.yml
test_assemble:
  script:
    - ./gradlew assembleRelease
    - echo ${KEYSTORE_FILE} | base64 -d > my.keystore
    - apksigner sign --ks my.keystore -v --v2-signing-enabled true --ks-key-alias ${KEY_ALIAS} --ks-pass pass:${KEYSTORE_PASSWORD} --key-pass pass:${KEY_PASSWORD} --out app-${FLAVOR}_-release.apk $(pwd)/app/build/outputs/apk/${FLAVOR}_/release/app-${FLAVOR}_-release-unsigned.apk
```

Variablesを使用する場合は$(ドルマーク)を使います（括弧はなくてもよい）

# VersionCodeにJobIDを指定

build.gradleに記載しているVersionCodeをいちいち手動で書き換えるのはめんどくさいので、CIから動的に変更するようにしたいと思います
場合によってはversionNameの方も書き換えますが、今回は省略します。

流れとしては

- build.gradleにtaskを追加
- gitlabciにデフォルトである環境変数を呼ぶ
- ciからtaskを呼び、上記パラメータを渡す

という感じになります

## build.gradleにCIのVariablesを取得する処理を追加

build.gradle(appじゃない方)にVersionCodeを動的に変更するタスクを追加します
[こちら](https://asmz.hatenablog.jp/entry/gradle-tasks-for-increment-android-versions)を参考にさせていただきました

```build.gradle
task setJobNumber {
    if (project.hasProperty("num")) {
        def pattern = "versionCode (\\d+)"
        def manifestFile = file("./app/build.gradle")
        def manifestText = manifestFile.getText()
        
        def num = project.property("num")
        def manifestContent = manifestText.replaceAll(pattern, "versionCode " + num)
        manifestFile.write(manifestContent)
    }
}
```
numというプロパティを実行時に渡してあげることで、ビルド前にVersionCodeを書き換える処理を実現します

## gitlabciで準備されている環境変数について

上記では自分でkey-valueを設定し、環境変数を追加しましたが、gitlabciにはciで発行されるIDやTokenを取得するための環境変数が準備されています
結構種類は豊富です。[こちら](https://docs.gitlab.com/ee/ci/variables/README.html)を参考にしてください

今回は**CI_JOB_ID**を使用します

## 上記処理を実行するコマンドをymlに追加

上記で作成したタスクを呼び、パラメータを付与します

```yml:.gitlab-ci.yml
test_assemble:
  script:
    - ./gradlew setJobNumber -Pnum=${CI_JOB_ID}
```

# ブランチごとにフレーバーを指定する

- 上記処理をコピペして各ブランチごとに処理を作れば実現可能ではあるが、コマンドが重複しがち
- paramを使って処理のTemplateを作り、extendsを使用して重複しないように記載していく

先ほど作成したtest_assembleをテンプレート部と変数代入部に分けるイメージです

```yml:.gitlab-ci.yml
.param_build:
  script:
    - ./gradlew setJobNumber -Pnum=${CI_JOB_ID}
    - ./gradlew assemble${FLAVOR}_Release
    - echo ${KEYSTORE_FILE} | base64 -d > my.keystore
    - apksigner sign --ks my.keystore -v --v2-signing-enabled true --ks-key-alias ${KEY_ALIAS} --ks-pass pass:${KEYSTORE_PASSWORD} --key-pass pass:${KEY_PASSWORD} --out app-${FLAVOR}_-release.apk $(pwd)/app/build/outputs/apk/${FLAVOR}_/release/app-${FLAVOR}_-release-unsigned.apk
  artifacts:
    paths:
      - app-${FLAVOR}_-release.apk
```


まずは.param_buildに先程作った処理を記載し、環境ごとに変わる値を環境変数に置き換えます（ここでは**FLAVOR**としています）

```yml:.gitlab-ci.yml
develop_assemble:
  stage: build
  only:
    - develop
  variables:
    FLAVOR: develop
  extends:
    - .param_build

#以下に他のブランチが続く
```

初登場の3つを説明します

- only
    - こちらでブランチを指定します
    - 今回はdevelopブランチにcommitしたときに実行したいのでdevelopとしています
- variables
    - 環境変数をここで設定します
    - 今回はFLAVORをdevelopとして設定します
- extends
    - 使うテンプレートを指定します
    - 今回は当然.param_buildです
    

- 注意点としては、.param_buildで指定されている処理を書いてしまうと完全に上書きされてしまいます
    - 例えばscriptをこっちに書いてしまうと、.param_buildの処理を上書きしてしまうので、.param_buildのscript処理は実行されなくなります


# 最終成果物がこちら

```yml:.gitlab-ci.yml
# This file is a template, and might need editing before it works on your project.
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.

image: openjdk:8-jdk

variables:
  ANDROID_COMPILE_SDK: "28"
  ANDROID_BUILD_TOOLS: "29.0.3"
  ANDROID_SDK_TOOLS: "4333796"

before_script:
  - 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 -d android-sdk-linux android-sdk.zip
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null
  - export ANDROID_HOME=$PWD/android-sdk-linux
  - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/
  - export PATH=$PATH:$PWD/android-sdk-linux/build-tools/${ANDROID_BUILD_TOOLS}/
  - chmod +x ./gradlew
  # temporarily disable checking for EPIPE error and use yes to accept all licenses
  - set +o pipefail
  - yes | android-sdk-linux/tools/bin/sdkmanager --licenses
  - set -o pipefail

.param_build:
  tags:
    - Android
  script:
    - ./gradlew setJobNumber -Pnum=${CI_JOB_ID}
    - ./gradlew assemble${FLAVOR}_Release
    - echo ${KEYSTORE_FILE} | base64 -d > my.keystore
    - apksigner sign --ks my.keystore -v --v2-signing-enabled true --ks-key-alias ${KEY_ALIAS} --ks-pass pass:${KEYSTORE_PASSWORD} --key-pass pass:${KEY_PASSWORD} --out app-${FLAVOR}_-release.apk $(pwd)/app/build/outputs/apk/${FLAVOR}_/release/app-${FLAVOR}_-release-unsigned.apk
  artifacts:
    paths:
      - app-${FLAVOR}_-release.apk

develop_assemble:
  stage: build
  only:
    - develop
  variables:
    FLAVOR: develop
  extends:
    - .param_build

#以下に他のブランチが続く
```

一つ補足しておくと、.param_buildのtagsでrunnerの指定をしています
プロジェクトに複数のrunnerが登録されている場合、どのrunnerでビルドを行うかをTagによって指定することができます

こちらを各ブランチにcommitすると、指定したビルドが動き、「CI/CD」→「Pipeline」から生成物をダウンロードできるはずです

# 確認
署名ができているかどうかは下記コマンドで確認できます

```
keytool -printcert -jarfile （APK名）
```

中身の確認はAndroid Studioのapkanalyzerで、手動ビルドとの比較が可能です

# 追記

今回はビルド後に後付の署名を行いましたが、ビルド時に署名情報を付与することが可能です

試してはみたのですが、何故かうまく行かず、このままでは気持ち悪いので、少し調査してみようと思います
分かり次第記事にしようと思っております


長々とご覧頂きありがとうございました