はじめに
Java 9 で導入されたモジュールシステムの Gradle における扱い方について見ていきます。
Java Platform Module System については以下を参照してください。
Gradle でのモジュールシステムの運用
Gradle 6.4 よりモジュールのビルドが直接サポートされています。
以下の3つの条件を満たす場合に、classpath ではなくmodule-path に依存 Jar を配置します。
modularity.inferModulePath
が有効になっているmodule-info.java
によりモジュールが定義されている module-path に配置- または MANIFEST.MF の
Automatic-Module-Name
属性が定義されている
- または MANIFEST.MF の
- モジュールが依存している Jar がモジュールである場合に依存 Jar を module-path に配置
modularity.inferModulePath
は以下のように有効化できます。
java {
modularity.inferModulePath = true
}
Gradle 8 からはmodularity.inferModulePath
がデフォルトで有効になっているので、
Java モジュールのサポートを無効にする場合に以下のように設定します(Kotlin DSL の例)。
java { modularity.inferModulePath.set(false) } tasks.compileJava { modularity.inferModulePath.set(false) }
モジュールの依存関係の定義
build.gradle で宣言した依存関係と、module-info.java で宣言したモジュールの依存関係は直接関係があります。以下の表のように宣言が同期していることが望ましい形となります。
Gradle Configuration | Java Module 定義 |
---|---|
implementation | requires |
api | requires transitive |
compileOnly | requires static |
compileOnlyApi | requires static transitive |
現在の Gradle は、上記の依存関係の宣言が同期しているかどうかを自動的にチェックしていません。
非モジュール・ライブラリの利用
各種依存ライブラリのモジュール化対応はまちまちです。
com.google.code.gson:gson:2.8.6 は既にモジュール記述子を持つ完全なモジュールとして提供されています。
org.apache.commons:commons-lang3:3.10 などは MANIFEST.MF の Automatic-Module-Name 属性が定義されており、自動モジュールとして利用できます。
commons-cli:commons-cli:1.4 などはモジュール情報を全く提供しない伝統的なライブラリです。
これらは build.gradle では以下のように定義します。
dependencies { implementation 'com.google.code.gson:gson:2.8.6' // real module implementation 'org.apache.commons:commons-lang3:3.10' // automatic module implementation 'commons-cli:commons-cli:1.4' // plain library }
モジュール宣言 module-info.java では以下のような定義にします。
module org.gradle.sample.app { requires com.google.gson; // real module requires org.apache.commons.lang3; // automatic module // commons-cli-1.4.jar はモジュールではないため定義できない }
上記依存があるプロジェクトを以下のような build.gradle
とした場合、モジュール化された(named module)アプリケーションから 'commons-cli:commons-cli:1.4' を参照することができません。
plugins { id 'java' id 'application' } repositories { jcenter() } dependencies { implementation 'com.google.code.gson:gson:2.8.6' implementation 'org.apache.commons:commons-lang3:3.10' implementation 'commons-cli:commons-cli:1.4' } application { mainClassName = 'org.gradle.sample.app.App' } java { modularity.inferModulePath = true }
plain library であるライブラリは classpath に配置され module-path に配備されません。ライブラリは無名モジュールとして扱われるため、自動モジュールからは参照できても、モジュール(named module)からは参照できないためです。
このような場合は、build.gradle
に以下を追加することで対応できます。
compileJava {
doFirst {
options.compilerArgs = [
"--module-path", classpath.asPath
]
classpath = files()
}
}
classpath のライブラリをモジュールをmodule-path に Automatic Module として扱うことができます(IDE 側の設定は別途必要となります)。
その他の対応方法としては、プロジェクトの一部として対象のライブラリを自動モジュールでラップしたり、アーティファクト変換を使って既存ライブラリにモジュール記述子を追加してモジュール化するなどがあります。サンプルとして artifact transformsが参考になります。
具体的には以下を参照してください。
しかし、プラグインにて対応するのが現実的でしょう。
gradle-modules-plugin を使えば以下のようにシンプルに対応が可能です。
plugins { id 'java' id 'application' id 'org.javamodularity.moduleplugin' version '1.7.0' } repositories { jcenter() } dependencies { implementation 'com.google.code.gson:gson:2.8.6' // real module implementation 'org.apache.commons:commons-lang3:3.10' // automatic module implementation 'commons-cli:commons-cli:1.4' // plain library } application { mainClassName = 'org.gradle.sample.app.App' }
複数モジュールから構成されるアプリケーションの例
application
モジュールと utilities
モジュールと list
モジュール の3つのモジュールがあり、application
-> utilities
-> list
のような依存関係であったとします。
これらを Gradle のマルチモジュールプロジェクトとして定義します。
settings.gradle
は通常のマルチモジュールプロジェクトと同様に以下のように定義します。
rootProject.name = 'modules-multi-project' include 'application', 'utilities', 'list'
build.gradle
では以下のように modularity.inferModulePath
を有効にします。
subprojects { version = '1.0.0' group = 'org.gradle.sample' repositories { jcenter() } plugins.withType(JavaPlugin).configureEach { java { modularity.inferModulePath = true } } tasks.withType(Test).configureEach { useJUnitPlatform() } }
プロジェクト構成は以下のようなレイアウトになります。
list モジュール
list モジュールの build.gradle は以下のようになります。
plugins { id 'java-library' } dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' }
list モジュールの module-info.java は以下のようになります。
module org.gradle.sample.list { exports org.gradle.sample.list; }
外部から利用するパッケージを公開します。
utilities モジュール
utilities モジュールの build.gradle は以下のようになります。
plugins { id 'java-library' } dependencies { api project(':list') }
utilities モジュールの module-info.java は以下のようになります。
module org.gradle.sample.utilities { requires transitive org.gradle.sample.list; exports org.gradle.sample.utilities; }
list
モジュールへの依存を定義するとともに、外部から利用するパッケージを公開します。
list
モジュールを requires transitive としているため、list
モジュールの依存先も推移的に utilities
モジュールで利用可能となります。
application モジュール
application モジュールの build.gradle は以下のようになります。
plugins { id 'application' } dependencies { implementation project(':utilities') testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' } application { mainModule = 'org.gradle.sample.app' mainClass = 'org.gradle.sample.app.Main' }
application モジュールの module-info.java は以下のようになります。
module org.gradle.sample.app { exports org.gradle.sample.app; requires org.gradle.sample.utilities; }
utilities
モジュールへの依存を定義しています。
まとめ
Gradle における JPMS の扱い方について説明しました。
まだまだモジュール化されたライブラリなどの整備などが追いついていない状況ではありますが、準備は整えておきたいものです。