こんにちは、こんばんは、kitakkunです。
Gradleビルドスクリプトの共通化・外部化する際、Composite Build という仕組みを活用して Convention Plugin を実装することが一般的になってきました。
しかし、現在広く使われている org.gradle.api.Plugin
を実装する手法には、いくつか欠点があります。
- ボイラープレートが増える。
-
settings.gradle.kts
の共通化が難しい。
本記事では、ktsファイル を使ってより効率的かつ柔軟に Convention Plugin を構成する手法を紹介します。
従来の方法
説明上、簡単なものの方が好ましいと思うので、適用時に "Hello, convention plugin!"
と出すだけのものを作ります。
gradle-conventions モジュールの作成
まず、Convention Pluginの実装を含むモジュール(gradle-conventions
)を作ります。
gradle-conventions
├─ src/main/kotlin/MyConventionPlugin.kt
├─ build.gradle.kts
└─ settings.gradle.kts
class MyConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
println("Hello, convention plugin!")
}
}
plugins {
`kotlin-dsl`
}
gradlePlugin {
plugins {
register("my-convention") {
id = "my-convention"
implementationClass = "MyConventionPlugin"
}
}
}
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
まあよくある構成ですね。
プラグインを includeBuild する
次に、ルートプロジェクトの settings.gradle.kts
内の pluginManagement
で includeBuild
します。
pluginManagement {
includeBuild("gradle-conventions")
}
すると、以下のようにカスタムプラグインが適用可能になります。
plugins {
id("my-convention")
}
Gradle Sync して以下のように出ればOKです。
> Configure project :
Hello, convention plugin!
このように、一般的には org.gradle.api.Plugin
クラスを実装する方法が使われます。
しかし、実は src の中に直接 gradle.kts を配置する方法もあります。
gradle.kts を src 配下に置く手法
src
の配下に println
だけ書いた my-convention.gradle.kts
を作成します。
println("Hello, convention plugin! (from kts)")
gradlePlugin
の定義は不要なので消してしまいます。
plugins {
`kotlin-dsl`
}
- gradlePlugin {
- plugins {
- register("my-convention") {
- id = "my-convention"
- implementationClass = "MyConventionPlugin"
- }
- }
- }
gradle-conventions/src/main/kotlin/MyConventionPlugin.kt
も削除します。
- class MyConventionPlugin : Plugin<Project> {
- override fun apply(target: Project) {
- println("Hello, convention plugin!")
- }
- }
もう一度 Gradle Sync してみましょう。同じ結果になるはずです。
> Configure project :
Hello, convention plugin! (from kts)
随分と短く簡潔に作れましたね。ボイラープレートも減って見やすくなりました。
何が起きているのか?
src
内に配置された gradle.kts
は、Pluginクラスにプリコンパイルされます。
ですから、my-convention.gradle.kts
からは MyConventionPlugin
が生成されます。
/**
* Precompiled [my-convention.gradle.kts][My_convention_gradle] script plugin.
*
* @see My_convention_gradle
*/
public
class MyConventionPlugin : org.gradle.api.Plugin<org.gradle.api.Project> {
override fun apply(target: org.gradle.api.Project) {
try {
Class
.forName("My_convention_gradle")
.getDeclaredConstructor(org.gradle.api.Project::class.java, org.gradle.api.Project::class.java)
.newInstance(target, target)
} catch (e: java.lang.reflect.InvocationTargetException) {
throw e.targetException
}
}
}
自動生成された MyConventionPlugin
は、ファイル名の my-convention
の部分をid
として適用できます。
settings.gradle.kts
を共通化する
kts化するもう一つのメリットは、settings.gradle.kts
が共通化しやすいことです。
settings.gradle.kts
の共通化が必要なケースはそう多くないと思いますが、動的に生成する versionCatalog をプロジェクト全体で一貫して使用したい場合などに役立ちます。
例: Kotlinバージョンを動的に切り替えてコンパイラプラグインをビルドしたい時
repositories を設定する Convention Plugin を作成する例
簡単な例として、プロジェクトの依存アーティファクトの参照先となる repositories
を設定するConvention Plugin を作ってみましょう。
現在、settings.gradle.kts
には repositories
の定義が欠けていますので、
pluginManagement {
includeBuild("gradle-conventions")
}
build.gradle.kts
で Kotlin JVMプラグインを適用しようとすると・・
plugins {
id("my-convention")
id("org.jetbrains.kotlin.jvm") version "2.0.0"
}
以下のように「プラグインが解決できないよ」と怒られます。
conventions:main: Cannot resolve external dependency org.jetbrains.kotlin:kotlin-stdlib:2.0.0 because no repositories are defined.
Required by:
root project :
Possible solution:
- Declare repository providing the artifact, see the documentation at https://docs.gradle.org/current/userguide/declaring_repositories.html
ゴールは、このエラーを消すことです。具体的な手順は2ステップ。
-
settings-conventions.settings.gradle.kts
にrepositories
設定を書く。 - ルートの
settings.gradle.kts
内でプラグインを適用する。
まず、settings-conventions.settings.gradle.kts
は以下のように書きます。
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
これをルートプロジェクトのsettings.gradle.kts
で適用してあげます。
pluginManagement {
includeBuild("gradle-conventions")
}
+ plugins {
+ id("settings-conventions")
+ }
すると、Gradle Sync が通るようになるはずです。
ついでに gradle-conventions
にも冗長な repositories
定義があるので、このプラグインで代替します。
- pluginManagement {
- repositories {
- mavenCentral()
- gradlePluginPortal()
- }
- }
- dependencyResolutionManagement {
- repositories {
- mavenCentral()
- }
- }
+ apply(from = "src/main/kotlin/settings-conventions.settings.gradle.kts")
こちらも問題なく Gradle Sync 成功するはずです。
まとめ
本記事では、gradle.kts の形式でConvention Pluginを実装することで、ボイラープレートを減らしつつ settings.gradle.kts
の共通化も含め柔軟にビルド設定が行えることをご紹介しました。
この手法は、kotlinx.rpc リポジトリをコードリーディングしていた際に発見したものです。
settings.gradle.kts
の共通化などはエッジケースかと思いますが、Convention Pluginの実装において面倒な作業を極限まで省けるという恩恵は、どなたでも享受できるものかと思います。
kts convention、やっていきましょう
宣伝
現在個人的に開発している Kondition というコンパイラプラグインがあります。
簡単に説明すると、アノテーションをつけることで特定の条件が満たされていることを保証できるプラグインとなっています。
fun myFunction(@NonEmpty value: String) {
// この中では value が空文字でないことが保証される
// ※require(value.isNotEmpty()) がIRレベルで生成される
}
Kotlinの複数バージョン対応を行う際に、ktsベースの Convention Plugin がかなり役に立ちました。具体的には、versionCatalogs の共通化など少し高度なことを行っています。もし興味がありましたらご覧ください!
ここまで読んでいただきありがとうございました!
Gradle、何もわからん!!!!