Gradle で jar ファイルを作成する際、特定のクラスの main 関数を実行させたいケースってありますよね。
しかし作成した jar ファイルを何気なく実行してみると以下のエラーが・・・。
マニフェスト属性がありません
通常、実行可能な jar ファイルを作成する場合は、マニフェストファイル(MANIFEST.MF)に実行クラスを定義して jar に含めるのが一般的です。
今回は Gradle で jar ファイルを作成する際に、どのように manifest を指定すればいいのか確認してみました。
jarのタスク定義
今回のケースでは、以下を jar ファイルに含めると想定します。
・main 関数を実行するクラス
・依存関係のライブラリ
必要最低限となりますが、これだけ揃っていれば「java -jar」コマンドで jar を実行することが可能となります。
java -jar hogefuga.jar
もちろん引数(args)の指定も可能です。
java -jar hogefuga.jar [引数1] [引数2]
main関数を実行するクラス
まずは main 関数の実行対象ですね。
Kotlin DSL の場合は build.gradle.kts に以下を定義します。
1 2 3 4 5 | val jar by tasks.getting(Jar::class) { manifest { attributes["Main-Class"] = "com.example.Sample" } } |
manifest のメソッドが用意されているので、Main-Class 属性にパッケージからのクラス名を指定するだけです。
1 2 3 4 5 6 | package com.example; class Sample { public static void main(String args[]) { } } |
依存関係のライブラリ
次に、別のモジュールや外部のライブラリなど、プログラムの実行に必要なファイルも一緒に jar に入れます。
せっかく jar が実行できても、参照先のモジュールがなければ以下のエラーが発生することになりますからね。
・NoClassDefFoundError
・ClassNotFoundException
Gradle のバージョンによって定義が異なりますが、Gradle7.1 では以下の指定で問題ありません。
1 2 3 4 5 | val jar by tasks.getting(Jar::class) { configurations["runtimeClasspath"].forEach { file: File -> from(zipTree(file.absoluteFile)) } } |
ただし、Gradle のバージョンによっては下記のエラーが発生することが。
(バージョン 7.x 系がダメなのかな)
1 2 3 4 5 6 7 8 9 10 11 12 | FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':hogefuga:jar'. > Entry META-INF/versions/11/module-info.class is a duplicate but no duplicate handling strategy has been set. Please refer to https://docs.gradle.org/7.1/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:duplicatesStrategy for details. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 14s |
調べてみると、ファイル重複のエラーは以下の設定で回避できるとのこと。
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
これを追記しておきましょう。
完成形の jar タスクの中身は以下のようなイメージとなります。
1 2 3 4 5 6 7 8 9 | val jar by tasks.getting(Jar::class) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes["Main-Class"] = "com.example.Sample" } configurations["runtimeClasspath"].forEach { file: File -> from(zipTree(file.absoluteFile)) } } |
jarに含めたくないファイルの除外
jar ファイルに不要なファイルを含めたくない。
exclude メソッドを使えばこれも実現できちゃいます。
以下はテキストファイルと、META-INF 配下の *.MF ファイルを除外する例です。
exclude(*.txt”, “META-INF/*.MF”)
これで jar のタスクも最低限は完成ですね。
すべてまとめるとこんな感じ。
1 2 3 4 5 6 7 8 9 10 | val jar by tasks.getting(Jar::class) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes["Main-Class"] = "com.example.Sample" } configurations["runtimeClasspath"].forEach { file: File -> from(zipTree(file.absoluteFile)) } exclude(*.txt", "META-INF/*.MF") } |
まとめ
Gradle(Kotlin DSL)による実行可能な jar ファイルの作成について紹介してきました。
やってることは単純なのですが、いざ定義しようと思うとドキュメントと格闘です・・・。
groovy 版や、古い Gradle バージョンのサンプルは多く見つかるのですけどね。
jar を直接実行する機会は滅多にないと思いますが、覚えておいて損はないでしょう。