前回は、JVM component modelによるJavaプロジェクトのビルドの入門をおこなった。
これまでのjava
プラグインに比べると記述量が増えてしまうが、遜色ないビルドができることは示せたと思う。
複数ライブラリー(コンポーネント)
前回、簡単にするためにmain
というライブラリーだけに限ってプロジェクトのビルドを行いましたが、複数のライブラリー(コンポーネント)を指定することも可能でした。そこで、今回はまず複数のライブラリーを作成するプロジェクトを構築してみたいと思います。
前回用いたMain
クラスとRes
クラスをそれぞれ別のライブラリーに分けることにします。
クラス | ライブラリー |
---|---|
sample.Main |
main |
sample.Res |
res |
また、resources/app.properties
ファイルはmain
ライブラリーに含めるものとします。
まず、main
というライブラリーとres
というライブラリーの二つのコンポーネントを持ちますので、build.gradle
のDSLは次のように書くことができます。
build.gradle
plugins { id 'jvm-component' id 'java-lang' } model { components { main(JvmLibrarySpec) res(JvmLibrarySpec) } }
この状態で、components
タスクを実行すると、前回に実行した時のように二つのライブラリーを持つコンポーネントが準備されていることがわかります。
$ gradle components :components ------------------------------------------------------------ Root project ------------------------------------------------------------ JVM library 'main' ------------------ Source sets Java source 'main:java' srcDir: src/main/java JVM resources 'main:resources' srcDir: src/main/resources Binaries Jar 'mainJar' build using task: :mainJar targetPlatform: Java SE 8 tool chain: JDK 8 (1.8) Jar file: build/jars/mainJar/main.jar JVM library 'res' ----------------- Source sets Java source 'res:java' srcDir: src/res/java JVM resources 'res:resources' srcDir: src/res/resources Binaries Jar 'resJar' build using task: :resJar targetPlatform: Java SE 8 tool chain: JDK 8 (1.8) Jar file: build/jars/resJar/res.jar Note: currently not all plugins register their components, so some components may not be visible here. BUILD SUCCESSFUL Total time: 0.756 secs $
また、このコンポーネント構成を満たすためのディレクトリーは次のようになります。
$ tree -d src src ├── main │ ├── java │ └── resources └── res ├── java └── resources 6 directories $
Javaファイルの作成
前回作成したファイルをそれぞれ次の位置に配置し直します。
元のファイル | 新しいファイル |
---|---|
src/main/java/sample/Main.java |
変更なし |
src/main/java/sample/Res.java |
src/res/java/sample/Res.java |
src/main/resources/app.properties |
変更なし |
この時のディレクトリー構成は次のようになります。
$ tree src ├── main │ ├── java │ │ └── sample │ │ └── Main.java │ └── resources │ └── app.properties └── res ├── java │ └── sample │ └── Res.java └── resources 8 directories, 3 files $
ビルドの実行
ではビルドを実行します。
$ gradle build :compileMainJarMainJava /Users/mike/IdeaProjects/gradle-projects/gradle-self-training/g3-java-intro/src/main/java/sample/Main.java:23: エラー: シンボルを見つけられません Res res = new Res(); ^ シンボル: クラス Res 場所: クラス Main /Users/mike/IdeaProjects/gradle-projects/gradle-self-training/g3-java-intro/src/main/java/sample/Main.java:23: エラー: シンボルを見つけられません Res res = new Res(); ^ シンボル: クラス Res 場所: クラス Main エラー2個 :compileMainJarMainJava FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':compileMainJarMainJava'. > Compilation failed; see the compiler error output for details. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. BUILD FAILED Total time: 0.848 secs $
ライブラリーを分けた瞬間にMain
クラスが参照することのできた、Res
クラスを参照することができなくなり、コンパイルエラーとなってしまいました。
コンポーネント間の依存性
これは、JVM component modelにおける、コンポーネント間のAPI依存性です。例えば、ライブラリーfooがライブラリーbarに依存する場合、fooをコンパイルするためにはbarをコンパイルする必要があるというものです。
Gradle2.8現在、コンポーネント依存性解決については以下の二つのタイプをサポートしています。
- 同一プロジェクトのライブラリー間の依存性解決
- 同一プロジェクトを構成するサブプロジェクトのライブラリー間の依存性解決
現在のところ、外部ライブラリー(例えばslf4jとかtwitter4jのようなライブラリー)の参照はサポートされていないようです。
外部ライブラリー依存解決についてGradle Forumに質問してみたところ、現在のコードベース(リリースされていない)においては、実装および簡単なテストはあるようですが、リリースについてはGradle2.10頃になるのではないかとのことです。もし、外部ライブラリーを利用したい方はナイトリービルドを用いるなどするとよいでしょう。
依存性の解決方法
依存性解決のDSLについては、SourceSets
DSLのように記述をおこないます。
今のこのサンプルにおいては、main
ライブラリーがres
ライブラリーに依存しているために、次のように記述します。
plugins { id 'jvm-component' id 'java-lang' } model { components { main(JvmLibrarySpec) { sources { java { dependencies { library 'res' } } } } res(JvmLibrarySpec) } }
では、このスクリプトの状態で、components
タスクを実行してみます。
$ gradle components :components ------------------------------------------------------------ Root project ------------------------------------------------------------ JVM library 'main' ------------------ Source sets Java source 'main:java' srcDir: src/main/java dependencies: library 'res' JVM resources 'main:resources' srcDir: src/main/resources Binaries Jar 'mainJar' build using task: :mainJar targetPlatform: Java SE 8 tool chain: JDK 8 (1.8) Jar file: build/jars/mainJar/main.jar JVM library 'res' ----------------- Source sets Java source 'res:java' srcDir: src/res/java JVM resources 'res:resources' srcDir: src/res/resources Binaries Jar 'resJar' build using task: :resJar targetPlatform: Java SE 8 tool chain: JDK 8 (1.8) Jar file: build/jars/resJar/res.jar Note: currently not all plugins register their components, so some components may not be visible here. BUILD SUCCESSFUL Total time: 0.759 secs $
main
ライブラリーのコンポーネント情報のところに、res
に依存するという情報が追加されました。
ちなみに、dependencies {}
ブロックのdelegateオブジェクトはDependencySpecContainer
であり、そこに指定できるのは単なるString
なので、次のような記述もできます。
main(JvmLibrarySpec) { sources { java { dependencies { library 'res' library 'foo' } } } }
まあ、できることはできますが、指定されたfoo
ライブラリーは存在しないので、コンパイル時にエラーになるだけです。評価フェーズにて特に検証を行っていないので、ライブラリー名のtypoには気をつけてください。
依存性解決プラスアルファ
サブプロジェクトへの依存性解決
例えばmain
ライブラリーが、サブプロジェクトsub
のライブラリーfoo
に依存するライブラリーの場合は次のように記述します(これは次々回に詳しくやりましょう)。
main(JvmLibrarySpec) { sources { java { dependencies { library 'res' project ':sub' library 'foo' } } } }
外部依存性の解決
先ほど、外部依存ライブラリーの解決はサポートされていないと書きましたが、教えてもらったテストケースを読むと次のように記述するようです。ただし、incubatingな機能なので、実際にリリースされる際には変更されている可能性があります。その点についてはご容赦ください。
main(JvmLibrarySpec) { sources { java { dependencies { library 'res' library 'org.slf4j:slf4j-api:1.7.13' } } } }
transitive dependencies(推移的依存性解決)
ライブラリー間の依存性解決については、transitive dependencies(推移的依存性解決)にも対応していません。したがって、次のような依存関係を持つライブラリーがあるとします。
foo
ライブラリーはbar
ライブラリーに依存しているbar
ライブラリーはbaz
ライブラリーに依存している
このような状況においては、foo
ライブラリーの依存性にbaz
ライブラリーを加えておく必要があります。
ビルド
さて、依存性の解決も行ったことですし、ビルドをしてみましょう。
$ gradle build :compileResJarResJava :processResJarResResources :createResJar :resJar :compileMainJarMainJava :processMainJarMainResources :createMainJar :mainJar :assemble :check UP-TO-DATE :build BUILD SUCCESSFUL Total time: 0.859 secs $
main
ライブラリーがres
ライブラリーに依存しているという情報を解析して、res
ライブラリー→main
ライブラリーの順番でビルドされていることがわかります。
では、実際にjarファイルが作成されていることもあわせて確認してみましょう。
$ tree build/jars/ build/jars/ ├── mainJar │ └── main.jar └── resJar └── res.jar 2 directories, 2 files $
実行
おそらくMANIFEST.MF
ファイルにMain-Class
属性はないものと思われますので、-cp
オプションでjarを読み込んで、mainクラスを自分で指定して、実行しましょう。また、まだ、incubatingな機能ですから、クラスのリンクを書き出す機能などないでしょうし、res
ライブラリーの方も、-cp
オプションに入れ忘れないように気をつけます。
$ java -cp build/jars/mainJar/main.jar:build/jars/resJar/res.jar sample.Main こんにちわ mikeさん 僕はGradle $
前回と同じ結果が出力されました。
とはいえ、毎回、こんな長いコマンドを書くのも面倒ですね。というわけで、カスタムタスクを作ってしまいましょう。
task runApp(type: JavaExec) { def jar = org.gradle.jvm.tasks.Jar tasks.withType(jar) { classpath += it.outputs.files } main = 'sample.Main' }
このタスクを実行すると次のようになります。
$ gradle runApp :compileResJarResJava UP-TO-DATE :processResJarResResources UP-TO-DATE :createResJar UP-TO-DATE :resJar UP-TO-DATE :compileMainJarMainJava UP-TO-DATE :processMainJarMainResources UP-TO-DATE :createMainJar UP-TO-DATE :runApp こんにちわ mikeさん 僕はGradle BUILD SUCCESSFUL Total time: 1.095 secs $
見事、アプリケーションを実行してくれました。
注意
スクリプト中でJar
の型をorg.gradle.jvm.tasks.Jar
と書きました。Gradle2.8ではJar
タイプのタスクの型はorg.gradle.api.tasks.bundling.Jar
ですが、ドキュメントのほうが若干先行していてorg.gradle.jvm.tasks.Jar
となっています(少し(5分くらい)ハマりました)。現在、JavaまわりのAPIが過渡期なので注意してください。
終わり
なんか、Gradle2.9rc-1のリリースノートを読み直したら、JavaOneに合わせてか、Jigsaw関連のexportモジュール関連のリリースノートが追加されていました。いや、もしかしたら、僕がJigsawわかってなかったから意識的に読まなかったのかもしれませんが…
ここまで、読んでこられて、かつ、Java9の事情にある程度詳しい皆様なら薄々感づいていらっしゃると思いますが、JVM component modelはGradleのJava9対応でもあるわけです。そのあたりのところを次回(あるのか????)やっていきたいとおもいます。