【Modern Java】jpackage ツール(JEP 343) の使い方

f:id:Naotsugu:20200722233213p:plain

blog1.mammb.com


JEP 343: Packaging Tool (Incubator)

Java 14 で追加されたパッケージングツールによりプラットフォーム別のネイティブなインストールパッケージを作成できます。

Windows では msi と exe、macOS では pkg と dmg、Linux では deb と rpm 形式のパッケージをコマンドラインツール(jpackage)を介して作成したり、ToolProvider API を介してプログラム的に作成することができます。これにより、インストールやアンインストールをユーザに馴染みのある方法で行うことが可能となります。

また、jlink を使用して JDK を必要最低限のモジュールセットに分解してインストーラに含めることで、ターゲットマシンの環境に依存しない適切なサイズのパッケージとして配布することができます。

JDK 7 の javafxpackager 、JDK 8 で名を変え javapackager が同様のツールになりますが、こちらは JavaFX のJDK からの削除に合わせて JDK 11 から削除されました。jpackage はこの後継として JDK 14 に含まれるようになりました。


jpackage の使い方

$JAVA_HOME/bin 配下に jpackage コマンドが追加されており、このコマンドラインツールによりパッケージを生成します。

jpackage でパッケージを作成するために、各プラットフォームで追加のソフトウェアが必要になります。

  • macOS
    • パッケージに署名(--mac-sign)する場合と、アイコンを指定(--icon)する場合には Xcode command line tools が必要
  • Windows
  • Linux
    • Red Hat Linux の場合は rpm-build package が必要
    • Ubuntu Linux の場合は fakeroot package が必要

このツールによる配布パッケージの作成は、Java 9 で入った Jigsaw つまり、モジュールシステム構成(module-info.javaが定義されている)であるかそうでないかで使い方が多少異なります。


非モジュールアプリケーションの場合、アプリケーションの Jar ファイルが build/libs に有り、main.jar という名前とすると、以下のようにすることでインストーラが作成できます。

$ jpackage --name myapp --input build/libs --main-jar main.jar

macOS の場合は myapp.dmg という dmg パッケージが生成されます(プラットフォーム別のデフォルト形式)。

Jar ファイルの MANIFEST.MF ファイルに Main-Class 属性がない場合は、以下のようにメインクラスを明示的に指定します。

$ jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main

jlink コマンドにて必要なモジュールを指定して Jar を作成することで、最低限のランタイムで容量の少ないパッケージにすることもできます。


アプリケーションがモジュール化されている場合、メインクラスが myapp モジュールに含まれるとすると、以下のようにすることでインストーラが作成できます。

$ jpackage --name myapp --module-path build/libs -m myapp

モジュール化されたアプリケーションを Jar 化する場合、jar コマンドで --main-class オプションを指定すると、MANIFEST.MF ファイルの Main-Class 属性にメインクラスが設定される他、module-info.class に ModuleMainClass 属性が設定されます。

この指定がなく、モジュールがメインクラスを特定しない場合は、以下のようにメインクラスを明示的に指定する必要があります。

$ jpackage --name myapp --module-path build/libs -m myapp/myapp.Main


jpackage のオプション

jpackage コマンドの代表的なオプションには以下のようなものがあります。

オプション 説明
--help (-h) 有効な各オプションのリストと説明を出力
--name ( -n ) アプリケーションおよびパッケージの名前
--dest ( -d ) 生成された出力ファイルが配置されるパス
--type ( -t ) 作成するパッケージのタイプ(app-image,exe, msi, rpm, deb, pkg, dmg)
--app-version アプリケーションまたはパッケージ(あるいはその両方)のバージョン
--vendor ベンダー文字列
--icon アプリケーション・バンドルのアイコンのパス
--input ( -i ) パッケージ化するファイルを含む入力ディレクトリのパス
--module-path ( -p ) モジュールのディレクトリまたはモジュラjarへのパス
--main-class 実行するアプリケーション・メイン・クラスの修飾名(--main-jar が指定されている場合のみ)
--module ( -m ) アプリケーションのメイン・モジュール(オプションでメイン・クラス)を指定
--arguments メイン・クラスに渡すコマンドライン引数(起動ツールにコマンドライン引数が指定されていない場合)
--java-options Javaランタイムに渡すオプション
--win-menu Windows の場合、アプリケーションをシステム・メニューに追加(--win-menu-group でスタート・メニュー・グループを指定可能)


ヘルプの出力は以下のようになります。

$ jpackage -h
使用方法: jpackage <options>

使用例:
--------------
    ホスト・システムに適したアプリケーション・パッケージを生成します。
        モジュラ・アプリケーションの場合:
            jpackage -n name -p modulePath -m moduleName/className
        非モジュラ・アプリケーションの場合:
            jpackage -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        事前作成されたアプリケーション・イメージから:
            jpackage -n name --app-image appImageDir
    アプリケーション・イメージの生成:
        モジュラ・アプリケーションの場合:
            jpackage --type app-image -n name -p modulePath \
                -m moduleName/className
        非モジュラ・アプリケーションの場合:
            jpackage --type app-image -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        jlinkに独自のオプションを指定するには、jlinkを別個に実行します。
            jlink --output appRuntimeImage -p modulePath -m moduleName \
                --no-header-files [<additional jlink options>...]
            jpackage --type app-image -n name \
                -m moduleName/className --runtime-image appRuntimeImage
    Javaランタイム・パッケージを生成します。
        jpackage -n name --runtime-image <runtime-image>

一般的なオプション:
  @<filename>
          ファイルからの読取りオプションおよびモード
          このオプションは複数回使用できます。
  --type -t <type>
          作成するパッケージのタイプ
          有効な値: {"app-image", "dmg", "pkg"}
          このオプションが指定されていない場合、プラットフォーム依存の
          デフォルト・タイプが作成されます
  --app-version <version>
          アプリケーションおよびパッケージのバージョン
  --copyright <copyright string>
          アプリケーションのコピーライト
  --description <description string>
          アプリケーションの説明
  --help -h
          使用方法テキストと現在のプラットフォームの有効なオプションのリストと説明を
          出力ストリームに出力して、終了します
  --name -n <name>
          アプリケーションおよびパッケージの名前
  --dest -d <destination path>
          生成された出力ファイルが配置されるパス
          デフォルトは現在の作業ディレクトリです。
          (絶対パスまたは現在のディレクトリからの相対パス)
  --temp <file path>
          一時ファイルの作成に使用される新規または空のディレクトリのパス
          (絶対パスまたは現在のディレクトリからの相対パス)
          指定した場合、タスク完了時に一時ディレクトリは削除されないため
          手動で削除する必要があります
          指定しなかった場合、一時ディレクトリが作成され
          タスク完了時に削除されます。
  --vendor <vendor string>
          アプリケーションのベンダー
  --verbose
          詳細な出力を有効にします
  --version
          製品バージョンを出力ストリームに出力して終了します

ランタイム・イメージを作成するためのオプション:
  --add-modules <module name>[,<module name>...]
          追加するモジュールのカンマ(",")区切りリスト。
          このモジュール・リストとメイン・モジュール(指定した場合)
          が--add-module引数としてjlinkに渡されます。
          指定しなかった場合、メイン・モジュールのみ(--moduleが
          指定された場合)、またはデフォルトのモジュール・セット(--main-jarが
          指定された場合)が使用されます。
          このオプションは複数回使用できます。
  --module-path -p <module path>...
          パスの:区切りリスト
          各パスは、モジュールのディレクトリまたは
          モジュラjarへのパスです。
          (各パスは、絶対パスまたは現在のディレクトリからの相対パスです)
          このオプションは複数回使用できます。
  --bind-services
          --bind-servicesオプションをjlink (
          サービス・プロバイダ・モジュールとその依存性内でリンクします)に渡します
  --runtime-image <file path>
          アプリケーション・イメージにコピーされる、事前定義済みのランタイム・イメージ
          のパス
          (絶対パスまたは現在のディレクトリからの相対パス)
          --runtime-imageが指定されていない場合、jpackageはjlinkを実行し、
          次のオプションを使用してランタイム・イメージを作成します:
          --strip-debug、--no-header-files、--no-man-pagesおよび
          --strip-native-コマンド。

アプリケーション・イメージを作成するためのオプション:
  --icon <icon file path>
          アプリケーション・パッケージのアイコンのパス
          (絶対パスまたは現在のディレクトリからの相対パス)
  --input -i <input path>
          パッケージ化するファイルを含む入力ディレクトリへのパス
          (絶対パスまたは現在のディレクトリからの相対パス)
          入力ディレクトリのすべてのファイルは、アプリケーション・イメージに
          パッケージ化されます。

アプリケーション・ランチャを作成するためのオプション:
  --add-launcher <launcher name>=<file path>
          ランチャの名前、およびキー、値のペアのリスト
          を含むプロパティ・ファイルへのパス
          (絶対パスまたは現在のディレクトリからの相対パス)
          キー"module"、"main-jar"、"main-class"、
          "arguments"、"java-options"、"app-version"、"icon"、
          "win-console"を使用できます。
          これらのオプションを元のコマンドライン・オプションに追加するか、これらのオプションを
          使用して元のコマンドライン・オプションを上書きして、追加の代替ランチャを作成します。
          メイン・アプリケーション・ランチャはコマンドライン・オプションから作成されます。
          このオプションを使用して追加の代替ランチャを作成でき、
          このオプションを複数回使用して
          複数の追加のランチャを作成できます。
  --arguments <main class arguments>
          ランチャにコマンド・ライン引数が指定されていない場合にメイン・クラスに渡す
          コマンド・ライン引数
          このオプションは複数回使用できます。
  --java-options <java options>
          Javaランタイムに渡すオプション
          このオプションは複数回使用できます。
  --main-class <class name>
          実行するアプリケーション・メイン・クラスの修飾名
          このオプションを使用できるのは、--main-jarが指定されている場合だけです。
  --main-jar <main jar file>
          メイン・クラスを含む、アプリケーションのメインJAR
          (入力パスからの相対パスとして指定)
          --moduleまたは--main-jarオプションを指定できますが、両方は
          指定できません。
  --module -m <module name>[/<main class>]
          アプリケーションのメイン・モジュール(およびオプションでメイン・クラス)
          このモジュールは、モジュール・パスに置かれている必要があります。
          このオプションが指定されている場合、メイン・モジュールは
          Javaランタイム・イメージ内でリンクされます。--moduleまたは--main-jar
          オプションを指定できますが、両方は指定できません。
  --mac-package-identifier <ID string>
          MacOSのアプリケーションを一意に識別するID
          メイン・クラス名にデフォルト設定されています。
          英数字(A-Z、a-z、0-9)、ハイフン(-)
          およびピリオド(.)文字のみ使用できます。
  --mac-package-name <name string>
          メニュー・バーに表示されるアプリケーションの名前
          アプリケーション名とは異なります。
          この名前は16文字未満にする必要があり、メニュー・バー
          およびアプリケーション情報ウィンドウに表示するのに適している必要があります。
          アプリケーション名にデフォルト設定されています。
  --mac-package-signing-prefix <prefix string>
          アプリケーション・パッケージに署名する際、既存のパッケージIDのない
          署名が必要なすべてのコンポーネントに、
          この値が接頭辞として付けられます。
  --mac-sign
          パッケージに署名するようリクエストします
  --mac-signing-keychain <file path>
          署名アイデンティティを検索するキーチェーンのパス
          (絶対パスまたは現在のディレクトリからの相対パス)。
          指定しなかった場合、標準のキーチェーンが使用されます。
  --mac-signing-key-user-name <team name>
          Apple署名アイデンティティの名前のチーム名部分。
          例: "Developer ID Application: "

アプリケーション・パッケージを作成するためのオプション:
  --app-image <file path>
          インストール可能なパッケージの作成に使用する、事前定義済み
          アプリケーション・イメージの場所
          (絶対パスまたは現在のディレクトリからの相対パス)
  --file-associations <file path>
          キー、値のペアのリストを含むプロパティ・ファイルへのパス
          (絶対パスまたは現在のディレクトリからの相対パス)
          キー"extension"、"mime-type"、"icon"、"description"
          を使用して関連付けを記述できます。
          このオプションは複数回使用できます。
  --install-dir <file path>
          アプリケーションのインストール・ディレクトリの絶対パス
  --license-file <file path>
          ライセンス・ファイルへのパス
          (絶対パスまたは現在のディレクトリからの相対パス)
  --resource-dir <path>
          オーバーライドjpackageリソースへのパス
          アイコン、テンプレート・ファイルおよびjpackageのその他のリソースは、
          このディレクトリに置換リソースを追加することでオーバーライドできます。
          (絶対パスまたは現在のディレクトリからの相対パス)
  --runtime-image <file-path>
          インストールする事前定義済みのランタイム・イメージのパス
          (絶対パスまたは現在のディレクトリからの相対パス)
          ランタイム・パッケージの作成時には、オプションが必要です。

アプリケーション・パッケージを作成するためのプラットフォーム依存オプション:


Gradle からの jpackage 利用

現実的には jpackage コマンドを直接使うことはほとんどありません。ここでは Gradle でパッケージを作成する方法について説明します。Java14 を使うには Gradle 6.3 以降を使用します。

build.gradle に直接 commandLine でコマンド実行するタスクを定義することもできますが、badass-jlink プラグインを使うと簡単です。

badass-jlink プラグインは、モジュール化されていない依存関係が多数ある場合のカスタムランタイムイメージ作成を簡素化します(モジュール化されていない多数の Jar を1つの Jar に結合し、モジュール記述子を作成)。

badass-jlink プラグインでは jpackage タスクも定義されており、簡単にパッケージが作成できます。


JavaFX による簡単なアプリケーションを例にしましょう。

build.gradle は以下のように定義します。

plugins {
    id 'java'
    id 'org.openjfx.javafxplugin' version '0.0.9'
    id "org.beryx.jlink" version "2.21.0"
}

repositories {
    jcenter()
}

dependencies {
}

application {
    mainClassName = 'com.mammb.app.App'
}

javafx {
    version = '14'
    modules = [ 'javafx.controls' ]
}

badass-jlink プラグインにより application プラグインも暗黙的に定義されたものとなります。


ソースのルートにパッケージ記述子を以下のように作成します。

module com.mammb.app {
    exports com.mammb.app;
    requires javafx.controls;
}


ウインドウを表示するだけの簡単な App を以下のように作成します。

package com.mammb.app;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage stage) {
        Label label = new Label("Hello");
        Scene scene = new Scene(new StackPane(label), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}


実行は以下のようにするだけです。

$ ./gradlew jpackage

f:id:Naotsugu:20200722232808p:plain

build/jpackage 以下にインストールパッケージが作成されました。


パッケージのインストール

作成された app-1.0.pkg を実行してみましょう。

f:id:Naotsugu:20200722233001p:plain

見慣れたインストーラとして起動します。

f:id:Naotsugu:20200722233200p:plain

f:id:Naotsugu:20200722233213p:plain


アプリケーションとして追加されました。

f:id:Naotsugu:20200722233609p:plain

ダブルクリックで起動します。

f:id:Naotsugu:20200722233308p:plain


まとめ

Java 14 で追加されたパッケージングツールの使い方を紹介しました。

それぞれのプラットフォーム別に環境が必要な点は少し使いにくいかもしれません。

それでもディスクトップアプリケーションの配布には良い選択肢になるでしょう。