日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

Clojure でコマンドラインツール作成のお供に lein-jlink のススメ

前置き

英語的には Motivation に当たるのかな。

Clojure + GraalVM で Java ランタイム不要の CLI 作成可能な事は 以前の記事 でも紹介したが、その後作ったツールは全部 GraalVM でいいかと言うとそういう訳でもなく、 実際試してみると Unsupported Feature やらでコケる事が結構ある。

その場合別の配布の選択肢として Java ランタイム含めるケースがあるが、そういや 最近の Java って JRE バイナリ配布されてないよな?どうすんだっけとふと思い出したのが Java Platform Module System、略して JPMS、正式リリース前は Project Jigsaw と呼ばれてたやつ。

これによってできる事の一つに必要モジュールのみのJRE作成と言うものがあり、 Clojure でもその恩恵を受けられないかなと探してみたら lein-jlink と言う Leiningen の PlugIn を見つけた。

名前の通り jlink と言う 必要モジュールのみ入れたJRE作成 が可能な JDK ツールコマンドを leiningen から設定・使用可能にしたプラグインである。

さっとググった限り jlink 単体の日本語記事は割と出てきたが、Clojure と絡めたものは すぐには見つからなかったのでさほど大した事は書いてないが記事を書くことにした。

コマンドラインツールを GraalVM native-image で作れなかった場合にでもどうぞ。 さすがに native-image で作成された単体 exe にはシンプルさで負けるがそれでもさほど大きくないサイズで JRE を同梱できるのは良い。

前提

JDK 入れたフォルダの bin にPATH を通しててコマンドプロンプトjlink が使えたら ok。

jdeps で必要モジュールを調べる

まず、lein uberjarJRE 上で動作する実行可能 jar を作る。 次に、jdeps --list-deps xxx.jar で必要 Java モジュールをリストアップする。 自分が作ったとあるツールではこんな出力が出た。

>jdeps --list-deps test-tool-0.1.0-SNAPSHOT-standalone.jar
   java.base
   java.desktop
   java.management
   java.security.jgss
   java.sql
   java.xml.crypto
   jdk.javadoc
   jdk.jdi
   jdk.unsupported

オプションなしで jar だけ指定するともっと細かく出るがこの後設定するのにはこれで十分だと思う。 ・・・何か leiningen の追加 task で設定できそうな出力だなあ。

project.clj に必要モジュール指定の設定を追加する

さっきの情報を元にproject.cljにこんな感じの設定を追加する。

  :jlink-modules ["java.desktop"
                  "java.management"
                  "java.security.jgss"
                  "java.sql"
                  "java.xml.crypto"
                  "jdk.javadoc"
                  "jdk.jdi"
                  "jdk.unsupported"]

java.base モジュールはデフォルトで指定されるので追加不要との事(参考)。

カスタム JRE 作成

leiningen プロジェクトフォルダルートに入って以下を実行。

lein jlink init

するとデフォルトでは target\image 下にカスタム JRE が出来る。 :jlink-modules で指定したモジュールをカスタム JRE に含めるようで、指定なしのjava.baseのみだとサイズは 24.1 MB と中々小さめだが 今回の指定では約 49 MB と倍増した。とは言え昔のような JRE 全体を同梱していたサイズと比較すればそれでも十分小さい方だと思う。

ただすぐ後で書いているが、デフォルトの場所は変更した方が良さそう。

カスタム JRE 作成場所の設定

自分がまだちゃんと公式ドキュメント類を読んでないだけかもしれないが 後述する lein jlink assemble 時に target\image フォルダが消される動きをするみたいで assemble タスクが 完了しなかった。これを回避するにはカスタム JRE 作成場所を決める :jlink-image-path の値に別のパスを指定してやれば良い。 例えば project.clj に以下を追加する。

  :jlink-image-path ["image"]

これで assemble タスクが最後まで行けるようになる。

実行スクリプト作成

既に出てきたが以下を実行。

lein jlink assemble

これで :jlink-image-path に指定した場所に uberjar された実行可能 jar ファイルがコピーされ、 その場所にあるカスタム JRE で動かすスクリプト(bat/ps/sh)が作られる。

コマンドライン引数無しならただスクリプト実行すれば良いし、 有りなら自分が使う起動スクリプトコマンドライン引数を受け入れるようちゃちゃっと修正すれば良い。

パッケージング

参考

一応 lein jlink packagetar.gz または zip で固められるが 試してみた所 プロジェクト名などでフォルダを作らず、:jlink-image-path の下にある ファイル、フォルダを直接固める動きなのが自分は好きじゃなかったのでこれだけ使っていない。

最後に

やってみて実感したが JPMS は後方互換性も十分に考慮されてるようで、 詳細は出せないが今回自分がテストで使った過去作った Java(Clojure) コマンドラインツールは 依存ライブラリが別にモジュール化されている必要もなければ、 自分が作ったコードを特別何かモジュール化する必要もなく利用可能なのはちょっとした感動だった。

いやね、何か色々作業する必要あるのかなって思ってたんで。

もしかしたら使い続けてて GraalVM native-image の時のように 制限なり特有の問題にぶち当たったりするかもしれないがそれはまた起こった時にでも。