CircleCI 2.0 で deploygate に Android アプリをアップロード
Android アプリを DeployGate へ
CircleCI を使って簡単にデプロイしたいな〜、と記事を探したら、
1.0 の記事が多かったので CircleCI 2.0 向けの記事を書くことにしました。
異なる証明書で署名されたアプリと言われ、deploygate に毎回アンインストールを求められる件についても、
「6. 異なる証明書で署名されたアプリと言われないために」の項で解決策を記しています。
実験台となったのは、
前回の『AndroidのgRPCクライアントをKotlinで実装しました』の記事で取り扱ったこちらのリポジトリ。
1. config.yml はこうなった
まずは出来上がりから。
.circleci ディレクトリを作った上で、その中に config.yml という名前で保存します。
ただ、このあとの解説で書きますが、 DG_API_KEY
を設定しないといけないので、
このままだとまだデプロイが出来ません。
aliases: android_docker: &android_docker docker: - image: circleci/android:api-28 environment: TZ: Asia/Tokyo steps: - restore_cache: &restore_cache key: &jars_key jars-{{ checksum "build.gradle.kts" }}-{{ checksum "app/build.gradle.kts" }} - run: &download_deps name: Download dependencies command: ./gradlew androidDependencies - save_cache: &save_cache paths: - ~/.gradle key: *jars_key - run: &build_debug_apk name: Build debug APK command: ./gradlew assembleDebug - run: &test name: test command: ./gradlew test - run: &upload_to_deploy_gate name: Upload to DeployGate # Set DG_API_KEY in CircleCI command: | APK_PATH=app/build/outputs/apk/debug/app-debug.apk TIME=$(date "+%Y/%m/%d %H:%M") COMMIT_HASH=$(git log --format="%H" -n 1 | cut -c 1-8) USERNAME=nekonenene curl -F "file=@${APK_PATH}" -F "token=${DG_API_KEY}" -F "message=Build by CircleCI <${COMMIT_HASH}> (${TIME})" https://deploygate.com/api/users/${USERNAME}/apps version: 2 jobs: build: <<: *android_docker steps: - checkout - restore_cache: *restore_cache - run: *download_deps - save_cache: *save_cache - run: *build_debug_apk test: <<: *android_docker steps: - checkout - restore_cache: *restore_cache - run: *download_deps - save_cache: *save_cache - run: *test deploy: <<: *android_docker steps: - checkout - restore_cache: *restore_cache - run: *download_deps - save_cache: *save_cache - run: *build_debug_apk - run: *upload_to_deploy_gate workflows: version: 2 build_and_deploy: jobs: - build - test - deploy: requires: - build - test filters: branches: only: master context: deploygate
あとで再び書きますが、このリポジトリでは gradle 設定を Kotlin で書いているために、
「build.gradle.kts」と書いている箇所があります。
たいていの方は「build.gradle」と書き直してください。
また、docker image は「circleci/android:api-28」を使っていますが、
compileSdkVersion に合わせたものをお使いください。
2. master 以外のブランチではデプロイしない
他のブランチで push されたときもテストはしたいけど、
deploygate にアップロードするのは master ブランチへコミットがあったときに限定したいな〜
という希望があると思います。
それを実現しているのが workflows の部分です。
workflows: version: 2 build_and_deploy: jobs: - build - test - deploy: requires: - build - test filters: branches: only: master context: deploygate
workflows としてはこうなっています。
ここの jobs で、実行する job を指定でき、各々にはさらに設定を書くことが出来ます。
- deploy: requires: - build - test filters: branches: only: master context: deploygate
filters: に branches: only: master と書かれていることによって、
master ブランチへコミットがあったときだけ、deploy job は実行されるようになっています。
また、 requires: に build と test が書かれていることによって、
build job と test job の両方が終わった後に deploy job が走るようになっています。
これを書かなかった場合、他の job と deploy job がパラレルに(並行して)走ります。
つまり、フローとしては以下のようになっています。
build ──┬── deploy (only: master) test ──┘
context については後で説明します。
3. 各 job を定義する
workflows から先に説明しましたが、もっとも大事なのは jobs の部分です。
ここで、何がおこなわれるかを定義します。
version: 2 jobs: build: <<: *android_docker steps: - checkout - restore_cache: *restore_cache - run: *download_deps - save_cache: *save_cache - run: *build_debug_apk
と書かれている部分ですね。
簡潔に書くため、YAMLのAnchors and Aliases 機能を使って書きましたが、
省略せずに書くと以下のようになっています。
jobs: build: docker: - image: circleci/android:api-28 environment: TZ: Asia/Tokyo steps: - checkout - restore_cache: key: jars-{{ checksum "build.gradle.kts" }}-{{ checksum "app/build.gradle.kts" }} - run: name: Download dependencies command: ./gradlew androidDependencies - save_cache: paths: - ~/.gradle key: jars-{{ checksum "build.gradle.kts" }}-{{ checksum "app/build.gradle.kts" }} - run: name: Build debug APK command: ./gradlew assembleDebug
今回はビルドに使う Android SDK が 28 であるため、
使用する Docker image も circleci/android:api-28
を使用しています。
他のバージョンもありますので、ふさわしいものを探してください。
https://hub.docker.com/r/circleci/android/tags
restore_cache では、
build.gradle.kts
および app/build.gradle.kts
が更新されているかを確認し、
変更がなければ以前 save_cache で保存した .gradle
を使用します。
これにより、2回目以降の CircleCI の実行時間が短くなります。
前回の記事でも触れましたが、 Gradle Kotlin DSL により
gradle 設定を Kotlin で書いているためにファイル名が「build.gradle.kts」になっています。
Gradle Kotlin DSL を使っていない場合は、 key: のところは
jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
と書きましょう。
4. deploygate にアップロード
肝心の deploygate へのアップロードですね!
このようにコマンドを作成しました。
- run: &upload_to_deploy_gate name: Upload to DeployGate # Set DG_API_KEY in CircleCI command: | APK_PATH=app/build/outputs/apk/debug/app-debug.apk TIME=$(date "+%Y/%m/%d %H:%M") COMMIT_HASH=$(git log --format="%H" -n 1 | cut -c 1-8) USERNAME=nekonenene curl -F "file=@${APK_PATH}" -F "token=${DG_API_KEY}" -F "message=Build by CircleCI <${COMMIT_HASH}> (${TIME})" https://deploygate.com/api/users/${USERNAME}/apps
deploygate の API ドキュメント によれば、
アプリのアップロードは
curl \ -F "token=xxx" \ -F "file=@sample.apk" \ -F "message=sample" \ https://deploygate.com/api/users/_your_name_/apps
で出来ると書かれていますのでそれに従っています。
変数 APK_PATH にデバッグビルドで作られるアプリのパスを指定、
message に入れる文字列として変数 TIME, COMMIT_HASH を定義、
変数 USERNAME には deploygate のユーザー名を代入したあと、API を叩いています。
"message=Build by CircleCI <${COMMIT_HASH}> (${TIME})"
となっていますので、
「Build by CircleCI <02f18181> (2019/02/23 19:07)」のようなメッセージが付けられることになります。
メッセージに日時を入れているのは、
deploygate アプリからバージョン一覧を見たときに、どれが何か全然わからなかったためですね…。
↓ 入れないとこんな感じ
コミットメッセージを入れようかとも思いましたが、
コミットメッセージに特殊文字が含まれる場合に curl が失敗しないよう
処理を考えなくては…なので今回はやりませんでした。
5. deploygate API Key の指定
さて、上のコマンドで "token=${DG_API_KEY}" となっている箇所がありました。
ここの変数にどうやって値を入れているかの話をします。
CircleCI には、設定画面からプロジェクトごと、もしくは Context ごとに環境変数を設定できるようになっています。
今回はそれの後者、Context を使った環境変数の設定をおこなっています。
参考 : https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-context
以下の画像のように、「SETTINGS」の「Contexts」へと進みます。
右上に「Create Context」のボタンがあると思いますので、それを押して Context の新規作成をします。
Name はわかりやすく「deploygate」としておきましょう。
すると、以下の画面が出ますので「Add Environment Variable」ボタンから環境変数の追加をおこないます。
入力画面が出たら、Nameに「DG_API_KEY」、Valueに deploygate の API キーを入力します。
API キーは https://deploygate.com/settings の下の方に書かれています。
この設定を完了させたことにより、
(2. で説明を省きました) context: deploygate のコンフィグにより、
deploy job の実行時にいま設定した環境変数が読み込まれるようになります。
これで、 deploygate へ Android アプリを永遠にアップロードすることができる
CircleCI config が完成しました! おつかれさまです!!
6. 異なる証明書で署名されたアプリと言われないために
これで一件落着かと思いきや罠があります。
異なる証明書で署名されたアプリ (バージョン 1.0=1) が既にインストールされています。新しいアプリをインストールする前に、一度アンインストールしてください。
と、アンインストールが毎回求められてしまうことが発生します。
なんで起きるかというと、デバッグビルドの署名は通常 ~/.android/debug.keystore
にある証明書が使われるのですが、
CircleCI が使う Docker イメージが毎回異なるため、署名に使う証明書も一致しないのです。
ということで、 デバッグビルドに使う証明書を指定しましょう!!
まずはプロジェクトのルートディレクトリにて以下のコマンドを実行します。
keytool -v -genkey -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" -keyalg RSA -validity 36500
これで 36500 日(およそ100年)有効の証明書が作成できます。
次に app ディレクトリの build.gradle.kts
に以下のような設定が含まれるよう修正します。
参照: https://github.com/nekonenene/grpc_android_client/blob/master/app/build.gradle.kts
android { // 中略 signingConfigs { getByName("debug") { storeFile = rootProject.file("debug.keystore") storePassword = "android" keyAlias = "androiddebugkey" keyPassword = "android" } } buildTypes { getByName("debug") { isMinifyEnabled = false signingConfig = signingConfigs.getByName("debug") } } }
Gradle Kotlin DSL を使わない場合の書き方は異なります。公式のドキュメントなどを参照してください。
storePassword, keyAlias, keyPassword は、さっきのコマンドで指定した値(-storepass android -alias androiddebugkey -keypass android
)と一致するように気を付けてください。
これにより、デバッグビルド時に、プロジェクトのルートディレクトリに作った
debug.keystore
が毎回使われ、同一の証明書による署名となってくれるため、
deploygate にアンインストールが毎回求められる問題はなくなります。
7. おしまい
しっかり書いたらけっこう長くなりましたが、以上!
Android アプリを deploygate にデプロイし続ける方法でしたー!
異なる証明書で署名されたアプリ 問題は、昔「毎回アンインストールはめんどいな〜」って思いながらも
しぶしぶやっていましたが、今回調べたらちゃんと対処法が発見できてよかったです。
DroidKaigi 2018 のアプリ もよく見ると、
debug.keystore がトップディレクトリに置いてありますね。なるほどなー。
らくらく実機テストをして、楽しいアンドロイド開発ライフをお過ごしください!