1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Android】Gradleのタスクを利用してVersion Catalogへチョット楽に移行する

1
Posted at

はじめに

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に手伝ってもらったりしました。 :robot:

:elephant: 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)
    }
}

:mag: コードの説明

大事そうな箇所のコードの説明をしたいと思います。

#1 Configurationの取得

バージョン定義を移すに当たり、 build.gradledependencies に定義されているものを取ってくる必要があります。

例えばこのようなやつ ↓

build.gradle
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 に、 implementationkapt などの情報が管理されているのでそれを使うことができます。

イメージはこのような感じです。

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.0com.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.groupnull だったり、dependency.versionunspecified のものはスキップします。
(プロジェクトのサブモジュールを implementation している場合などに該当するようでした。)

#3~7 toml 形式に変換

#2までで、ライブラリの依存関係を取得することができたので、あとはこねくり回して libs.versions.toml を生成していきます。:amphora:

このような定義があった場合、

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 を出力

librariesversions にそれぞれ値が入ったので、重複を取り除き、並び替えしてから、Version Catalogのフォーマットに沿うようにして出力します。

libs.versions.toml
[versions]

[libraries]

[plugins]

:arrow_forward: 実行してみる

実際にタスクを実行してみた様子はこちら ↓

無事 libs.versions.toml が生成されるかと思います。

gen_version_catalog.gif

:writing_hand: 移行を完了する

後は手作業で 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 :pray:

:robot: まとめ

今回の記事では、Version Catalog への移行を手軽に行うためのGradleタスクをご紹介しました。このタスクを使って、libs.versions.tomlファイルを簡単に生成できるようになり、依存関係の管理がスムーズになります。ただし、プラグインに関しては対応していないので、手作業や別のツールを使って対応する必要があります。

この方法を試してみることで、Android開発での依存関係の管理が楽になり、プロジェクトのメンテナンス性が向上することを期待しています。ぜひお試しください!

(もっといい方法やツールをご存知でしたらコメントいただけると幸いです :pray:

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?