L is B BLOG

株式会社L is Bの社員ブログです。会社の取り組みや技術ブログを発信しています。

Gradle で BOM を扱う(Gradle 5.2 以降)

こんにちわ。サーバーで Java 書いている持田です。

f:id:mike_neck:20190619163018p:plain

今回はバージョン 5.2 以降の Gradle で BOM(Bill Of Materials の略。詳しくはリンク先の Maven のドキュメントの Dependency Management を参照) を扱う方法について紹介します。

時間がない人のための3行要旨


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 を出力するためのビルドスクリプトは以下のように作ります。

  1. java-platform プラグインを適用する
  2. maven-publish プラグインを適用して、 BOM を出力できるようにする
  3. dependencies {} ブロックの中に constraints{} ブロックを設けて、中に BOM に出力するライブラリーを記述する
  4. constraints{} 内で使える configurationapiruntime のみ
  5. MavenPublication ブロック内で fromJavaPlatform コンポーネント(components['javaPlatform']) を指定する
  6. 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#fromcomponents['javaPlatform'] を指定する

記事とは関係ありませんが、 L is B では現在サービスを安定的に運用・改善するのが好きで好きで夜も眠れない目に隈ができているエンジニアを募集しています(隈はなくてもいいし、しっかり寝てていい、いやむしろ睡眠は大事だ)。 われこそはという方はこちらから応募してもらえるとありがたいです!