Android, Gradle: Version Catalog と PluginManagement を使って Gradle プラグインを利用する


## 2023/02/28 新しい記事を書きました

PluginManagement の設定が不要となるおすすめの設定について記事を書きました。Version Catalog にバージョン情報を集約できるという点で、新しい記事の設定方法がおすすめです。

新しい記事

https://qiita.com/irgaly/items/b87ceb423fbd413ff398

---

以下、この記事のもとの内容です。

---

Gradle Version Catalog を使って、Gradle Plugin を利用する方法を解説します。

## 前提

* Gradle 7.2 以上
    * Gradle 7.2 から Version Catalog に `[plugins]` セクションを使えるようになりました
* Gradle Kotlin DSL 使用
    * 解説は Kotlin DSL とします

## 実装

### buiildscript ブロックの削除

build.gradle.kts に buildscript ブロックを定義している場合は削除してください。新しい Plugin DSL では classpath() 定義が不要になりました。

`(project root)/build.gradle.kts`

```diff
-buildscript {
-    repositories {
-        google()
-        mavenCentral()
-        gradlePluginPortal()
-    }
-    dependencies {
-       classpath(...)
-       classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.1")
-    }
-}
```

### settings.gradle.kts の設定

settings.gradle.kts から enableFeaturePreview() で VERSION_CATALOG を有効化し、pluginManagement を定義して Plugin の依存解決に必要なリポジトリを定義します。

`settings.gradle.kts`

```kotlin
enableFeaturePreview("VERSION_CATALOGS")
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
```

### Version Catalog の定義

Version Catalog を用意して、`[plugins]` セクションを定義します。ここでは例として AndroidX Navigation SafeArgs Plguin を定義しています。AndroidX Navigation SafeArgs Plugin は 2.4.0 から新しい Plugin DSL に対応しました。2.4.0 ではエラーが出て設定できなかったため、ここでは 2.4.1 を指定しています。

`gradle/libs.versions.toml`

```toml
...
[plugins]
androidx-navigation-safeargs = { id = "androidx.navigation.safeargs", version = "2.4.1" }
```

これで、`libs.plugins.*` の形式で Plugin 情報へアクセスできるようになりました。

### Plugin の利用

build.gradle.kts の plugins ブロックで alias() を使って Plugin を利用します。

`app/build.gradle.kts`

```kotlin
plugins {
    id("com.android.application")
    kotlin("android")
...
    alias(libs.plugins.androidx.navigation.safeargs)
}
...
```

これで、Plugin が適用されました。

## 実装 (subprojects / allprojects での利用)

すべてのプロジェクトへ Plugin を適用するには subprojects ブロックか allprojects ブロックを使用しますが、この場合は特殊な設定が必要になります。

例として [ktlint-gradle Plugin](https://github.com/JLLeitschuh/ktlint-gradle) を allprojects ブロックですべてのプロジェクトに適用する場合の設定を解説します。

### Version Catalog の定義

Version Catalog へ Plugin を定義します。このとき、バージョン番号は省略します。

`gradle/libs.versions.toml`

```toml
[plugins]
ktlint-gradle = { id = "org.jlleitschuh.gradle.ktlint" }
```

### pluginManagement の定義

settings.gradle.kts の pluginManagement で、Plugin のバージョンを定義します。Plugin ID から Plugin のバージョンを特定するためにこの設定が必要です。残念ながら、いまのところ settings.gradle.kts から Version Catalog へはアクセスできないため、subprojects / allprojects から apply() を使う場合は Version Catalog へバージョンを定義して使用することはできません。

`settings.gradle.kts`

```kotlin
...
pluginManagement {
...
    plugins {
        ...
        id("org.jlleitschuh.gradle.ktlint") version "10.0.0"
    }
}
```

### subprojects / allprojects と apply() で Plugin を使用する

build.gradle.kts の allprojects ブロックで、apply() により Plugin を適用します。

また、先頭の plugins ブロックでも Plugin を定義する必要があります。先頭の plugins ブロックでの Plugin 定義がないと `Plugin with id '{Plugin id}' not found.` エラーとなってしまいます

`(project root)/build.gradle.kts`

```kotlin
import org.jlleitschuh.gradle.ktlint.KtlintExtension
plugins {
    // plugins ブロックでの Plugin 読み込みは必須。
    // ここでは Plugin の適用を実行しないため .apply(false) を設定している。
    // apply は infix スタイルに対応しているため、`apply false` と書ける。
    id(libs.plugins.ktlint.gradle.get().pluginId) apply false
}
allprojects {
...
    afterEvaluate {
        // 対象プロジェクトの afterEvaluate の中でのみ libs.plugins.* へのアクセスが成功します
        // libs.plugins.ktlint.gradle.get().pluginId は
        // Version Catalog で定義した "org.jlleitschuh.gradle.ktlint" を取得できます
        apply(plugin = libs.plugins.ktlint.gradle.get().pluginId)
        configure<KtlintExtension> {
...
        }
    }
}
```

これで、従来の subprojects / allprojects の形式で Plugin を適用することができます。

## 新しい Plugin DSL に対応していない Plugin の利用

利用したい Plugin が [Plugin Markers](https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers) に対応していなければ、新しい Plugin DSL で設定することはできません。利用したい Plugin がまだ新しい Plugin DSL に対応していない場合は、resolutionStrategy によるモジュールの解決が必要になります。以前までの classpath() 設定の代替として resolutanioStrategy を設定します。

たとえば、新しい Plugin DSL に対応していない AndroidX Navigation SafeArgs 2.3.5 であれば以下のように設定します。

Version Catalog と alias() の設定は新しい Plugin DSL と同様に設定します。

`gradle/libs.versions.toml`

```toml
...
[plugins]
androidx-navigation-safeargs = { id = "androidx.navigation.safeargs", version = "2.3.5" }
```

`app/build.gradle.kts`

```kotlin
plugins {
    id("com.android.application")
    kotlin("android")
...
    alias(libs.plugins.androidx.navigation.safeargs)
}
...
```

Plugin Marker の存在しない Plugin は、classpath() の代わりに、resolutionStrategy から useModule() を設定します。useModule() には classpath() へ渡していた Plugin Dependency を渡します。Version Catalog に Plugin バージョンを定義していれば `requested.version` に指定済みのバージョン番号が含まれています。

`settings.gradle.kts`

```kotlin
...
pluginManagement {
...
    resolutionStrategy {
        eachPlugin {
            when (requested.id.id) {
                "androidx.navigation.safeargs" -> useModule("androidx.navigation:navigation-safe-args-gradle-plugin:${requested.version}")
                //"{other plugin id}" -> useModule("{plugin dependency}")
            }
        }
    }
}
```

この設定により、新しい Plugin DSL に対応していない Plugin を読み込むことができます。

## Version Catalog で管理していない Plugin のバージョンを pluginManagement で指定する

Gradle Plugin のバージョンはなるべく Version Catalog に定義して alias() によって使用するのがお勧めですが、Version Catalog で管理していない Plugin でも pluginManagement にバージョンを設定しておくことができます。

たとえば、[kotlinx.serialization Plugin](https://github.com/Kotlin/kotlinx.serialization) のバージョンを指定する場合は以下のようになります。

`settings.gradle.kts`

```kotlin
...
pluginManagement {
...
    plugins {
        kotlin("plugin.serialization") version "1.6.10"
    }
...
}
...
```

この設定により、build.gradle.kts の plugins ブロックではバージョン指定が不要となります。

`app/build.gradle.kts`

```kotlin
plugins {
...
    // kotlin("plugin.serialization") version "1.6.10" のようなバージョン指定が不要となる
    kotlin("plugin.serialization")  
...
}
...
```

## Gradle Plugin が新しい Plugin DSL に対応しているかを確認する

Gradle Plugin の [Plugin Markers](https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers) Artifact がアップロードされていれば新しい Plugin DSL で読み込むことができます。

Plugin のドキュメントや README を確認して、classpath() の定義がない新しい Plugin DSL での指定方法が記載されていれば、新しい Plugin DSL に対応していることがわかります。

また、その Gradle Plugin が [Gradle Plugin Portal](https://plugins.gradle.org/) で公開されていれば、新しい Plugin DSL に対応していると思われます。

Gradle Plugin が Google Maven Repository など、Gradle Plugin Portal 以外のリポジトリに設置されている場合は `{plugin id}.gradle.plugin` Artifact がアップロードされていれば、新しい Plugin DSL に対応していることがわかります。

* https://maven.google.com/web/index.html?q=androidx.navigation.safeargs.gradle.plugin

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/be33e232-bd25-920c-254f-dea144bf6f03.png)

## 参考

https://docs.gradle.org/current/userguide/plugins.html