![こー(エンジニア)](/_next/image?url=https%3A%2F%2Fapi.rightcode.co.jp%2Fwp-content%2Fuploads%2F2022%2F10%2F%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2022-10-17-20.06.23-150x150.png&w=64&q=75)
【Android】GitHub Actionsでアプリのバージョン情報を更新する
![こー(エンジニア)](/_next/image?url=https%3A%2F%2Fapi.rightcode.co.jp%2Fwp-content%2Fuploads%2F2022%2F10%2F%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2022-10-17-20.06.23-150x150.png&w=64&q=75)
IT技術
![](/_next/image?url=https%3A%2F%2Fapi.rightcode.co.jp%2Fwp-content%2Fuploads%2F2024%2F06%2Fversatile_thumbnail_Android.png&w=3840&q=75)
はじめに
こんにちは!株式会社ライトコードの福岡本社でモバイルエンジニアやってる こー です!
2021.11.05YOUは何しにライトコードへ?〜こーくん編〜プロジェクト内で安心感を与えられる存在になりたい!今回は、弊社のエンジニアである高さんにフィーチャー!技術力に定評のあ...
現在、個人開発しているAndroidアプリでFirebase App Distributionへの自動デプロイする際に利用している、GitHub Actionsを利用したアプリバージョン情報の更新 について解説していこうと思います。
次の記事では、実際に今回の方法と組み合わせて 1クリックでバージョン更新 & Firebase App Distributionにアプリをデプロイする方法 について執筆予定です。
それでは早速見ていきましょう!
どうしてやったの?
Androidアプリを継続リリースするためには、リリースAPKをビルドする前にアプリのバージョン情報を更新する必要があります。
バージョン情報は「バージョンコード」と「バージョン名」の2つで構成されています。
型 | 用途 | |
---|---|---|
バージョンコード | 整数 |
|
バージョン名 | 文字列 |
|
プロジェクトのビルドファイルapp/build.gradle.kts
にあるversionCode
/versionName
がそれぞれ「バージョンコード」/「バージョン名」に対応します。
ここを開発者が編集しリリースAPKをビルドする、というのが一般的です。
1/// app/build.gradle.kts
2android {
3 /* ~中略~ */
4 defaultConfig {
5 /* ~中略~ */
6 versionCode = 12
7 versionName = "0.10.0"
8 }
9 /* ~中略~ */
10}
この部分を GitHub Actionsを利用して自動化する ことで、タイムコストを削減しつつ手動更新によるヒューマンエラーを防ぐことができます。
また、GitHub上で実行するため開発者の環境に依存せず実行できるというメリットもあります。
何をやったの?
今回、この仕組みを作るにあたって行なったことは大きく分けて2つです。
- バージョン情報を更新するGradleタスクの実装
- 1を実行するGitHub Actionsのワークフローの実装
それでは、それぞれで具体的に何をやったのか、詳しく見ていきましょう。
ちなみに、今回Gradleファイルを扱いますが、Kotlin DSLでの実装を前提としています。
1. バージョン情報を更新するGradleタスクの実装
まず、versionCode
とversionName
からバージョンコード/バージョン名を操作するクラスを実装します。
1/// app/build.gradle.kts
2// バージョンを取り扱うクラス
3class Version(private var code: Int, version: String) {
4 private var major: Int
5 private var minor: Int
6 private var patch: Int
7
8 init {
9 // "0.0.0"で表現されるバージョン名を、メジャー/マイナー/パッチに分割 & 整数として操作できるように管理する。
10 val (major, minor, patch) = version.split(".").map { it.toInt() }
11 this.major = major
12 this.minor = minor
13 this.patch = patch
14 }
15
16 // バージョン情報を操作するメソッドをコマンドから実行するために、各メソッド名を保持。
17 val functionsByName = listOf(::bumpMajor, ::bumpMinor, ::bumpPatch).associateBy { it.name }
18
19 @SuppressWarnings("unused")
20 fun bumpMajor() {
21 major += 1
22 minor = 0
23 patch = 0
24
25 code += 1
26 }
27
28 @SuppressWarnings("unused")
29 fun bumpMinor() {
30 minor += 1
31 patch = 0
32
33 code += 1
34 }
35
36 @SuppressWarnings("unused")
37 fun bumpPatch() {
38 patch += 1
39
40 code += 1
41 }
42
43 fun getName(): String = "$major.$minor.$patch"
44 fun getCode(): Int = code
45}
バージョン番号/バージョン情報それぞれの文字列から、メジャー/マイナー/パッチを抽出して保持し、それぞれを操作するメソッドを実装しています。
次にGradleタスクのコマンドでそれぞれのメソッドを実行するために、メソッド名のリストをfunctionsByName
で保持しておきます。
次に実際にバージョン情報を操作するGradleタスクのコマンドを実装していきます。
1/// app/build.gradle.kts
2tasks.addRule("Pattern: bumpVersion") {
3 if (this.matches(Regex("bump(Major|Minor|Patch)Version"))) {
4 task(this) {
5 doLast {
6 // コマンド名から実行タイプを抽出
7 val type = this@addRule
8 .replace(Regex("bump"), "")
9 .replace(Regex("Version"), "")
10
11 println("Bumping ${type.lowercase(Locale.getDefault())} version...")
12
13 // 旧バージョンを取得
14 val oldVersionCode = android.defaultConfig.versionCode ?: return@doLast
15 val oldVersionName = android.defaultConfig.versionName ?: return@doLast
16 val version = Version(oldVersionCode, oldVersionName)
17
18 // メソッド名を取得しそのメソッドを実行(実行不可であればエラー)
19 val methodName = "bump${type}"
20 version.functionsByName[methodName]?.invoke() ?: error("Unknown method: $methodName")
21
22 // 更新したバージョンを取得
23 val newVersionCode = version.getCode()
24 val newVersionName = version.getName()
25
26 println("${oldVersionName}($oldVersionCode) -> ${newVersionName}($newVersionCode)")
27
28 // ビルドファイルに更新したversionName・versionCodeを書き込み
29 var updated = buildFile.readText()
30 updated = updated.replaceFirst(
31 "versionName = \"${oldVersionName}\"",
32 "versionName = \"${newVersionName}\""
33 )
34 updated = updated.replaceFirst(
35 "versionCode = $oldVersionCode",
36 "versionCode = $newVersionCode"
37 )
38 buildFile.writeText(updated)
39 }
40 }
41 }
42}
43
44// バージョン情報を出力するコマンド群
45tasks.register("printVersionCode") {
46 doLast {
47 println(android.defaultConfig.versionCode)
48 }
49}
50
51tasks.register("printVersionName") {
52 doLast {
53 println(android.defaultConfig.versionName)
54 }
55}
今回は、
- メジャー/マイナー/パッチそれぞれを操作する
bumpMajorVersion
/bumpMinorVersion
/bumpPatchVersion
の3コマンド - バージョンコード/バージョン名それぞれを標準出力する2コマンド
の合計5コマンドを実装しています。
個々の処理が何を行っているかはコード上のコメントを参照していただけると幸いですが、重要な部分を抜粋して解説します。
1-1. 旧バージョンの取得
1 // 旧バージョンを取得
2 val oldVersionCode = android.defaultConfig.versionCode ?: return@doLast
3 val oldVersionName = android.defaultConfig.versionName ?: return@doLast
4 val version = Version(oldVersionCode, oldVersionName)
プロジェクトに現在設定されているバージョン情報はandroid.defaultConfig
から参照することができます。
ここからバージョンコード/バージョン名を抽出し、先程実装したVersion
クラスのインスタンスを作成し、バージョン情報の操作を可能にします。
1-2. バージョン情報の操作
1 // メソッド名を取得しそのメソッドを実行(実行不可であればエラー)
2 val methodName = "bump${type}"
3 version.functionsByName[methodName]?.invoke() ?: error("Unknown method: $methodName")
4
5 // 更新したバージョンを取得
6 val newVersionCode = version.getCode()
7 val newVersionName = version.getName()
8
9 println("${oldVersionName}($oldVersionCode) -> ${newVersionName}($newVersionCode)")
10
11 // ビルドファイルに更新したversionName・versionCodeを書き込み
12 var updated = buildFile.readText()
13 updated = updated.replaceFirst(
14 "versionName = \"${oldVersionName}\"",
15 "versionName = \"${newVersionName}\""
16 )
17 updated = updated.replaceFirst(
18 "versionCode = $oldVersionCode",
19 "versionCode = $newVersionCode"
20 )
21 buildFile.writeText(updated)
実行したコマンド名から操作するバージョン番号のタイプを抽出し、それに対応したVersion
クラスのメソッドを実行することで次のバージョン情報を取得します。
実際のビルドファイルはbuildFile
で参照することができ、ここからバージョンコード/バージョン名の記載部分を読み取り、更新した次のバージョン情報に置き換え書き込むことでビルドファイルへ反映させます。
これでGradleタスクの実装は完了です。
これによりプロジェクトのルートディレクトリでターミナルから./gradlew bump(Major|Minor|Patch)Version
コマンドを実行することで、バージョン情報の更新を行うことができるようになりました!
ここで一旦実際にターミナルからコマンドを実行し、正しくバージョン情報が更新されるか動作確認を行なっておきましょうね。
次はこのコマンドをGitHub Actionsで実行するためのワークフローを実装していきます。
2. Gradleタスクを実行するワークフローの実装
メジャー/マイナー/パッチそれぞれの更新用ワークフローを実装する必要がありますが、叩くGradleタスクのコマンドを変更するだけなので、今回はメジャーバージョン更新用のワークフローに絞って見ていきましょう。
1# /.github/workflows/increment-major-version.yaml
2name: Increment Major Version
3
4# ワークフローは手動実行
5on: [ workflow_dispatch ]
6
7jobs:
8 build:
9 runs-on: ubuntu-latest
10
11 steps:
12 # releaseブランチのチェックアウト
13 - uses: actions/checkout@v4
14 with:
15 ref: release
16 fetch-depth: 0
17
18 # JDKのセットアップ
19 - name: set up JDK 17
20 uses: actions/setup-java@v2
21 with:
22 java-version: '17'
23 distribution: 'temurin'
24 cache: gradle
25
26 # gradlewの実行権限の付与
27 - name: Grant Permission gradlew
28 run: chmod +x gradlew
29
30 # メジャーバージョンの更新
31 - name: Bump Major Version
32 run: ./gradlew bumpMajorVersion
33
34 # 更新したバージョンを取得し出力変数に保存
35 - name: Get Version
36 run: |
37 echo "::set-output name=VERSION_CODE::$(./gradlew -q printVersionCode)"
38 echo "::set-output name=VERSION_NAME::$(./gradlew -q printVersionName)"
39 id: version
40
41 # バージョン更新処理をコミット & Pull Requestを作成
42 - name: Create Pull Request
43 uses: peter-evans/create-pull-request@v6
44 with:
45 token: ${{ secrets.GITHUB_TOKEN }}
46 branch: update-app-major-version-${{ steps.version.outputs.VERSION_NAME }}-${{ steps.version.outputs.VERSION_CODE }}
47 base: release
48 title: "chore: 🤖 versionName: ${{ steps.version.outputs.VERSION_NAME }} / versionCode: ${{ steps.version.outputs.VERSION_CODE }}"
49 commit-message: "chore: 🤖 versionName: `${{ steps.version.outputs.VERSION_NAME }}` / versionCode: `${{ steps.version.outputs.VERSION_CODE }}`"
2-1. gradlew
の実行権限の付与
1 - name: Grant Permission gradlew
2 run: chmod +x gradlew
GitHub ActionsのLinux環境ではgradlew
の実行権限が付与されていないことがあるため、明示的にchmod
コマンドで実行権限を付与しておきます。
2-2. メジャーバージョンの更新
1 - name: Bump Major Version
2 run: ./gradlew bumpMajorVersion
実行権限を付与したら、1で作成したバージョン更新のコマンドを実行します。
2-3. 更新したバージョンを取得し出力変数に保存
1 - name: Get Version
2 run: |
3 echo "::set-output name=VERSION_CODE::$(./gradlew -q printVersionCode)"
4 echo "::set-output name=VERSION_NAME::$(./gradlew -q printVersionName)"
5 id: version
次で行うPull Requestの自動作成で、Pull Requestタイトルやコミット名にバージョン情報を記載するため、更新後のバージョン情報をこのステップに保存します。
echo "::set-output name=A::B"
というコマンドで、Aという出力変数にBというコマンドの出力結果を保存することができます。
この出力変数をPull Requestの自動作成ステップで利用するため、id: version
で本ステップに参照名を指定し、出力変数を参照できるようにしています。
2-4. バージョン更新処理をコミット & PR作成
1 - name: Create Pull Request
2 uses: peter-evans/create-pull-request@v6
3 with:
4 token: ${{ secrets.GITHUB_TOKEN }}
5 branch: update-app-major-version-${{ steps.version.outputs.VERSION_NAME }}-${{ steps.version.outputs.VERSION_CODE }}
6 base: release
7 title: "chore: 🤖 versionName: ${{ steps.version.outputs.VERSION_NAME }} / versionCode: ${{ steps.version.outputs.VERSION_CODE }}"
8 commit-message: "chore: 🤖 versionName: `${{ steps.version.outputs.VERSION_NAME }}` / versionCode: `${{ steps.version.outputs.VERSION_CODE }}`"
バージョン更新で生じた差分をGitHub Actions上でコミット & Pull Requestを作成します。
ここでは、カスタムアクションとして公開されている peter-evans/create-pull-requestを利用します。
このカスタムアクションでは、読み書き可能なGitHubトークンや各プロパティを指定するだけで、リポジトリにPull Requestを作成してくれます。
GitHubトークンの取得方法は割愛しますが、秘匿情報になるため、必ずSecretsを通して取得するようにしてくださいね。
今回設定しているプロパティを軽くまとめておきます。
token |
|
---|---|
branch |
|
base |
|
title |
|
commit-message |
|
2-3で保存した出力変数はsteps.version.outputs.~
で参照することができ、これをPull Requestのタイトルやコミットメッセージで利用しています。
これで、GitHub Actionsからアプリのバージョン更新を自動で行う仕組みを作成することができました!
最後に実装全体を記載しますので、間違いや足りないところがないかの確認に利用してくださいね。
実装全体
1/// app/build.gradle.kts
2class Version(private var code: Int, version: String) {
3 private var major: Int
4 private var minor: Int
5 private var patch: Int
6
7 init {
8 val (major, minor, patch) = version.split(".").map { it.toInt() }
9 this.major = major
10 this.minor = minor
11 this.patch = patch
12 }
13
14 val functionsByName = listOf(::bumpMajor, ::bumpMinor, ::bumpPatch).associateBy { it.name }
15
16 @SuppressWarnings("unused")
17 fun bumpMajor() {
18 major += 1
19 minor = 0
20 patch = 0
21
22 code += 1
23 }
24
25 @SuppressWarnings("unused")
26 fun bumpMinor() {
27 minor += 1
28 patch = 0
29
30 code += 1
31 }
32
33 @SuppressWarnings("unused")
34 fun bumpPatch() {
35 patch += 1
36
37 code += 1
38 }
39
40 fun getName(): String = "$major.$minor.$patch"
41 fun getCode(): Int = code
42}
43
44tasks.addRule("Pattern: bumpVersion") {
45 if (this.matches(Regex("bump(Major|Minor|Patch)Version"))) {
46 task(this) {
47 doLast {
48 // コマンドから実行タイプを抽出
49 val type = this@addRule
50 .replace(Regex("bump"), "")
51 .replace(Regex("Version"), "")
52
53 println("Bumping ${type.lowercase(Locale.getDefault())} version...")
54
55 // 旧バージョンを取得
56 val oldVersionCode = android.defaultConfig.versionCode ?: return@doLast
57 val oldVersionName = android.defaultConfig.versionName ?: return@doLast
58 val version = Version(oldVersionCode, oldVersionName)
59
60 // メソッド名を取得しそのメソッドを実行(実行不可であればエラー)
61 val methodName = "bump${type}"
62 version.functionsByName[methodName]?.invoke() ?: error("Unknown method: $methodName")
63
64 // 更新したバージョンを取得
65 val newVersionCode = version.getCode()
66 val newVersionName = version.getName()
67
68 println("${oldVersionName}($oldVersionCode) -> ${newVersionName}($newVersionCode)")
69
70 // ビルドファイルに更新したversionName・versionCodeを書き込み
71 var updated = buildFile.readText()
72 updated = updated.replaceFirst(
73 "versionName = \"${oldVersionName}\"",
74 "versionName = \"${newVersionName}\""
75 )
76 updated = updated.replaceFirst(
77 "versionCode = $oldVersionCode",
78 "versionCode = $newVersionCode"
79 )
80 buildFile.writeText(updated)
81 }
82 }
83 }
84}
85
86tasks.register("printVersionCode") {
87 doLast {
88 println(android.defaultConfig.versionCode)
89 }
90}
91
92tasks.register("printVersionName") {
93 doLast {
94 println(android.defaultConfig.versionName)
95 }
96}
1# /.github/workflows/increment-major-version.yaml
2name: Increment Major Version
3
4on: [ workflow_dispatch ]
5
6jobs:
7 build:
8 runs-on: ubuntu-latest
9
10 steps:
11 - uses: actions/checkout@v4
12 with:
13 ref: release
14 fetch-depth: 0
15
16 - name: set up JDK 17
17 uses: actions/setup-java@v2
18 with:
19 java-version: '17'
20 distribution: 'temurin'
21 cache: gradle
22
23 - name: Grant Permission gradlew
24 run: chmod +x gradlew
25
26 - name: Bump Major Version
27 run: ./gradlew bumpMajorVersion
28
29 - name: Get Version
30 run: |
31 echo "::set-output name=VERSION_CODE::$(./gradlew -q printVersionCode)"
32 echo "::set-output name=VERSION_NAME::$(./gradlew -q printVersionName)"
33 id: version
34
35 - name: Create Pull Request
36 uses: peter-evans/create-pull-request@v6
37 with:
38 token: ${{ secrets.GITHUB_TOKEN }}
39 branch: update-app-major-version-${{ steps.version.outputs.VERSION_NAME }}-${{ steps.version.outputs.VERSION_CODE }}
40 base: release
41 title: "chore: 🤖 versionName: ${{ steps.version.outputs.VERSION_NAME }} / versionCode: ${{ steps.version.outputs.VERSION_CODE }}"
42 commit-message: "chore: 🤖 versionName: `${{ steps.version.outputs.VERSION_NAME }}` / versionCode: `${{ steps.version.outputs.VERSION_CODE }}`"
さいごに
さて、今回はGitHub Actionsを利用したAndroidアプリのバージョン情報の更新について見ていきました。
Kotlin DSLでGradleタスクのコマンドを実装できるため、Kotlinを利用したスクリプトをプロジェクト上に簡単に書けるのは嬉しいですね。
GitHub ActionsのLinux環境でも比較的高速に動き相性も良いため、こと自動化においては非常に強力なツールだと思います。
皆さんもこれを足がかりに様々な自動化に挑戦してみてはいかがでしょうか?
次回は冒頭でも述べたように、今回の内容を踏まえた1クリックでバージョン更新 & Firebase App Distributionへのアプリのデプロイ方法について書いていこうと思います!
どちらかというと今回は前座で次回が本番です(笑)
興味を持ってくださった方もそうでない方も楽しみにしていただけると嬉しいです!
今回の記事も最後までご覧いただき、本当にありがとうございました!
皆さんのスキルアップに少しでも貢献できていれば幸いです。
それではまた!!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
![こー(エンジニア)](/_next/image?url=https%3A%2F%2Fapi.rightcode.co.jp%2Fwp-content%2Fuploads%2F2022%2F10%2F%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88-2022-10-17-20.06.23-150x150.png&w=3840&q=75)
「クレヨンしんちゃんは人生のマニュアル」が口癖なモバイルアプリとバックエンドやってる人