はじめに
jlinkを使うとアプリ専用のJREができて、配布に便利。
というわけでgradleで使う方法を調べてみた。
プロジェクトの準備
とりあえず普通に
gradle init --type=java-application
で作る。
build.gradleの書き換え
最低限必要なのは、pluginsの項目に以下の行を追加すること。
id 'org.beryx.jlink' version '2.25.0'
これで、gradle jlink が使えるようになる。
また、デフォルトで入っている dependencies は消しておくと警告が消える。(数字で終わるモジュール名がいけないらしい)
その他、JDKのバージョンによっては、
The module name specified in 'application.mainModule' (null) has not the expected value
という警告が出る。これは、applicationの項目に
mainModule = 'module名'
を書いておくと消える。(module名は module-info.java で指定したもの)
jlink の項目を作って、optionを設定することも可能。例えば以下のように書ける。
ちなみにMicrosoft buildのOpenJDK 17.0.5 では --strip-debug がエラーになった。とりあえず消せばビルドできる。
jlink {
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}
targetPlatformで複数のプラットフォーム用アプリを作ることもできる。この場合、各プラットフォーム用のJDKを用意しておく。
jlink {
targetPlatform("mac") {
jdkHome = "/usr/java/jdk-mac/Contents/Home"
}
targetPlatform("linux-x64") {
jdkHome = "/usr/java/jdk"
}
targetPlatform("windows-x64") {
jdkHome = "/usr/java/jdk-win"
}
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}
ビルド
gradle build
では、jlinkを使わずにjarができる。jlinkする場合は、以下のようにする。
gradle jlink
これで build ディレクトリの下に image ができ、その下のディレクトリがあれば実行できるようになっている。
targetPlatformを指定している場合は、app-<ターゲットプラットフォーム名> のディレクトリができ、その下に置かれる。
アプリ実行のためには、bin ディレクトリに実行スクリプト(app)があるので、それを実行すれば良い。(Windows用の場合はapp.bat)
アプリの配布は bin, conf, legal, libの各ディレクトリ(要はimageディレクトリ)をまとめて配布すればOK。
そのままコピーして、appを実行すれば起動できる。
リソース
gradle init で準備したプロジェクトには app/source/main/resources ディレクトリがあるはず。ここにファイルを置いておくと、jlink で出来上がる modules の中にresourcesディレクトリ内のファイルが取り込まれる。
modulesにちゃんと入っているかは、jimage で確認可能。
jimage list app/build/image/lib/modules
とすると入っているファイル一覧が表示される。
このファイルは普通にgetResourceできる。
例えばresourcesにtest.pngを置いて、以下のようにすれば読み込める。
URL imgurl = App.class.getClassLoader().getResource("test.png");
Image img = Toolkit.getDefaultToolkit().getImage(imgurl);
ライブラリの利用
外部のライブラリも使用できる。例として Apache Derby を使ってみる。(module化されていて楽だったので)
まずは build.gradle の dependencies に指定する。これは jlink 関係ない部分。
dependencies {
implementation group: 'org.apache.derby', name: 'derby', version: '10.16.1.1'
}
次に module-info.java に指定する。
module名が org.apache.derby ではないので注意。
module derbyjlink
{
exports derbyjlink;
requires java.base;
requires java.sql;
requires transitive org.apache.derby.engine;
}
この org.apache.derby.engine は jdeps を使って調べた。
module-info.java に derby の行を書かずに jlink build すると、app/build/distributions に配布ファイルができる。
その中に derby-10.16.1.1.jar が含まれるので、これを jdeps で調べる。
jdeps derby-10.16.1.1.jar
Exception in thread "main" java.lang.module.FindException: Module org.apache.derby.commons not found, required by org.apache.derby.engine
at java.base/java.lang.module.Resolver.findFail(Resolver.java:893)
at java.base/java.lang.module.Resolver.resolve(Resolver.java:192)
at java.base/java.lang.module.Resolver.resolve(Resolver.java:141)
at java.base/java.lang.module.Configuration.resolve(Configuration.java:421)
at java.base/java.lang.module.Configuration.resolve(Configuration.java:255)
at jdk.jdeps/com.sun.tools.jdeps.JdepsConfiguration$Builder.build(JdepsConfiguration.java:564)
at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.buildConfig(JdepsTask.java:604)
at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:558)
at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:534)
at jdk.jdeps/com.sun.tools.jdeps.Main.main(Main.java:49)
このようにエラーになるが、org.apache.derby.engine の module が org.apache.derby.commons を要求していることがわかる。
というわけで、requires transitive で org.apache.derby.engine を指定すると org.apache.derby.commons もリンクしてくれる。
module化されていないライブラリも同じ方法で行けるらしいが未確認。module-info.javaにライブラリのパッケージ名を書けば良いらしい。