こんにちわ。サーバーで Java 書いている持田です。
今回はバージョン 5.2 以降の Gradle で BOM(Bill Of Materials の略。詳しくはリンク先の Maven のドキュメントの Dependency Management を参照) を扱う方法について紹介します。
時間がない人のための3行要旨
- BOM を読み込む場合は、依存ライブラリー指定の際に BOM の Dependency Notation(
org.springframework.boot:spring-boot-dependencies:2.1.4.RELEASE
というやつ) をplatform()
メソッドで囲って指定する - BOM を生成したい場合は
java-platform
とmaven-publish
プラグインを使う - 詳しくは Java Platform プラグインの公式ドキュメント と Maven-Publish プラグインの公式ドキュメント を読めばわかる
BOM とは???
Bill Of Materials(部品表) の略で、複数のプロジェクトからなる大きなプロジェクトにおいて利用する依存ライブラリーのバージョンを 統一的に指定できるようにする Maven の仕組みです。
エコシステムの発達した Java において、高品質なプログラムを速く開発するためにはライブラリーの利用は欠かせません。 また、ライブラリーの開発においても他のライブラリーに依存するケースなどがあります。 そのためソフトウェア開発には複雑なライブラリーの依存関係を抱えることになります。
複雑な依存関係を抱えたソフトウェアでは、 特定のライブラリーの異なるバージョンが複数の異なるライブラリー経由で参照している状況なども生まれ混乱が生じます。 例えば、次のような依存関係をもったプロジェクトでは、 guava のバージョンが何になるのか定められなくなります。 このようなプロジェクトでは、ライブラリーが想定外の動作をもたらすようなことも有ります。
project +--- foo-lib | \--- guava:27.0.1-jre \--- bar-lib \--- baz-lib \--- guava:26.0-android
これを解決する Maven の仕組みとして BOM があります。 parent
で指定した BOM pom あるいは dependencyManagement
で指定した BOM に記載されたライブラリーのバージョンが依存ライブラリーおよび推移的依存ライブラリー (依存ライブラリーが依存しているライブラリー)のバージョンを規定します。
例えば、先程のプロジェクトで次のような BOM pom を取り込むと、 foo-lib
および baz-lib
が依存している guava のバージョンが 28.0-jre
になります。
<!-- pom の一部分 --> <dependencyManagement> <dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.0-jre</version> </dependency> </dependencies> </dependencyManagement>
Spring Boot を Maven でビルドしている方にはおなじみの仕組みだと思います。
BOM と Gradle(バージョン 5.1.1 まで)
BOM を取り込む機能について Gradle のバージョン 5.1.1 まではプレビュー的な位置づけでした。 このプレビュー機能を使うためには settings.gradle
に対してプレビューを ON にする記述(enableFeaturePreview('IMPROVED_POM_SUPPORT')
)が必要でした。 settings.gradle
でプレビュー機能を ON にできない条件下で、 Gradle で BOM を使って複数のプロジェクトで統一されたライブラリーを利用するようなことをやりたい場合、 特殊なプラグインを作るしか方法がありませんでした。
例えば、 Spring Boot では、 io.spring.dependency-management
プラグインがその特殊なプラグインに該当します。
plugins { id 'org.springframework.boot' version '2.1.5.RELEASE' id 'java' } // このプラグインによってバージョンが解決される apply plugin: 'io.spring.dependency-management' // 一部省略 dependencies { // バージョンは記載されていないが、プラグインによってバージョンが解決される implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
Gradle(5.2 〜) の Java Platform プラグイン
Gradle のバージョン 5.2 にて導入された Java Platform プラグインとその周辺の開発によって、 BOM が正式に扱えるようになりました。 なお、 settings.gradle
に記述する必要のあったプレビュー機能を ON にする記述は非推奨になっています。
BOM を使って依存ライブラリーを解決する
先程の Spring Boot の build.gradle
を少し修正して BOM によるバージョン解決を試してみます。
plugins { id 'org.springframework.boot' version '2.1.5.RELEASE' id 'java' } // dependency-management プラグインは使わないので、コメントアウト // apply plugin: 'io.spring.dependency-management' // 一部省略 dependencies { // platform メソッドで BOM(spring-boot-starter-parent) を指定する implementation platform('org.springframework.boot:spring-boot-starter-parent:2.1.5.RELEASE') implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
これで BOM が適用されます。 では試してみましょう。
$ ./gradlew dependencies --configuration testRuntimeClasspath > Task :dependencies ------------------------------------------------------------ Project :spring-app ------------------------------------------------------------ testRuntimeClasspath - Runtime classpath of source set 'test'. +--- org.springframework.boot:spring-boot-starter-parent:2.1.5.RELEASE | +--- org.springframework.boot:spring-boot-starter-data-jpa:2.1.5.RELEASE (c) | +--- org.springframework.boot:spring-boot-starter-test:2.1.5.RELEASE (c) | +--- org.springframework.boot:spring-boot-starter-thymeleaf:2.1.5.RELEASE (c) | +--- com.h2database:h2:1.4.199 (c) 途中省略 +--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.1.5.RELEASE | +--- org.springframework.boot:spring-boot-starter-aop:2.1.5.RELEASE | | +--- org.springframework.boot:spring-boot-starter:2.1.5.RELEASE | | | +--- org.springframework.boot:spring-boot:2.1.5.RELEASE 途中省略 +--- org.springframework.boot:spring-boot-starter-thymeleaf -> 2.1.5.RELEASE | +--- org.springframework.boot:spring-boot-starter:2.1.5.RELEASE (*) | +--- org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE | | +--- org.thymeleaf:thymeleaf:3.0.11.RELEASE | | | +--- ognl:ognl:3.1.12 途中省略 +--- com.h2database:h2 -> 1.4.199 \--- org.springframework.boot:spring-boot-starter-test -> 2.1.5.RELEASE +--- org.springframework.boot:spring-boot-starter:2.1.5.RELEASE (*) +--- org.springframework.boot:spring-boot-test:2.1.5.RELEASE 以下省略
spring-boot-starter-data-jpa
/spring-boot-starter-thymeleaf
/spring-boot-starter-test
/h2
のバージョンが解決されているのがわかります。
BOM を出力する
BOM を利用する方法は 4.6 の頃からプレビュー機能として存在していたわけですが、 BOM を出力する方法はありませんでした (ただし、今回紹介する方法の API は 4.5 の頃から存在しているようです)。
BOM を出力するためのビルドスクリプトは以下のように作ります。
java-platform
プラグインを適用するmaven-publish
プラグインを適用して、 BOM を出力できるようにするdependencies {}
ブロックの中にconstraints{}
ブロックを設けて、中に BOM に出力するライブラリーを記述するconstraints{}
内で使えるconfiguration
はapi
とruntime
のみMavenPublication
ブロック内でfrom
にJavaPlatform
コンポーネント(components['javaPlatform']
) を指定するpublishing.repositories {}
ブロックで出力先の Maven レポジトリーを指定する
plugins { // 1. java-platform プラグインを適用する id 'java-platform' // 2. maven-publish プラグインを適用する id 'maven-publish' } group = 'com.example.bom' version = '1.0' repositories { mavenCentral() } dependencies { // 3. constraints ブロックの中に BOM に書き出す constraints { // 4. api または runtime のみ指定できる api 'org.slf4j:slf4j-api:1.7.12' runtime 'com.h2database:h2:1.4.199' api 'org.junit.jupiter:junit-jupiter:5.4.2' } } publishing { publications { maven(MavenPublication) { // 5. JavaPlatform コンポーネントを pom.xml に出力する(constraints ブロックで指定された依存ライブラリーが出力される) from components['javaPlatform'] } } repositories { // 6. 出力先のレポジトリーを指定する maven { name = 'myRepo' url = "$buildDir/repo" } } }
これで、 以下のタスクを実行すると、 build/repo/com/example/bom-example/1.0/bom-example-1.0.pom
という BOM pom ファイルが出力されます。 (プロジェクトの名前は bom-example
とする)
./gradlew publishMavenPublicationToMyRepoRepository
出力された pom ファイルの内容
<?xml version="1.0" encoding="UTF-8"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.bom</groupId> <artifactId>generator</artifactId> <version>1.0</version> <packaging>pom</packaging> <name>example bill of material</name> <dependencyManagement> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.4.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.199</version> <scope>runtime</scope> </dependency> </dependencies> </dependencyManagement> </project>
この例では、プロジェクトディレクトリーに出力しましたが、 インハウスの Maven レポジトリーや S3 なども利用できます。 詳細 はこちらを御覧ください。 また、 publishToMavenLocal
タスクも自動で生成され、ローカルレポジトリーにも出力可能です。
以上、 Gradle 5.2 以降での BOM ファイルの扱いについて紹介しました。
- BOM を利用する場合、
platform(BOM の group/artifact/version)
を configuration に追加する - BOM を出力する場合
java-platform
プラグインを使って、constraints{}
ブロックにライブラリーのバージョン等を指定する - BOM を publish する場合は
maven-publish
プラグインのMavenPublication#from
にcomponents['javaPlatform']
を指定する
記事とは関係ありませんが、 L is B では現在サービスを安定的に運用・改善するのが好きで好きで夜も眠れない目に隈ができているエンジニアを募集しています(隈はなくてもいいし、しっかり寝てていい、いやむしろ睡眠は大事だ)。 われこそはという方はこちらから応募してもらえるとありがたいです!