こんにちは、グノシーAndroidアプリの開発担当のLiangです。
この記事はGunosy Advent Calendar 2022の23日目の記事です。前回の記事は Rui さんの 広告レコメンドでIncrementalトレーニングを実践し、学習コストを大幅に削減した話 でした。
今回では、Androidアプリの開発において、CIツールを用いたAPKのデプロイ作業を自動化する仕組みについて、お話したいと思います。
背景
Androidアプリの開発に当り、通常のリリースでは、従来以下の手作業で行います:
- DeployGateでビルドされた最新版のリリースAPKをダウンロード
- 該当リリースAPKをGoogle Playコンソールにてβ公開する。
release/xxxxxxをmasterにマージする。masterにversionCodeでタグを切る。
リリース頻度が高くなるに連れて、手動によるミスもあり得る状況を防ぐために、1と2の作業の自動化を検討し始めました。
やり方
前提として私達はCircleCIを用いて、継続的にビルドとテストの運用をしています。 では、どのような仕組みで自動化すれば、今の環境に一番相応しいのでしょうか?
fastlane ❌ docs.fastlane.tools
- fastlaneがない環境で、最初から導入する必要がある。
- ただデプロイをするためだけのために、fastlaneによる膨大なライブラリーの依存が発生する。
- デプロイ失敗した時のログが完全ではない。
Gradle Play Publisher ❌ github.com
- Gradle 7以上が必要で、検討した時点ではGradle 6だった。
build.gradleの設定が複雑になりがち。- リリースノートの書き込みは別途ファイルを作成する必要がある。
Google Play Developer API ✅ github.com
- Google公式のライブラリーなので、余計な処理を入れない。
- ライブラリー自体はJavaなので、Kotlinで書けば互換出来る。
- こちらでフローを作成して制御するため、エラーなどは素早く対応出来る。
というわけで、Google Play Developer APIを利用することに決定しました。ワークフローとしては、Kotlinで書いたデプロイ用のGradleタスクをCircleCIに実行させます。
仕組み
buildSrcのタスク用Gradle設定
project/buildSrc/build.gradle
- Kotlinファイルのためのプラグイン追加
org.jetbrains.kotlin.jvm dependenciesにGoogle Play Developer API ライブラリーの依存関係を追加
plugins {
id('org.jetbrains.kotlin.jvm') version '1.7.20'
}
repositories {
google()
mavenCentral()
}
dependencies {
implementation gradleApi()
implementation 'com.google.apis:google-api-services-androidpublisher'
implementation 'com.google.api-client:google-api-client'
implementation 'com.google.auth:google-auth-library-oauth2-http'
}
デプロイ用のカスタムタスク
project/buildSrc/src/main/kotlin/GooglePlayDeployTask.kt
open classの指定でproject層のbuild.gradleにtaskをimport- annontation
@Optionを付けた変数は、コメントラインで渡すversionCode: 対象バージョンAPKのダウンロード用googleDeployKey: Base64でエンコードしたAPI用のサービスアカウントJson
DefaultTask()の継承により、annontation@TaskActionを付けたメソッドでデプロイのタスクを実行
open class GooglePlayDeployTask : DefaultTask() { @Input @Option(option = "versionCode", description = "") lateinit var versionCode: String @Input @Option(option = "googleDeployKey", description = "") lateinit var googleDeployKey: String @TaskAction fun deploy() { } }
デプロイのコードフロー
- サービスアカウントJsonで
GoogleCredentialsの認証を行う- 保存した
googleDeployKeyをBase64デコードし、InputStreamに転換
- 保存した
- 認証を通ったら、デプロイ用のクラス
AndroidPublisherを作成 editIdを作成、APIを叩く時に必要な識別IDApks.uploadで対象APKをInputStreamContentのフォマットでアップロードTracks.updateで作成したTrackにリリース情報をアップロードUpdate.track = betaでTrackをオープンテストに指定TrackRelease.status = completedで100%公開に指定
- 最後に
Commit.commitでAPIを叩く一連の作業を実行して完了
fun deploy() { val keyInputStream = ByteArrayInputStream(Base64.getDecoder().decode(googleDeployKey)) val credentials = GoogleCredentials.fromStream(keyInputStream).createScoped(scopes) ... val androidPublisher = AndroidPublisher .Builder(httpTransport, jsonFactory, httpRequestInitializer) .setApplicationName(PACKAGE_NAME) .build() val edits = androidPublisher.edits() val editId = edits .insert(PACKAGE_NAME, null) .execute() .id val apkInputStreamContent = InputStreamContent( "application/vnd.android.package-archive", ByteArrayInputStream(apkResponse) ) val apk = edits .apks() .upload(PACKAGE_NAME, editId, apkInputStreamContent) .execute() val trackRelease = TrackRelease() .setVersionCodes(apkVersionCodes) .setReleaseNotes(latestReleaseNotes) .setStatus("completed") val updateTrack = Track() .setReleases(listOf(trackRelease)) val track = edits .tracks() .update(PACKAGE_NAME, editId, "beta", updateTrack) .execute() edits .commit(PACKAGE_NAME, editId) .execute() }
実行のtaskを定義
project/build.gradle
import GooglePlayDeployTask task deployGooglePlay(type: GooglePlayDeployTask)
CircleCIの設定でGradleタスクを追加
project/circle.yml
jobsでGradleタスクの実行用Jobを追加:deploy_google_playVERSION_CODEは今回リリースのVersionCodeGOOGLE_DEPLOY_KEYは環境変数で保存されたサービスアカウントJson
deploy_google_play:
steps:
- run: ./gradlew deployGooglePlay
--versionCode=$VERSION_CODE
--googleDeployKey=$GOOGLE_DEPLOY_KEY
workflowsでデプロイJobdeploy_google_playを追加- ブランチを
masterに指定、マージする度に実行
- ブランチを
- deploy_google_play:
name: deploy_google_play
filters:
branches:
only: master
結論
以上の仕組みの導入によって、β公開のリリース作業を自動化し、かなりの手間を省けました。従来GradleタスクはGroovyで書くことが多いですが、Kotlinで書けるのもAndroidエンジニアにとって嬉しいことです。使う場面がございましたら、是非皆さんも利用してみましょう!
次回は aita さんの記事です。お楽しみに!
