こんにちは、グノシー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_play
VERSION_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 さんの記事です。お楽しみに!