GradleのJVM component modelスタイルのプロジェクトをJigsawでビルドする #gradle #java #jigsaw

前回のエントリーでGradleにおけるJVM component modelの実践的な内容を紹介した。
ここで紹介したプロジェクトのビルド成果物はこちらのエントリーでも紹介した、Javaモジュールシステム的な考えが採用された成果物である。つまり、強いカプセル化によって、外部APIと内部用のクラスが厳格に切り分けられた成果物である。
さて、今回のエントリーでは前回のエントリーで使用したプロジェクトをJigsawでビルドする試みである。
まず、前回のおさらい。なお、括弧内の表記はGradleでの用語である。
プロジェクト(コンポーネント)の構成
プロジェクト(コンポーネント)は次のモジュール(ライブラリー)によって構成されている。
exceptionsモジュール(ライブラリー)functionsモジュール(ライブラリー)dataモジュール(ライブラリー)test.frameworkモジュール(ライブラリー)data.testモジュール(ライブラリー)
これらのモジュールは次のような依存関係にある。

module-info.javaの記述
依存関係を考慮して次のように記述している。
exceptionsモジュール
module exceptions {
exports com.sample;
}
functionsモジュール
module functions {
requires public exceptions;
exports com.sample.func.api;
}
dataモジュール
module data {
exports com.sample.data.api;
requires public functions;
}
test.frameworkモジュール
module test.framework {
exports test;
}
data.testモジュール
module data.test {
requires data;
requires test.framework;
}
コンパイル
コンパイラーはJDK9 Early Access with Project Jigsawのページからダウンロードする。
ダウンロードしたものを解凍する(Mac OS X版の場合)と、jdk1.9.0というディレクトリーが作成され、その中のContents/Home/binの中にjavacやらjarやらビルドに必要なものが含まれている。
今回はGradleをJava9で動かすことが目的ではないので、これらをPATHには通さずに、コマンドラインから叩いて行く予定だったが、一つだけ致命的な問題があり、Gradleから叩いていくことにした。
致命的な問題というのは、次のようなものである。
Maven/Gradleスタイルのディレクトリー構成(Javaのソースコードの位置がsrc/main/javaにあるタイプ)、もしくはGradleのJVM component modelスタイルのディレクトリー構成(Javaのソースコードがsrc/lib.name/javaにある)を取る場合に、モジュール名を解決できないためにコンパイルに失敗する。
もちろん、build.gradleを書き直すことで、Jigsawが求めるディレクトリー構成にすることもできなくはない。
model {
components {
data(JvmLibrarySpec) {
def libName = delegate.name
targetPlatform 'java8'
api {
exports 'com.sample.data.api'
dependencies {
library 'functions'
}
}
sources {
java {
source {
srcDir "src/${libName}"
}
}
}
}
}
}
モジュール(ライブラリー)が一つだけなら、まだなんとかなるかもしれないが、プロジェクト(コンポーネント)には5つのモジュール(ライブラリー)が含まれている。これをすべてに適用するととたんにメンテナンスができないビルドスクリプトが出来上がってしまうことは想像に難くない。
そういうわけで、(1)Gradleで一度ソースコードをJigsawスタイルのディレクトリー構成にコピーするタスクを実行してから、(2)javacを叩くタスクを起動することにした。
(1)と(2)のタスクの定義は次のとおり
ext {
jigsawBuild = 'Jigsaw Build'
jigsaw = 'jigsaw'
copySrcTask = 'copyAsJigsawStructure'
compileJigsaw = 'compileJigsaw'
jdkBin = "${projectDir}/jdk1.9.0.jdk/Contents/Home/bin"
}
model {
components {
// library の定義
}
tasks {
// ソースのコピー
$.components.each {lib ->
create("${lib.name}Copy", Copy) {
into "${buildDir}/${jigsaw}/src/${lib.name}"
lib.sources.java.source.srcDirs.each {
from fileTree(it)
}
}
}
// ソースコピーの同期
create(copySrcTask) {
group = jigsawBuild
$.components.each {lib ->
dependsOn "${lib.name}Copy"
}
}
// コンパイル
create(compileJigsaw, Exec) {
// コンパイルしたソースの保存先
def destDir = file("${buildDir}/${jigsaw}/tmp")
// ソースコピー完了後にコンパイル実行
dependsOn copySrcTask
group = jigsawBuild
// 出力先の設定
outputs.dir destDir
// コマンド実行ディレクトリー
workingDir "${buildDir}/${jigsaw}"
// javacコマンドの指定
def javac = "${jdkBin}/javac"
// 実行コマンドの指定
def commands = [javac,'-d', 'tmp', '-modulesourcepath', 'src']
commands += $.components.collect {lib ->
lib.sources.java.source.srcDirs.collect {
project.fileTree(it).files
}
}.flatten().collect {
it.absolutePath.replace("${projectDir}/", '').replace('/java/', '/')
}
commandLine commands
// コンパイル結果ディレクトリーを事前に作成しておく
doFirst {
if (!destDir.exists()) {
destDir.mkdirs()
}
}
}
}
なお、Jigsawによるコンパイルのやり方はこちらのドキュメントを参考にしました。
これらのタスクは
- 各ライブラリーのソースコードを一度
build/jigsaw/src/にモジュールごとにコピーする - コピーしたソースコードをモジュールを認識させつつ、コンパイルして
build/jigsaw/tmpに保存する
というものです。
上記スクリプトの中で、$というオブジェクトが登場していますが、これはmodel{}ブロック内でのみ利用できるmodelオブジェクトです。既に値が確定した後のオブジェクトを利用することができます。この機能はGradle2.9より登場しています。
コンパイルの実行
上記のタスクにより、Jigsawでのクラスファイル生成を行うことができます。
$ gradle compileJigsaw
:data.testCopy
:dataCopy
:exceptionsCopy
:functionsCopy
:test.frameworkCopy
:copyAsJigsawStructure
:compileJigsaw
src/data.test/com/sample/Main.java:16: 警告: package exists in another module: exceptions
package com.sample;
^
src/exceptions/com/sample/EvaluationException.java:16: 警告: package exists in another module: data.test
package com.sample;
^
src/exceptions/com/sample/ExecutionException.java:16: 警告: package exists in another module: data.test
package com.sample;
^
警告3個
BUILD SUCCESSFUL
Total time: 3.505 secs
$ tree build/jigsaw/
build/jigsaw/
├── src
│ ├── data
│ │ ├── com
│ │ │ └── sample
│ │ │ └── data
│ │ │ ├── api
│ │ │ │ └── Maybe.java
│ │ │ └── internal
│ │ │ ├── MaybeBase.java
│ │ │ ├── Nothing.java
│ │ │ └── Some.java
│ │ └── module-info.java
│ ├── data.test
│ │ ├── com
│ │ │ └── sample
│ │ │ ├── Main.java
│ │ │ └── tests
│ │ │ ├── NothingTest.java
│ │ │ └── SomeTest.java
│ │ └── module-info.java
│ ├── exceptions
│ │ ├── com
│ │ │ └── sample
│ │ │ ├── EvaluationException.java
│ │ │ └── ExecutionException.java
│ │ └── module-info.java
│ ├── functions
│ │ ├── com
│ │ │ └── sample
│ │ │ └── func
│ │ │ ├── api
│ │ │ │ ├── Condition.java
│ │ │ │ ├── ExCondition.java
│ │ │ │ ├── ExFunction.java
│ │ │ │ ├── ExOperation.java
│ │ │ │ ├── Function.java
│ │ │ │ ├── Operation.java
│ │ │ │ ├── Synthesis.java
│ │ │ │ └── Verifications.java
│ │ │ └── internal
│ │ │ └── Operation.java
│ │ └── module-info.java
│ └── test.framework
│ ├── module-info.java
│ └── test
│ ├── Execute.java
│ ├── Test.java
│ ├── TestSuite.java
│ ├── exception
│ │ ├── Difference.java
│ │ ├── TestExecutionException.java
│ │ └── TestFailureException.java
│ ├── exec
│ │ ├── ExecutionManager.java
│ │ ├── Sorter.java
│ │ ├── TestCases.java
│ │ ├── TestExecutor.java
│ │ ├── TestResults.java
│ │ └── print
│ │ ├── ColorPrinter.java
│ │ └── ResultPrinter.java
│ └── result
│ ├── Accident.java
│ ├── Failure.java
│ ├── Panic.java
│ ├── Result.java
│ ├── Statistics.java
│ └── Success.java
└── tmp
├── data
│ ├── com
│ │ └── sample
│ │ └── data
│ │ ├── api
│ │ │ └── Maybe.class
│ │ └── internal
│ │ ├── MaybeBase.class
│ │ ├── Nothing.class
│ │ └── Some.class
│ └── module-info.class
├── data.test
│ ├── com
│ │ └── sample
│ │ ├── Main.class
│ │ └── tests
│ │ ├── NothingTest$1.class
│ │ ├── NothingTest$SomethingWrong.class
│ │ ├── NothingTest.class
│ │ └── SomeTest.class
│ └── module-info.class
├── exceptions
│ ├── com
│ │ └── sample
│ │ ├── EvaluationException.class
│ │ └── ExecutionException.class
│ └── module-info.class
├── functions
│ ├── com
│ │ └── sample
│ │ └── func
│ │ ├── api
│ │ │ ├── Condition.class
│ │ │ ├── ExCondition.class
│ │ │ ├── ExFunction.class
│ │ │ ├── ExOperation.class
│ │ │ ├── Function.class
│ │ │ ├── Operation.class
│ │ │ ├── Synthesis.class
│ │ │ └── Verifications.class
│ │ └── internal
│ │ └── Operation.class
│ └── module-info.class
└── test.framework
├── module-info.class
└── test
├── Execute.class
├── Test$1.class
├── Test$Actual.class
├── Test$Asserting.class
├── Test$ExSupplier.class
├── Test$Step.class
├── Test.class
├── TestSuite.class
├── exception
│ ├── Difference.class
│ ├── TestExecutionException.class
│ └── TestFailureException.class
├── exec
│ ├── ExecutionManager$1.class
│ ├── ExecutionManager.class
│ ├── Sorter.class
│ ├── TestCases$1.class
│ ├── TestCases$ResultCollector.class
│ ├── TestCases.class
│ ├── TestExecutor.class
│ ├── TestResults.class
│ └── print
│ ├── ColorPrinter.class
│ ├── ResultPrinter$AbstractPrinter.class
│ ├── ResultPrinter$FailType.class
│ ├── ResultPrinter$FailurePrinter.class
│ ├── ResultPrinter$Printer.class
│ ├── ResultPrinter$SuccessPrinter.class
│ └── ResultPrinter.class
└── result
├── Accident.class
├── Failure.class
├── Panic.class
├── Result.class
├── Statistics$1.class
├── Statistics$Builder.class
├── Statistics$StatisticsCollector.class
├── Statistics.class
└── Success.class
52 directories, 102 files
JDK9 ea b-86の問題
ここまできたらパッケージングなのだが、jdk 1.9.0-ea-jigsaw-nightly-h3660-20151022-b86という僕が使った(2015/11/25入手できた)バージョンのJigsaw入のJDK9ではjarコマンドが壊れていて、パッケージングができなかった。
パッケージング方法は先のドキュメントに記載されている。
$ cd build $ cd jigsaw $ ../../jdk1.9.0.jdk/Contents/Home/bin/jar --create --file=libs/exceptions-0.1.jar --module-version 0.1 -C tmp/exceptions/ jar: You must specify one of -ctxui options. Try `jar --help' for more information. $ ../../jdk1.9.0.jdk/Contents/Home/bin/jar -c --file=libs/exceptions-0.1.jar --module-version 0.1 -C tmp/exceptions/ jar: You must specify one of -ctxui options. Try `jar --help' for more information.
というわけで、現在(2015/11/25)の時点のJigsawではまだパッケージングができないようである。
したがって、唐突ではあるがこのエントリーもここで中断せざるを得ない。
結論
Javaモジュールシステムの完成度において、
jdkのナイトリービルドのjarコマンド壊れてたので、Gradleの勝ちということでよろしいか
— かつて持田真哉であった人 (@mike_neck) 2015, 11月 25
と言える。