前回のエントリーで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
と言える。