gradleでjlinkを使う

# はじめに
jlinkを使うとアプリ専用のJREができて、配布に便利。
というわけでgradleで使う方法を調べてみた。

# プロジェクトの準備
とりあえず普通に
> gradle init --type=java-application

で作る。

# build.gradleの書き換え
最低限必要なのは、pluginsの項目に以下の行を追加すること。

```groovy
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 がエラーになった。とりあえず消せばビルドできる。

```groovy
jlink {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}
```

targetPlatformで複数のプラットフォーム用アプリを作ることもできる。この場合、各プラットフォーム用のJDKを用意しておく。

```groovy
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を置いて、以下のようにすれば読み込める。

```java
URL imgurl = App.class.getClassLoader().getResource("test.png");
Image img = Toolkit.getDefaultToolkit().getImage(imgurl);
```

# ライブラリの利用
外部のライブラリも使用できる。例として Apache Derby を使ってみる。（module化されていて楽だったので）
まずは build.gradle の dependencies に指定する。これは jlink 関係ない部分。

```groovy
dependencies {
        implementation group: 'org.apache.derby', name: 'derby', version: '10.16.1.1'
}
```

次に module-info.java に指定する。
module名が org.apache.derby ではないので注意。

```groovy
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にライブラリのパッケージ名を書けば良いらしい。