JUnit 5のビルドシステムを探る(その3)

<a href="https://qiita.com/advent-calendar/2023/yumemi-23-graduation">
  <img alt="YUMEMI New Grad Advent Calendar 2023" width="1240" height="112" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672609/6d8a7098-9aef-a19a-54fe-83a2d493e033.png">
</a>

前回はJUnit 5の各プロジェクトで共通に使用されているプラグインについてみました。今回は使用しているライブラリなどのバージョンの設定方法について見ていきます。

JUnit 5のライセンス Eclipse Public License - v 2.0

https://github.com/junit-team/junit5/blob/main/LICENSE.md

JUnit 5のリポジトリ

https://github.com/junit-team/junit5

この記事は [`f5b78f68c6a90dfa63611c7a10027581c5d913ba`](https://github.com/junit-team/junit5/tree/f5b78f68c6a90dfa63611c7a10027581c5d913ba) での内容をベースにしています。
記事の中に出てくるコードはすべて上記のリポジトリや公式ドキュメントからの引用や一部改変です。

# 概要

* [JUnit 5リポジトリの構成について](https://qiita.com/k-kojima-yumemi/items/f05aaf38b77abd45db41)
* [共通で使われている設定について](https://qiita.com/k-kojima-yumemi/items/e9a28a3849c3c5693a7d)
* バージョンの記述について<この記事>
* 個々のプロジェクトの設定について

前回の記事

https://qiita.com/k-kojima-yumemi/items/e9a28a3849c3c5693a7d

前回は共通の設定を記述しているプラグインについてみていきました。

今回は使用されているライブラリのバージョンの記述についてみていきます。

# Version Catalogについて

Gradleにはversion catalogを呼ばれるシステムがあり、複数のプロジェクトで依存関係を共有することができます。

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

## `settings.gradle.kts` での設定方法

公式ドキュメントには以下の記述が例として挙げられています。この記述により、 `build.gradle.kts` 内で `libs.groovy.core` と参照することができます。`libs.groovy.core` は `library("groovy-core", "org.codehaus.groovy:groovy:3.0.5")` で定義されているgroovyのライブラリを表します。ライブラリの名前のハイフンやアンダースコアはドット(`.`)に変換されるルールになっています。
`libs` 以下の各ライブラリについて、IDEの補完が効きます。

```kotlin settings.gradle.kts
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            library("groovy-core", "org.codehaus.groovy:groovy:3.0.5")
            library("groovy-json", "org.codehaus.groovy:groovy-json:3.0.5")
            library("groovy-nio", "org.codehaus.groovy:groovy-nio:3.0.5")
            library("commons-lang3", "org.apache.commons", "commons-lang3").version {
                strictly("[3.8, 4.0[")
                prefer("3.9")
            }
        }
    }
}
```

この方法であればサブプロジェクト間で依存しているバージョンが異なってしまうことを防ぎやすくなります。ドキュメントにもある通り、この方法は使用するバージョンを強制する方法ではないので、全てのバージョンを合わせるのであればバージョンの書き方を `libs.groovy.core` の形式で統一する必要があります。

```kotlin build.gradle.kts
dependencies {
    implementation(libs.groovy.core)
    implementation(libs.groovy.json)
    // version catalogの書き方と今まで通りの書き方を併用することも可能
    implementation("org.codehaus.groovy:groovy-nio:3.0.5")
}
```

コメントにもある通り、これまで通りの書き方と混在させることも可能ですがバージョンの記述箇所が散在してしまうためおすすめされません。

## `libs.versions.toml` での設定方法

さらに、以下の方法でtomlファイルを記述することでもversion catalogを使用できます。

https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format

```toml gradle/libs.versions.toml
[versions]
groovy = "3.0.5"

[libraries]
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }
```

こちらの方法でも上記の `settings.gradle.kts` と同様に `libs.groovy.core` で参照ができます。
dependabotもRenovateもlibs.version.tomlを認識してバージョンのチェックをしてくれるようです。

https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#gradle

https://docs.renovatebot.com/modules/manager/gradle/

## Pluginのバージョン定義

tomlでは以下のようにPluginのバージョンを定義できます。

```toml gradle/libs.versions.toml
[plugins]
versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }
```

使用する場合にはpluginブロックの中に以下のように記述します。

```kotlin build.gradle.kts
plugins {
    alias(libs.plugins.versions)
}
```

ただしこの記法はプラグインの定義内では使用できません。プロジェクトの定義でのみ使用できます。
各プロジェクトで共通に使用されているプラグインの中では別の方法での記述が必要です。

# JUnit 5でのバージョン管理

JUnit 5では `gradle/libs.versions.toml` にバージョン定義を記述しています。

https://github.com/junit-team/junit5/blob/main/gradle/libs.versions.toml

ファイルをよく見てみると `[libraries]` と `[plugins]` に一部重複して記述されているライブラリがあります。これは `[libraries]` をうまく使うことで、[`gradle/plugins`](https://github.com/junit-team/junit5/tree/main/gradle/plugins) 配下の、共通で使用しているプラグイン内で依存するプラグインのバージョンを統一しています。(プラグイン内でdependenciesにversion catalogを使うにはさらに工夫が必要です)

## 共通で使用しているプラグイン内でのバージョン管理

共通で使用しているプラグイン内でのバージョンを揃えるために、 `gradle/plugins/settings.gradle.kts` で以下の定義がされています。

```kotlin settings.gradle.kts
dependencyResolutionManagement {
	versionCatalogs {
		create("libs") {
			from(files("../libs.versions.toml"))
		}
	}
}
```

この記述により `gradle/libs.versions.toml` からVersion Catalogを読み込んでいます。この方法についてはドキュメントにも記載があります。

https://docs.gradle.org/current/userguide/platforms.html#sec:importing-catalog-from-file

またプラグインの中で依存しているプラグインを定義するために、`gradle/plugins/common/build.gradle.kts` で以下の記載があります。この記載によって `libs.versions.toml` で定義したバージョンのプラグインを使用するようにできます。

```kotlin build.gradle.kts
dependencies {
	implementation(projects.buildParameters)
	implementation(kotlin("gradle-plugin"))
    // これ以下の部分がlibs.versions.tomlで定義されているプラグインです
	implementation(libs.gradle.bnd)
	implementation(libs.gradle.commonCustomUserData)
	implementation(libs.gradle.enterprise)
	implementation(libs.gradle.foojayResolver)
	implementation(libs.gradle.shadow)
	implementation(libs.gradle.spotless)
	implementation(libs.gradle.versions)
}
```

例えば `junitbuild.shadow-conventions.gradle.kts` のファイルではshadowプラグインを使用しています。
```kotlin
plugins {
	id("junitbuild.java-library-conventions")
	id("com.github.johnrengelman.shadow")
}
```

https://github.com/junit-team/junit5/blob/main/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts

プラグイン内ではバージョンの記載はしませんが、これで `build.gradle.kts` でdependenciesに記述したバージョンが使用されます。

dependencies内の依存ライブラリを記述するための工夫として、JUnit 5では `ProjectExtensions.kt` のファイルを定義しています。

https://github.com/junit-team/junit5/blob/main/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt

ここで定義されている `dependencyFromLibs` の関数を使うことで `libs.versions.toml` で定義したバージョン定義を使用しています。ただしライブラリ名の補完が効きません。`junitbuild.testing-conventions.gradle.kts` などのファイルで使用されています。

https://github.com/junit-team/junit5/blob/main/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts

Gradleのドキュメントではこちらに書かれている内容になります。

https://docs.gradle.org/current/userguide/platforms.html#sub:type-unsafe-access-to-catalog

## 各プロジェクトでのバージョン管理

個々のプロジェクトではドキュメント通り定義するだけです。例えば `junit-jupiter-engine.gradle.kts` のファイルでは以下のようにdependenciesを定義しています。

```kotlin build.gradle.kts
dependencies {
	api(platform(projects.junitBom))
	api(projects.junitPlatformEngine)
	api(projects.junitJupiterApi)

	compileOnlyApi(libs.apiguardian)

	testImplementation(projects.junitPlatformLauncher)
	testImplementation(projects.junitPlatformSuiteEngine)
	testImplementation(projects.junitPlatformTestkit)
	testImplementation(testFixtures(projects.junitPlatformCommons))
	testImplementation(kotlin("stdlib"))
	testImplementation(libs.jimfs)
	testImplementation(libs.junit4)
	testImplementation(libs.kotlinx.coroutines)
	testImplementation(libs.groovy4)
	testImplementation(libs.memoryfilesystem)
	testImplementation(testFixtures(projects.junitJupiterApi))

	osgiVerification(projects.junitPlatformLauncher)
}
```

`libs` から始まるライブラリがVersion Catalogで定義されている依存関係です。

# まとめ

今回はJUnit 5で使用しているライブラリなどのバージョンの設定方法についてみていきました。
他のプロジェクトを見ていくことで、Gradleを使ってどのような記述が可能か、何ができるのか、どのように工夫するかがわかるので大変参考になります。