はじめに
Version Catalog 使っていますか?
Version Catalog と言えば、依存関係とプラグインを効率的に管理するための仕組みですが、最近のAndroid開発において重要になってきそうな雰囲気を感じています。
・Android Studio Giraffe からサポートされたり…(まだ色々と制限はあるようですが)
・移行ガイドができてたり…
・ナウいAndroidのリファレンスのアプリである nowinandroid でも普通に使われていたり…
そんな Version Catalog ですが、既存のプロジェクトで移行するとなると libs.versions.toml にライブラリのバージョン定義を移すのがチョット面倒だと思います。
(※ settings.gradleに定義する方法もあります。)
この記事では、Gradleのタスクを利用してVersion Catalogへチョット楽に移行する方法 をご紹介したいと思います。
※ 一応OSSのプラグインで libs.versions.toml を生成できるものがあったのですが、実際に試してみると思ってたのと違ったので自作するに至りました。
※ 自作すると言いつつGradleは全然詳しくないので、ChatGPTに手伝ってもらったりしました。 ![]()
Gradleのタスク
早速ですが、タスクのコードとしては下記になります。
これをルートの build.gradle に書き、タスクを実行(./gradlew generateVersionCatalog)すると gradle ディレクトリに libs.versions.toml が生成されます。
task generateVersionCatalog {
// groupの置換用定義
def groupReplaceMap = [
"com.google.android.": "",
"com.google." : "",
]
// 対象となるConfiguration定義
def targetConfigurationNames = [
"implementation",
"kapt",
]
doLast {
def libraries = []
def versions = []
subprojects.each { project ->
// #1 Configurationの取得
project.configurations.each { configuration ->
if (!targetConfigurationNames.contains(configuration.name)) {
return
}
// #2 Dependencyのチェック
configuration.dependencies.each { dependency ->
if (dependency.group == null || dependency.version == "unspecified") {
return
}
// #3 groupの置換
def group = dependency.group
groupReplaceMap.each {
if (group.contains(it.key)) {
group = group.replace(it.key, it.value)
}
}
// #4 versionの調整
// BOMを使っている場合など、versionがnullになるため、空文字で対応
def version = dependency.version ?: ""
// #5 tomlのkeyのベースとなる文字列の生成
def key = ""
if (!group.isEmpty()) {
// groupがあれば「.」を「-」に置換して付与(例: androidx.core → androidx-core)
key += "${group.replaceAll('\\.', '-')}-"
}
key += dependency.name
// groupとvalueで文字が重複するのを避ける(例: androidx-core-core-ktx → androidx-core-ktx)
key = key.split("-").toUnique().join("-")
// #6 versionのkeyはcamelCase
def versionKey = key.toLowerCase().replaceAll("(-)([A-Za-z0-9])", { Object[] s -> s[2].toUpperCase() })
if (!version.isEmpty()) {
versions.add("${versionKey} = \"${version}\"")
}
// #7 libraryのkeyはkebab-case
def libraryKey = key.toLowerCase()
def libraryValue = "{ group = \"${dependency.group}\", name = \"${dependency.name}\""
if (!version.isEmpty()) {
libraryValue += ", version.ref = \"${versionKey}\""
}
libraryValue += " }"
libraries.add("${libraryKey} = ${libraryValue}")
}
}
}
// 8
versions.unique().sort()
libraries.unique().sort()
// 9
def file = file("gradle/libs.versions.toml")
def fileContent = ""
fileContent += "[versions]\n"
fileContent += versions.join("\n")
fileContent += "\n\n"
fileContent += "[libraries]\n"
fileContent += libraries.join("\n")
fileContent += "\n\n"
fileContent += "[plugins]\n"
file.write(fileContent)
}
}
コードの説明
大事そうな箇所のコードの説明をしたいと思います。
#1 Configurationの取得
バージョン定義を移すに当たり、 build.gradle の dependencies に定義されているものを取ってくる必要があります。
例えばこのようなやつ ↓
dependencies {
implementation "androidx.core:core-ktx:1.9.0"
implementation "com.google.dagger:hilt-android:2.4.3"
kapt 'androidx.hilt:hilt-compiler:1.0.0'
}
project.configurations で取得できる configuration に、 implementation や kapt などの情報が管理されているのでそれを使うことができます。
イメージはこのような感じです。
| configuration | dependencies |
|---|---|
| implementation | [androidx.core:core-ktx:1.9.0, com.google.dagger:hilt-android:2.4.3] |
| kapt | [androidx.hilt:hilt-compiler:1.0.0] |
| ... | ... |
ただ、対象としなくてもいい configuration があったりするので、必要なものだけ targetConfigurationNames として定義して、それ以外はスキップするようにしてます。
#2 Dependencyのチェック
configuration.dependencies で取得できる dependency に、依存情報( androidx.core:core-ktx:1.9.0 や com.google.dagger:hilt-android:2.4.3)が含まれています。
androidx.core:core-ktx:1.9.0 の例でいうとこのような感じです。
| 値 | |
|---|---|
| dependency.group | androidx.core |
| dependency.name | core-ktx |
| dependency.version | 1.9.0 |
この際、dependency.group が null だったり、dependency.version が unspecified のものはスキップします。
(プロジェクトのサブモジュールを implementation している場合などに該当するようでした。)
#3~7 toml 形式に変換
#2までで、ライブラリの依存関係を取得することができたので、あとはこねくり回して libs.versions.toml を生成していきます。![]()
このような定義があった場合、
implementation("androidx.core:core-ktx:1.9.0")
implementation("com.google.dagger:hilt-android:2.4.3")
下記のような toml が生成できるといいのかなと考えて、ロジックを作成しています。
コードにコメントを書いているので興味があれば覗いてみてください。
[versions]
androidXCoreKtx = "1.9.0"
hiltAndroid = "2.4.3"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCoreKtx" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltAndroid" }
#8~9 libs.versions.toml を出力
libraries と versions にそれぞれ値が入ったので、重複を取り除き、並び替えしてから、Version Catalogのフォーマットに沿うようにして出力します。
[versions]
[libraries]
[plugins]
実行してみる
実際にタスクを実行してみた様子はこちら ↓
無事 libs.versions.toml が生成されるかと思います。
移行を完了する
後は手作業で implementation の箇所を libs.xxx.xxx の形に置き換えて行けばOKです。
ここもうまいことすれば自動化できるのかもしれませんが、コード補完を駆使すればそこまで苦ではないかなという印象です。
implementation("androidx.core:core-ktx:1.9.0")
// ↓
implementation(libs.androidx.core.ktx)
また、記載したタスクではプラグイン(Gradle Plugin)は未対応となっています。
ただ、ライブラリの定義に比べ、プラグインの定義はそこまで数が多くないと思うので、手作業 or 下記のツールを使うことで十分対応可能かなと思います。
takahirom / gradle-version-catalog-converter ![]()
まとめ
今回の記事では、Version Catalog への移行を手軽に行うためのGradleタスクをご紹介しました。このタスクを使って、libs.versions.tomlファイルを簡単に生成できるようになり、依存関係の管理がスムーズになります。ただし、プラグインに関しては対応していないので、手作業や別のツールを使って対応する必要があります。
この方法を試してみることで、Android開発での依存関係の管理が楽になり、プロジェクトのメンテナンス性が向上することを期待しています。ぜひお試しください!
(もっといい方法やツールをご存知でしたらコメントいただけると幸いです
)
