やんくさんに頼まれたので、誰かが書くだろうと思って待ってたけど誰も書かない Gradle の kotlin-dsl
プラグイン について書きます。
まず、先日(2020/12/11)開催された JJUG ナイトセミ ナーにて、時間がないのでほぼ飛ばしてしまった、こちらの話題について解説します。
build.gradle
(build.gradle
) の中で if
文(Kotlin だと式ですね)とか for
文(こっちは式だったっけ?)を書くと、宣言的なコード(例えば依存ライブラリーの宣言など)と命令的なコードが混在してしまいます。そのため、ビルドスクリプト を読むために頭の切り替えが必要になるのでメンテナンス性が下がるというのが理由です。したがって、命令的なコードを書いている部分はすべてプラグイン にして、 buildSrc
プロジェクトに移動するのがよいと公式のドキュメントに書いてあります。
docs.gradle.org
しかし、これは素直におすすめできないところがあって、プラグイン を書くためには少しだけプラグイン の仕組みの学習コストがかかります(まあ、大したコストではありませんが…) 。そこで今持っている build.gradle
(build.gradle.kts
) の 知識でプラグイン を無理なく書ける仕組みを使うのがよいでしょう。
プリコンパイル ド・スクリプト ・プラグイン (Precompiled script plugins) というのがその仕組です。
docs.gradle.org
具体的には以前このブログで紹介した groovy-gradle-plugin
が Groovy の場合のプラグイン です。ここでは詳しく解説しないので、以下のエントリーを読んでください。
mike-neck.hatenadiary.com
これの Kotlin 版が kotlin-dsl
プラグイン です。(この言い方は語弊があって、先に kotlin-dsl
があって、その後に Groovy 版ができたらしい)
なお、ググラビリティが悪いので、このプラグイン の名前はどうにかしてほしい
kotlin-dsl
の書き方
buildSrc/build.gradle.kts
ファイルに以下のように書きます。 repositories {}
ブロックを忘れると kotlin-dsl
をビルドするための Kotlin をダウンロードできなくなってエラーが発生します
plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}
buildSrc/src/main/kotlin
の下に任意の名前で .gradle.kts
ファイルを作成します。ここではサンプルのため、 example.gradle.kts
というファイル名にします。なお、内容的には先の
Java toolchains のエントリーの build.gradle
と同じ様な内容にしています
plugins {
`java`
}
repositories {
mavenCentral()
}
dependencies {
compileOnly("org.jetbrains:annotations:19.0.0" )
implementation("org.slf4j:slf4j-api:1.7.30" )
testImplementation("org.junit.jupiter:junit-jupiter:5.7.0" )
testImplementation("org.assertj:assertj-core:3.18.1" )
}
internal val ss = sourceSets
internal val testSourceSets = ss.getByName("test" )
@Suppress ("UnstableApiUsage" )
internal val javaToolChains = extensions.getByType<JavaToolchainService>()
@Suppress ("UnstableApiUsage" )
internal val javaPluginExt = extensions.getByType<JavaPluginExtension>()
javaPluginExt.toolchain {
@Suppress ("UnstableApiUsage" )
languageVersion.convention(JavaLanguageVersion.of(8 ))
@Suppress ("UnstableApiUsage" )
vendor.convention(JvmVendorSpec.AZUL)
@Suppress ("UnstableApiUsage" )
implementation.convention(JvmImplementation.VENDOR_SPECIFIC)
}
open class ProjectTestConfig {
@Suppress ("UnstableApiUsage" )
fun testOnJavaVersion(vararg javaVersions: Int ): Map<Int , TaskProvider<Test>> =
javaVersions.map { javaVer ->
javaVer to tasks.register<Test>("testJava ${ javaVer} " ) {
group = "verification"
description = "runs test on Java $javaVer "
classpath = testSourceSets.runtimeClasspath
testClassesDirs = testSourceSets.output.classesDirs
useJUnitPlatform()
javaLauncher.convention(javaToolChains.launcherFor {
languageVersion.convention(JavaLanguageVersion.of(javaVer))
})
}
}.onEach { pair ->
tasks.named("check" ).configure { dependsOn(pair.second) }
}.fold(mapOf()) { acc, pair ->
acc + pair
}
}
extensions.add<ProjectTestConfig>("testConfig" , ProjectTestConfig())
なお、このスクリプト ですが、書いている途中でコンパイル エラーが発生すると補完が効かなくなるので、気合で乗り越えていく必要があります。
これが僕が Kotlin DSL をあまり好まない理由の一つです。
tasks くらい取れるだろ
`launcherFor` だったっけな? `languageVersion` だったっけな?補完してくれなきゃわからん
最後に、ルートディレクト リーつまりメインプロジェクトの build.gradle.kts
です。ここで、先程の .gradle.kts
ファイルの拡張子を抜いた部分(ここまでの例だと example.gradle.kts
→ example
) をプラグイン として指定します。
plugins {
`example`
}
testConfig.testOnJavaVersion(11 , 15 )
かなりシンプル(というより、なにもない)になりました。
では、 Java 8 以外で実行すると落ちるようなテストを書いて実行してみます
class Version {
@Test
void show() {
String javaHome = System.getProperty("java.home" );
assertThat(javaHome).contains("jre" );
}
}
check タスクから起動。途中のテストで落ちるので continue オプション付き
Java 11 と 15 でめでたくテストが落ちたようです
というわけで、 kotlin-dsl
を使うとビルドスクリプト がシンプルになったと思います。また、 groovy での記事でも書いたように複数のサブプロジェクトに対してプロジェクト横断的に同一な記述が必要になるケース(例えば、同じ Kotlin のバージョンと Linter と フォーマッターを使いたい)で、 kotlin-dsl
で作ったプラグイン をサブプロジェクトで利用できます。
kotlin-dsl
のおさらい
プロジェクト(プロダクト)用の build.gradle.kts
から命令的なコードを取り除いて、 buildSrc
プロジェクトに命令的なコードを記述する
buildSrc
にプラグイン を書く場合に kotlin-dsl
を使うと .gradle.kts
ファイルを作成する
で作成した .gradle.kts
ファイルのピリオドよりも前の部分をメインのスクリプト のプラグイン に適用する
以上のような形でビルドスクリプト をシンプルにできます。