【初心者向け】Maven Assembly Pluginで実行可能JarやZipを作成

はい、承知いたしました。Maven Assembly Plugin を使って実行可能 JAR や配布用 ZIP を作成する方法について、初心者向けに詳細な記事を記述します。約5000語となるよう、各設定や概念を丁寧に解説し、具体的な例を豊富に盛り込みます。

以下が記事の内容です。


【初心者向け】Maven Assembly Pluginで実行可能JarやZipを作成

はじめに:なぜアプリケーションの「配布可能な形」が必要なのか?

Javaでアプリケーション開発を進めている皆さん、こんにちは! Mavenを使ってプロジェクトのビルドや依存性の管理はできるようになりましたか? 次のステップとして、「作ったアプリケーションを他の人に使ってもらう」「サーバーにデプロイする」という段階に進むと、一つの疑問が浮かび上がります。それは、「どうやって、このたくさんのJavaファイルやライブラリを、まとめて配布できる形にするのだろう?」という疑問です。

アプリケーションを構成するのは、皆さんが書いたJavaコードだけではありません。実行に必要な依存ライブラリ、設定ファイル、起動スクリプト、READMEファイルなど、様々なファイルが必要です。これらを一つずつ手作業で集めて配布するのは、非常に手間がかかりますし、ミスも起こりやすくなります。

ここで登場するのが、アプリケーションを一つのまとまりとしてパッケージングする「配布可能な形」です。代表的なものとしては、以下の二つがあります。

  1. 実行可能JAR (Runnable JAR): これ一つあれば、Java実行環境がある場所で java -jar your-app.jar のようにコマンド一つでアプリケーションを起動できる形式です。依存ライブラリも含めて一つのJARにまとめられていることが多いです。
  2. 配布用アーカイブ (Distribution Archive): アプリケーションのJARファイル、依存ライブラリ群、設定ファイル、起動スクリプト、ドキュメントなどを特定のディレクトリ構造で配置し、それをZIPやTar.gzなどの形式で圧縮したものです。サーバーにデプロイしたり、ユーザーにダウンロードしてもらったりする際に便利な形式です。

これらの「配布可能な形」を自動的に、かつ効率的に作成するために、Mavenには様々なプラグインが用意されています。その中でも、Maven Assembly Plugin は、非常に柔軟かつ強力なプラグインです。

Maven Assembly Plugin とは?なぜこれを選ぶのか?

Maven Assembly Plugin は、プロジェクトの成果物やその依存関係、その他の必要なファイル(設定ファイル、スクリプトなど)を収集し、カスタマイズ可能な形式(JAR、ZIP、Tar.gzなど)でパッケージングするためのMavenプラグインです。

似たような目的を持つプラグインとして、maven-jar-pluginmaven-shade-plugin があります。それぞれの得意なことと、Assembly Pluginが選ばれる理由を簡単に見てみましょう。

  • maven-jar-plugin: これはMavenの標準的なビルドプロセスで自動的に実行されるプラグインで、皆さんが書いたJavaコードなどをコンパイルしてクラスファイルにし、それらをまとめてJARファイルを作成します。しかし、デフォルトではプロジェクトの依存ライブラリをこのJARに含めません。実行するには、別途クラスパスを設定して依存ライブラリを指定する必要があります。実行可能JARを作成する機能も限定的です(Main-Classの設定や、MANIFEST.MFへのClass-Path追記は可能ですが、依存ライブラリ自体をJARに同梱する機能は基本ありません)。
  • maven-shade-plugin: このプラグインは、「Uber JAR」や「Fat JAR」と呼ばれる、依存ライブラリをすべて一つのJARファイルに同梱した実行可能JARを作成するのに特化しています。依存ライブラリに含まれる同じ名前のファイルを適切に処理したり、依存関係の衝突を解決(シェーディングやリロケーション)したりする高度な機能を持っています。実行可能JARを作成するなら、Shade Pluginも有力な選択肢です。
  • maven-assembly-plugin: このプラグインは、単一の実行可能JARだけでなく、複雑なディレクトリ構造を持つ配布用アーカイブ(ZIP, Tar.gzなど)を作成するのに非常に長けています。プロジェクトのJAR、依存ライブラリ群を別々のディレクトリに配置したり、設定ファイルやスクリプトなど、ビルド成果物以外のファイルを自由な場所に含めたりすることができます。Shade Pluginのような高度な依存性解決機能は持ちませんが、アーカイブ構造の柔軟性という点ではAssembly Pluginが優れています。

つまり、

  • 単にプロジェクトのJARを作成したい → maven-jar-plugin (デフォルト)
  • 依存ライブラリを一つのJARにまとめて実行可能にしたい、かつ依存性の衝突解決が重要 → maven-shade-plugin
  • 依存ライブラリを同梱した実行可能JARだけでなく、設定ファイルやスクリプトなども含めた複雑なディレクトリ構造を持つ配布用アーカイブ(ZIP/Tar.gzなど)を作成したいmaven-assembly-plugin

このように、Assembly Pluginは「様々な要素を組み合わせて、柔軟な構造を持つ配布物を作成する」場合に真価を発揮します。特に、サーバーアプリケーションや、複数の設定ファイル、外部スクリプトが必要なデスクトップアプリケーションなどの配布物作成に適しています。

この記事では、Maven Assembly Pluginの基本的な使い方から始め、最終的にカスタム定義を使って思い通りのアーカイブを作成する方法までを、初心者の方にも分かりやすく解説していきます。

Maven Assembly Plugin の基本的な使い方:まずは簡単な実行可能JARから

まずはAssembly Pluginをプロジェクトに追加し、最も簡単な「依存ライブラリをすべて含んだ実行可能JAR」を作成してみましょう。これはAssembly Pluginの標準的な機能の一つである jar-with-dependencies という定義済みディスクリプタを使います。

1. pom.xml への Assembly Plugin の追加

Mavenプラグインを使用するには、プロジェクトの pom.xml<build> セクションを追加(または編集)し、<plugins> の中にプラグインの定義を記述します。

“`xml
4.0.0

<groupId>com.example</groupId>
<artifactId>my-awesome-app</artifactId>
<version>1.0.0-SNAPSHOT</version>

<name>My Awesome Application</name>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
    <!-- アプリケーションに必要な依存ライブラリ -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>31.1-jre</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
    <!-- 例として、何か簡単なユーティリティライブラリ -->
</dependencies>

<build>
    <plugins>
        <!-- Maven Assembly Plugin の定義 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.5.0</version> <!-- 最新バージョンを指定することが推奨されます -->
            <configuration>
                <!-- ここにアセンブリの定義を書きます -->
                <!-- 今回は jar-with-dependencies という定義済みディスクリプタを使用 -->
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <!-- 生成されるJARファイル名に assembly ID を付与するかどうか -->
                <appendAssemblyId>true</appendAssemblyId>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id> <!-- 任意のID -->
                    <phase>package</phase> <!-- package フェーズで実行 -->
                    <goals>
                        <goal>single</goal> <!-- single ゴールを実行 -->
                    </goals>
                </execution>
            </executions>
        </plugin>

        <!-- main メソッドを持つクラスを指定するための maven-jar-plugin の設定 -->
        <!-- 実行可能JARとするためには、JARファイルに Main-Class を指定する必要がある -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.3.0</version> <!-- 最新バージョンを指定することが推奨されます -->
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.example.myapp.MainApp</mainClass> <!-- ここに皆さんの Main メソッドがあるクラス名を指定 -->
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

“`

ポイント解説:

  • <groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId> でプラグインを指定します。<version> には適切なバージョンを指定してください。
  • <configuration> セクションでAssembly Pluginの動作を設定します。
    • <descriptorRefs>: 使用する「アセンブリディスクリプタ」を指定します。ディスクリプタとは、「どのようなファイルを含めるか、どのように配置するか、どのような形式のアーカイブにするか」を定義したものです。ここではAssembly Pluginが提供する定義済みのディスクリプタである jar-with-dependencies を指定しています。
    • <appendAssemblyId>: 生成されるファイル名に、使用したディスクリプタのID(ここではjar-with-dependencies)を付与するかどうかです。true にすると、例えば my-awesome-app-1.0.0-SNAPSHOT-jar-with-dependencies.jar のようなファイル名になります。false にすると、単に my-awesome-app-1.0.0-SNAPSHOT.jar となりますが、複数のアセンブリを作成する場合に区別がつかなくなるため、true にしておくのが一般的です。
  • <executions>: プラグインを実行するタイミングやゴールを設定します。
    • <execution>: 一つの実行設定です。
    • <id>: 実行設定の識別子です。任意のもので構いません。
    • <phase>: この実行設定をMavenのどのビルドフェーズで実行するかを指定します。ここでは package フェーズを指定しています。package フェーズは、コンパイル、テストなどが完了した後に、プロジェクトの成果物(JARなど)を作成するフェーズです。このフェーズでAssembly Pluginを実行することで、通常のJAR作成と並行してアセンブリ(今回の場合は実行可能JAR)を作成できます。
    • <goals>: この実行設定で実行するプラグインのゴールを指定します。Assembly Pluginにはいくつかゴールがありますが、single ゴールは、指定したディスクリプタを使って一つのアセンブリを作成する最も一般的なゴールです。
  • maven-jar-plugin の設定: 実行可能JARとするためには、そのJARファイルに「どのクラスの main メソッドを実行すればよいか」を示す情報(Main-Class属性)を記録する必要があります。これは通常 maven-jar-plugin<archive> セクションで設定します。Assembly Pluginが作成する jar-with-dependencies も、プロジェクト本体のJAR(通常 maven-jar-plugin が作成したもの)をベースにするため、ここで Main-Class を設定しておくことが重要です。com.example.myapp.MainApp の部分は、皆さんのプロジェクトの main メソッドが含まれるクラスの完全修飾名に置き換えてください。

2. Main メソッドを持つクラスの準備

実行可能JARを作成する場合、アプリケーションのエントリーポイントとなる main メソッドを持つクラスが必要です。以下のような簡単なクラスを用意しておきましょう。

“`java
// src/main/java/com/example/myapp/MainApp.java
package com.example.myapp;

import com.google.common.collect.Lists; // guava ライブラリの利用例
import org.apache.commons.io.FileUtils; // commons-io ライブラリの利用例

import java.io.File;
import java.io.IOException;
import java.util.List;

public class MainApp {
public static void main(String[] args) {
System.out.println(“アプリケーションが起動しました!”);

    // Guava ライブラリの機能を使ってみる
    List<String> names = Lists.newArrayList("Alice", "Bob", "Charlie");
    System.out.println("名前リスト (Guava): " + names);

    // Commons-IO ライブラリの機能を使ってみる
    try {
        File tempFile = new File("temp.txt");
        FileUtils.writeStringToFile(tempFile, "これは一時ファイルです。", "UTF-8");
        System.out.println("一時ファイル '" + tempFile.getAbsolutePath() + "' を作成しました。");
        String content = FileUtils.readFileToString(tempFile, "UTF-8");
        System.out.println("一時ファイルの内容: " + content);
        FileUtils.deleteQuietly(tempFile); // 静かに削除
        System.out.println("一時ファイルを削除しました。");
    } catch (IOException e) {
        e.printStackTrace();
    }

    System.out.println("アプリケーションが終了します。");
}

}
“`

このクラスが、先ほど pom.xmlmaven-jar-plugin 設定で mainClass として指定した com.example.myapp.MainApp である必要があります。

3. ビルドの実行

pom.xml の設定と MainApp.java が準備できたら、Mavenビルドを実行します。

プロジェクトのルートディレクトリ(pom.xml がある場所)で、コマンドラインから以下のコマンドを実行してください。

bash
mvn clean package

または

bash
mvn clean install

package フェーズを実行すると、その前までのフェーズ(validate, compile, testなど)も順番に実行されます。Assembly Pluginは package フェーズにバインドされているため、このコマンドでAssembly Pluginも実行されます。

ビルドが成功すると、コンソールに出力されるログの中に、Assembly Pluginが実行され、ファイルが作成された旨のメッセージが表示されるはずです。

4. 生成されたファイルの確認

ビルドが成功したら、プロジェクトの target ディレクトリを開いてみましょう。

target ディレクトリの中には、いくつかファイルやディレクトリが生成されているはずです。その中に、以下のような名前のJARファイルが見つかるはずです。

  • my-awesome-app-1.0.0-SNAPSHOT.jar (これは通常、maven-jar-plugin が作成した、依存ライブラリを含まないJARです)
  • my-awesome-app-1.0.0-SNAPSHOT-jar-with-dependencies.jar (これがAssembly Pluginが作成した、依存ライブラリを含むJARです)

ファイル名の -jar-with-dependencies の部分は、pom.xml<appendAssemblyId>true</appendAssemblyId> と設定した結果です。

5. 実行可能JARの実行

生成された my-awesome-app-1.0.0-SNAPSHOT-jar-with-dependencies.jar が本当に実行可能か、試してみましょう。

コマンドラインで、target ディレクトリに移動し、以下のコマンドを実行します。

bash
cd target
java -jar my-awesome-app-1.0.0-SNAPSHOT-jar-with-dependencies.jar

アプリケーションが起動し、MainApp クラスで記述した処理(メッセージ表示、Guava/Commons-IOの機能利用)が実行されるはずです。

アプリケーションが起動しました!
名前リスト (Guava): [Alice, Bob, Charlie]
一時ファイル '/path/to/your/project/target/temp.txt' を作成しました。
一時ファイルの内容: これは一時ファイルです。
一時ファイルを削除しました。
アプリケーションが終了します。

このように、jar-with-dependencies ディスクリプタを使うことで、簡単に依存ライブラリを同梱した実行可能JARを作成できます。これは、ちょっとしたツールや、依存ライブラリが少ないアプリケーションを配布する場合に非常に手軽で便利です。

jar-with-dependencies ディスクリプタの詳細

jar-with-dependencies ディスクリプタは、内部的にどのように機能しているのでしょうか?

このディスクリプタは、基本的に以下の要素を収集します。

  1. プロジェクトのクラスファイルやリソースファイル: src/main/javasrc/main/resources に含まれるファイルがコンパイル・コピーされたもの。
  2. すべてのプロダクションスコープ(compile, runtime)の依存ライブラリ: pom.xml<dependencies> セクションで定義されている依存ライブラリのうち、providedtest スコープ以外のもの。

これらの要素をすべて展開し、一つの大きなJARファイルの中に再パッケージングします。つまり、依存ライブラリである Guava や Commons-IO のJARファイルがそのまま含まれるのではなく、それらのJARファイルの中身(.class ファイルなど)が、皆さんのアプリケーションのクラスファイルと同じ階層に展開されて含まれます。

生成されたJARファイルの内部構造を覗いてみると、多くの.classファイルやリソースファイルがルート直下やパッケージ名に基づいたディレクトリに含まれていることが確認できます。

そして、このJARを実行可能にするために、先ほど maven-jar-plugin で設定した Main-Class 情報が、生成されるJARの META-INF/MANIFEST.MF ファイルに記録されます。java -jar コマンドでJARを実行すると、JVMはこの MANIFEST.MF を読み込み、Main-Class 属性で指定されたクラスを見つけて、その main メソッドを実行するわけです。

注意点:

  • 依存ライブラリの衝突: 複数の依存ライブラリが同じ名前のクラスやリソースを含んでいる場合、Assembly Pluginは単にそれらを展開して含めるため、どちらか一方が上書きされるなどの問題が発生し、実行時にエラーとなる可能性があります。このような依存性の衝突をより高度に解決したい場合は、maven-shade-plugin の方が適している場合があります。
  • JARファイルの肥大化: 依存ライブラリが多い場合、生成される jar-with-dependencies は非常に大きくなることがあります。

jar-with-dependencies は手軽ですが、アーカイブ構造を細かく制御したい場合や、依存性の衝突が懸念される場合には、次に説明するカスタムディスクリプタや、maven-shade-plugin の利用を検討する必要があります。

配布用アーカイブ (ZIP/Tar.gz) を作成する:bin ディスクリプタとカスタムディスクリプタの導入

実行可能JAR一つで事足りるアプリケーションも多いですが、現実のアプリケーションでは、以下のようなものを一緒に配布したいケースがよくあります。

  • 設定ファイル: アプリケーションの動作を外部から変更するためのファイル(例: application.properties, logback.xml)。
  • 起動スクリプト: Windowsなら .bat ファイル、Linux/macOSなら .sh ファイルなど、アプリケーションを簡単に起動するためのシェルスクリプト。
  • READMEファイルやライセンスファイル: アプリケーションの使い方やライセンス情報を記載したファイル。
  • 依存ライブラリ: 実行可能JARにするのではなく、アプリケーション本体のJARと依存ライブラリ群を分けて配布したい場合。

このような要件を満たすには、Assembly Pluginの カスタムディスクリプタ を使うのが一般的です。カスタムディスクリプタを使うことで、「どのファイルを、アーカイブ内のどのディレクトリに、どのような権限で配置するか」といったことを細かく定義できます。

その前に、Assembly Pluginが提供するもう一つの便利な定義済みディスクリプタ bin を見てみましょう。

1. 定義済みディスクリプタ bin

bin ディスクリプタは、実行可能なアプリケーションの配布物を想定した構造のアーカイブを作成します。具体的には、通常以下のような内容を含むアーカイブ(ZIPやTar.gzなど)を作成します。

  • プロジェクト本体のJARファイル
  • 依存ライブラリ群をまとめたディレクトリ(例: lib/
  • READMEファイルやライセンスファイル(プロジェクトに存在する場合)

早速 bin ディスクリプタを使ってみましょう。pom.xml の Assembly Plugin の設定を以下のように変更します。

xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<descriptorRefs>
<!-- jar-with-dependencies の代わりに bin を指定 -->
<descriptorRef>bin</descriptorRef>
</descriptorRefs>
<appendAssemblyId>true</appendAssemblyId>
<!-- 複数の形式で出力したい場合は formats を指定 -->
<formats>
<format>zip</format>
<format>tar.gz</format>
</formats>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

<descriptorRefs><descriptorRef>bin</descriptorRef> に変更し、さらに <formats> セクションを追加しました。<formats> では、ziptar.gz の両方の形式でアーカイブを作成するように指定しています。指定しない場合は、デフォルトの形式(通常はZIP)が使用されます。

この設定で再度 mvn clean package を実行すると、target ディレクトリに以下のファイルが生成されます。

  • my-awesome-app-1.0.0-SNAPSHOT.jar (依存ライブラリを含まない本体JAR)
  • my-awesome-app-1.0.0-SNAPSHOT-bin.zip
  • my-awesome-app-1.0.0-SNAPSHOT-bin.tar.gz

生成された my-awesome-app-1.0.0-SNAPSHOT-bin.zip を解凍してみましょう。デフォルトでは、プロジェクト名とバージョンを含むトップレベルディレクトリの中にファイルが配置されます。

my-awesome-app-1.0.0-SNAPSHOT-bin/
├── lib/
│ ├── guava-31.1-jre.jar
│ ├── commons-io-2.11.0.jar
│ └── ... (その他の依存ライブラリ)
└── my-awesome-app-1.0.0-SNAPSHOT.jar

このように、bin ディスクリプタは、プロジェクト本体のJARと依存ライブラリを分離して配置する基本的な配布用アーカイブを作成してくれます。これはこれで便利ですが、「config ディレクトリを作成して設定ファイルを入れたい」「bin ディレクトリを作成して起動スクリプトを入れたい」といった、よりカスタムな構造にしたい場合には、物足りません。

そこで、カスタムディスクリプタ の出番です。

2. カスタムXMLディスクリプタの作成

Assembly Pluginの最大の強みは、XML形式のディスクリプタファイルを使って、アーカイブの内容と構造を自由に定義できることです。

ディスクリプタファイルは通常、プロジェクトの src/main/assembly ディレクトリの下に作成します。ファイル名は任意ですが、例えば distribution.xml といった名前がよく使われます。

src/main/assembly/distribution.xml ファイルを作成し、基本的な構造を記述してみましょう。

“`xml

<!-- アセンブリのID。生成ファイル名や appendAssemblyId=true の場合に利用 -->
<id>custom</id>

<!-- 生成するアーカイブ形式 -->
<formats>
    <format>zip</format>
    <format>tar.gz</format>
</formats>

<!-- アーカイブのルートにベースディレクトリ(プロジェクト名-バージョン)を含めるか -->
<includeBaseDirectory>true</includeBaseDirectory>
<!-- includeBaseDirectory を true にした場合のディレクトリ名。省略すると ${project.artifactId}-${project.version} -->
<!-- <baseDirectory>my-app-dist</baseDirectory> -->

<!-- ここに含めるファイルや依存関係などの定義を記述 -->


“`

ポイント解説:

  • <assembly> がルート要素です。名前空間の定義などが含まれます。
  • <id>: このカスタムディスクリプタの識別子です。生成されるファイル名に付与されます(例: my-awesome-app-1.0.0-SNAPSHOT-custom.zip)。
  • <formats>: 生成したいアーカイブ形式を指定します。<format> 要素の中に zip, tar.gz, tar.bz2, jar, dir などを記述できます。dir は、圧縮されていないディレクトリ構造として出力します。
  • <includeBaseDirectory>: true にすると、アーカイブのルートに プロジェクト名-バージョン のようなディレクトリが作成され、その中にすべての内容が格納されます。false にすると、アーカイブを解凍した際に、内容が直接カレントディレクトリに展開されます。通常は true にしておく方が、他のファイルとの衝突を防げるため安全です。<baseDirectory> で明示的にベースディレクトリ名を指定することも可能です。

このディスクリプタを使用するには、pom.xml の Assembly Plugin 設定を以下のように変更します。<descriptorRefs> の代わりに <descriptor><descriptors> を使用し、ディスクリプタファイルのパスを指定します。

xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<!-- 定義済みディスクリプタではなく、カスタムディスクリプタファイルを指定 -->
<descriptors>
<descriptor>src/main/assembly/distribution.xml</descriptor>
</descriptors>
<appendAssemblyId>true</appendAssemblyId>
<!-- formats はディスクリプタファイル側で指定したので、pom.xmlからは削除しても良いが、両方あっても動作する -->
<!-- <formats> ... </formats> -->
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

<descriptorRefs> を削除し、代わりに <descriptors> の中に <descriptor>src/main/assembly/distribution.xml</descriptor> を追加しました。これで、Assembly PluginはこのXMLファイルの内容に従ってアセンブリを作成するようになります。

この時点では、distribution.xml の中にファイルを含める定義を何も書いていないため、ビルドしても空っぽのアーカイブが作成されるだけです。次に、アーカイブに含める内容を定義する方法を見ていきましょう。

カスタムディスクリプタでの要素定義:Filesets, DependencySets, etc.

カスタムディスクリプタ (distribution.xml) の <assembly> 要素の中には、含めたいファイルや依存関係の種類に応じて、様々な要素を記述できます。代表的な要素は以下の通りです。

  • <filesets>: プロジェクト内の特定のディレクトリから、指定したパターンに一致するファイルをコピーして含める。
  • <files>: プロジェクト内の特定の単一ファイルをコピーして含める。
  • <dependencySets>: プロジェクトの依存ライブラリを収集して含める。
  • <moduleSets>: マルチモジュールプロジェクトの場合に、他のモジュールの成果物を含める。
  • <repositorySets>: Mavenリポジトリから特定のアーティファクトを取得して含める。

これらの要素を使って、配布用アーカイブの構造を構築していきます。

1. <filesets>:プロジェクト内のファイルをまとめて含める

<filesets> 要素は、プロジェクトのベースディレクトリからの相対パスで指定したディレクトリにあるファイル群を、パターンマッチング(includes, excludes)を使って選択し、アーカイブ内の指定した場所(outputDirectory)にコピーするために使用します。

例として、設定ファイルやスクリプトをアーカイブに含めてみましょう。

プロジェクトの構造を以下のように仮定します。

my-awesome-app/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/...
│ │ ├── resources/...
│ │ └── assembly/
│ │ └── distribution.xml <-- ここにカスタムディスクリプタ
│ └── scripts/ <-- スクリプトを置くディレクトリ
│ └── startup.sh
│ └── config/ <-- 設定ファイルを置くディレクトリ
│ └── application.properties
└── README.md <-- READMEファイル

src/scripts/startup.shsrc/config/application.properties、そしてルートの README.md をアーカイブに含めたいとします。

src/main/assembly/distribution.xml に、以下の <filesets> 要素を追加します。

“`xml

<id>custom</id>
<formats>
    <format>zip</format>
    <format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>

<filesets>
    <!-- スクリプトファイルを含める -->
    <fileset>
        <!-- コピー元のディレクトリ(プロジェクトのベースディレクトリからの相対パス) -->
        <directory>${project.basedir}/src/scripts</directory>
        <!-- コピー先のアーカイブ内のディレクトリ(ベースディレクトリからの相対パス) -->
        <outputDirectory>bin</outputDirectory>
        <!-- 含めるファイルパターン -->
        <includes>
            <include>*.sh</include>
            <include>*.bat</include>
        </includes>
        <!-- ファイルの権限設定(Unix/Linuxの場合) -->
        <fileMode>0755</fileMode> <!-- 実行権限を付与 -->
    </fileset>

    <!-- 設定ファイルを含める -->
    <fileset>
        <directory>${project.basedir}/src/config</directory>
        <outputDirectory>config</outputDirectory>
        <includes>
            <include>*.properties</include>
            <include>*.xml</include>
        </includes>
        <fileMode>0644</fileMode> <!-- 読み書き権限のみ -->
    </fileset>

    <!-- プロジェクトルートにあるファイルを含める (READMEなど) -->
    <fileset>
        <directory>${project.basedir}</directory>
        <outputDirectory>/</outputDirectory> <!-- アーカイブのルートディレクトリ -->
        <includes>
            <include>README*</include> <!-- READMEから始まるファイル -->
            <include>LICENSE*</include> <!-- LICENSEから始まるファイル -->
        </includes>
        <fileMode>0644</fileMode>
        <!-- プロジェクトのpom.xmlやtargetディレクトリなどを除外する場合は excludes を使う -->
        <excludes>
             <exclude>pom.xml</exclude>
             <exclude>target/</exclude>
        </excludes>
    </fileset>
</filesets>


“`

<filesets> および <fileset> 要素の詳細:

  • <filesets>: 複数の <fileset> 定義を含むための親要素です。
  • <fileset>: 単一のファイルセット定義です。
    • <directory>: コピー元のディレクトリを指定します。${project.basedir} は、プロジェクトのルートディレクトリを指すMavenプロパティです。このパスはプロジェクトのルートからの相対パスでも絶対パスでも指定できますが、${project.basedir} を使うのが一般的でポータブルです。
    • <outputDirectory>: コピー先の、アーカイブ内のディレクトリを指定します。アーカイブのルートからの相対パスです。/ はアーカイブのルート自身を意味します。bin, config と指定すると、アーカイブ内に bin ディレクトリ、config ディレクトリが作成されます。
    • <includes>: 含めたいファイルのパターンを指定します。<include> 要素の中にAntスタイルのパターン(例: *.sh, **/*.properties)を記述します。省略した場合、指定ディレクトリ以下のすべてのファイルが対象になります。
    • <excludes>: 除外したいファイルのパターンを指定します。<exclude> 要素の中にパターンを記述します。includes で指定したファイルの中から、excludes で指定したファイルが除外されます。
    • <fileMode>: アーカイブ内のファイルに設定する権限を、Unixスタイルの8進数表記で指定します(例: 0644, 0755)。これはZIP形式では反映されませんが、Tar.gzなどの形式で有効になります。特にスクリプトファイルには実行権限(例えば 0755)を付与することがよくあります。指定しない場合、デフォルトの権限が適用されます。
    • <directoryMode>: <fileMode> と同様に、作成されるディレクトリに設定する権限です。

上記の distribution.xml とプロジェクト構造で mvn clean package を実行し、生成されたZIPファイルを解凍すると、以下のような構造になっているはずです(includeBaseDirectorytrue の場合)。

my-awesome-app-1.0.0-SNAPSHOT-custom/
├── bin/
│ └── startup.sh (実行権限付き)
├── config/
│ └── application.properties
└── README.md

これで、ソースコードや依存ライブラリ以外の必要なファイルをアーカイブに含めることができました。

2. <files>:特定の単一ファイルを含める

<filesets> はパターンで複数ファイルを指定するのに便利ですが、特定の単一ファイルを、別の名前でアーカイブに含めたい場合や、単純にファイル名を指定したい場合は <files> 要素を使用します。

例として、プロジェクトルートにある特定のファイル notice.txt をアーカイブのルートに含めたい場合を考えます。

xml
<assembly ...>
...
<files>
<file>
<!-- コピー元のファイル(プロジェクトのベースディレクトリからの相対パス) -->
<source>${project.basedir}/notice.txt</source>
<!-- コピー先のアーカイブ内のディレクトリ -->
<outputDirectory>/</outputDirectory>
<!-- アーカイブ内でのファイル名。省略すると source のファイル名が使われる -->
<!-- <destName>NOTICE</destName> -->
<!-- ファイルの権限 -->
<fileMode>0644</fileMode>
</file>
<!-- 複数のファイルを含めたい場合は file 要素を繰り返す -->
</files>
...
</assembly>

<files> および <file> 要素の詳細:

  • <files>: 複数の <file> 定義を含むための親要素です。
  • <file>: 単一のファイル定義です。
    • <source>: コピー元のファイルのパスを指定します。${project.basedir} を使うのが一般的です。
    • <outputDirectory>: コピー先のアーカイブ内のディレクトリを指定します。
    • <destName>: アーカイブ内でのファイル名を指定します。省略すると <source> のファイル名がそのまま使用されます。別の名前でコピーしたい場合に指定します。
    • <fileMode>: ファイルの権限を設定します。

<filesets><files> の使い分けとしては、複数のファイルをまとめて扱いたい場合は <filesets>、特定の単一ファイルを細かく指定したい場合は <files> と考えると良いでしょう。

3. <dependencySets>:プロジェクトの依存ライブラリを含める

配布用アーカイブには、実行に必要な依存ライブラリを含めるのが一般的です。<dependencySets> 要素を使用すると、プロジェクトの依存ライブラリを収集し、アーカイブ内の指定した場所に配置できます。

例えば、依存ライブラリをアーカイブ内の lib ディレクトリにすべてコピーしたい場合、distribution.xml に以下の <dependencySets> 要素を追加します。

xml
<assembly ...>
...
<dependencySets>
<dependencySet>
<!-- コピー先のアーカイブ内のディレクトリ -->
<outputDirectory>lib</outputDirectory>
<!-- 含める依存ライブラリのスコープを指定 -->
<scope>runtime</scope> <!-- 実行時に必要なライブラリ(compile, runtime) -->
<!-- プロジェクト本体のアーティファクト(JAR)を含めるか -->
<useProjectArtifact>false</useProjectArtifact>
<!-- 依存ライブラリ(JAR)を展開して含めるか -->
<unpack>false</unpack>
<!-- 含める依存ライブラリをフィルタリング -->
<!--
<includes>
<include>groupId:artifactId:type:version</include>
<include>com.example:my-utility-lib</include> // groupId:artifactId の形式も可能
</includes>
<excludes>
<exclude>com.google.guava:guava</exclude> // 例:Guavaを除外する
</excludes>
-->
</dependencySet>
</dependencySets>
...
</assembly>

<dependencySets> および <dependencySet> 要素の詳細:

  • <dependencySets>: 複数の <dependencySet> 定義を含むための親要素です。
  • <dependencySet>: 単一の依存ライブラリセット定義です。
    • <outputDirectory>: 依存ライブラリをコピー先のアーカイブ内のディレクトリを指定します。例では lib ディレクトリに配置します。
    • <scope>: 含める依存ライブラリのスコープを指定します。compile, runtime, provided, test, system, import などが指定できます。runtime を指定すると、compile スコープと runtime スコープの依存ライブラリが含まれます。これは、アプリケーションを実行するために必要なライブラリをすべて含める場合に便利です。省略した場合、デフォルトではすべての依存ライブラリが含まれます。
    • <useProjectArtifact>: true にすると、アセンブリ対象のプロジェクト自身の成果物(デフォルトではJAR)もこの <dependencySet> のルールに従って扱われます。false にすると、プロジェクト成果物はここでは扱われず、別の方法(例えば <fileset> や後述の <moduleSets>)で含める必要があります。通常、プロジェクト本体のJARは依存ライブラリとは別に扱いたいので false にすることが多いです。
    • <unpack>: true にすると、依存ライブラリのJARファイルを展開し、その中身を <outputDirectory> にコピーします。false にすると、JARファイルのままコピーします。jar-with-dependencies ディスクリプタは内部的にこれを true にして、すべての依存ライブラリの中身をルートに展開しています。配布用アーカイブでは、依存ライブラリをJARファイルのまま lib ディレクトリに置くことが多いので、false を指定するのが一般的です。
    • <includes>, <excludes>: 特定の依存ライブラリだけを含めたり除外したりするためのフィルタリング設定です。<include><exclude> 要素の中に groupId:artifactId:type:classifier:version の形式で依存ライブラリを指定します。一部を省略した形式(例: groupId:artifactId)も可能です。

上記の distribution.xmlpom.xml の設定で mvn clean package を実行し、生成されたZIPファイルを解凍すると、先ほどのスクリプト/設定ファイルに加えて、lib ディレクトリの中に依存ライブラリのJARファイルが格納された構造になります。

my-awesome-app-1.0.0-SNAPSHOT-custom/
├── bin/
│ └── startup.sh
├── config/
│ └── application.properties
├── lib/
│ ├── guava-31.1-jre.jar
│ ├── commons-io-2.11.0.jar
│ └── ... (その他の依存ライブラリ)
└── README.md

これで、アプリケーションの配布に必要な要素(アプリケーション本体のJARはまだ含めていませんが、それは後述)をまとめてアーカイブに含める基本的な方法が分かりました。

4. <moduleSets>:マルチモジュールプロジェクトで他のモジュールの成果物を含める

もしプロジェクトが複数のMavenモジュールに分かれている場合、メインアプリケーションの配布物に、他のモジュールで生成されたJARファイルを含めたいことがあります。<moduleSets> はこのような場合に使用します。

例えば、my-awesome-app が親プロジェクトで、その下に core モジュール(JARを生成)と cli モジュール(この配布物を作成するモジュール)があるような構造を考えます。cli モジュールの配布物に、core モジュールのJARを含めたいとします。

cli モジュールの src/main/assembly/distribution.xml に、以下の <moduleSets> 要素を追加します。

xml
<assembly ...>
...
<moduleSets>
<moduleSet>
<!-- 含めるモジュールを指定 -->
<includes>
<include>com.example:core</include> <!-- core モジュールを含める -->
</includes>
<!-- モジュールの成果物を含めるか (binaries=JAR, sources=ソースJAR, testSources=テストソースJAR) -->
<binaries>
<!-- コピー先のアーカイブ内のディレクトリ -->
<outputDirectory>lib</outputDirectory>
<!-- モジュールの成果物(ここでは core モジュールのJAR)を展開して含めるか -->
<unpack>false</unpack>
<!-- モジュールの成果物をフィルタリング -->
<!-- <includes>, <excludes> (アーティファクト指定の形式) -->
</binaries>
<!-- ソースJARを含めたい場合は sources 要素を記述 -->
<!--
<sources>
<outputDirectory>src</outputDirectory>
</sources>
-->
</moduleSet>
</moduleSets>
...
</assembly>

<moduleSets> および <moduleSet> 要素の詳細:

  • <moduleSets>: 複数の <moduleSet> 定義を含むための親要素です。
  • <moduleSet>: 単一のモジュールセット定義です。
    • <includes>, <excludes>: 含めたり除外したりしたいモジュールを、groupId:artifactId の形式で指定します。省略した場合、すべてのモジュールが対象になります。
    • <binaries>: モジュールの成果物(デフォルトではJARやWARなど、パッケージングされたもの)をどのように含めるかを定義します。
      • <outputDirectory>: 成果物をコピーするアーカイブ内のディレクトリです。
      • <unpack>: true にすると成果物を展開して含めます。false にするとそのままの形で含めます。モジュールのJARをそのまま含めたい場合は false です。
      • <includes>, <excludes>: <dependencySet> と同様に、モジュールの成果物自体をフィルタリングできます。
    • <sources>: モジュールのソースコード(通常は artifactId-version-sources.jar)を含める場合に定義します。
    • <testSources>: モジュールのテストソースコード(通常は artifactId-version-test-sources.jar)を含める場合に定義します。

この設定を使用すると、cli モジュールの配布アーカイブに、core モジュールのJARファイルが lib ディレクトリにコピーされて含まれるようになります。

プロジェクト本体のJARを含める:

さて、ここまでのカスタムディスクリプタの例では、設定ファイル、スクリプト、依存ライブラリは含めましたが、プロジェクト本体(今回の例では my-awesome-app プロジェクト自身)で生成されたJARファイル を含めていませんでした。

プロジェクト本体のJARを含める方法はいくつかあります。

  1. <filesets> を使う: target ディレクトリにあるプロジェクト本体のJARファイルを <fileset> で指定して含める。

    xml
    <filesets>
    <fileset>
    <directory>${project.build.directory}</directory> <!-- target ディレクトリ -->
    <outputDirectory>/</outputDirectory> <!-- アーカイブのルート -->
    <includes>
    <include>${project.artifactId}-${project.version}.jar</include> <!-- プロジェクト本体のJAR -->
    </includes>
    <fileMode>0644</fileMode>
    </fileset>
    ... (他の filesets) ...
    </filesets>

    ${project.build.directory}target ディレクトリを指すMavenプロパティです。${project.artifactId}${project.version} はプロジェクトのアーティファクトIDとバージョンです。
    この方法でルートに本体JARを置き、lib に依存ライブラリを置くことで、実行可能JARではありませんが、依存ライブラリを別途クラスパスに追加して実行する形式の配布物を作成できます。

  2. <dependencySets><useProjectArtifact>true を使う: <dependencySet>useProjectArtifacttrue に設定し、プロジェクト本体を依存ライブラリと同様に扱って含める。ただし、これは他の依存ライブラリと同じ場所に配置されるため、あまり一般的ではありません。

  3. <moduleSets> を使う(マルチモジュールの場合): マルチモジュールプロジェクトの場合、配布物を作成するモジュールから見て、自分自身も一つの「モジュール成果物」として扱いたい場合は、<moduleSet> を使用できます。配布物を作成するモジュールの <moduleSet>includes に自分自身を指定し、<binaries> で出力先を指定します。

    xml
    <moduleSets>
    <moduleSet>
    <!-- 自分自身(このアセンブリを作成しているモジュール)を含める -->
    <includes>
    <include>${project.groupId}:${project.artifactId}</include>
    </includes>
    <binaries>
    <outputDirectory>/</outputDirectory> <!-- アーカイブのルートに配置 -->
    <unpack>false</unpack>
    </binaries>
    </moduleSet>
    ... (他の moduleSets や dependencySets) ...
    </moduleSets>

    この方法も、マルチモジュール構成で配布物を作成する場合に便利です。

最も一般的なのは、単一モジュールの場合は <filesets>target ディレクトリにあるJARファイルを指定する方法、マルチモジュールの場合は <moduleSets> で自分自身を含める方法です。

ここでは、単一モジュールの場合を想定し、<filesets> を使ってプロジェクト本体のJARファイルをアーカイブのルートに含めるように distribution.xml を修正してみましょう。

“`xml

<id>custom</id>
<formats>
    <format>zip</format>
    <format>tar.gz</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>

<filesets>
    <!-- プロジェクト本体のJARファイルを含める -->
    <fileset>
        <directory>${project.build.directory}</directory>
        <outputDirectory>/</outputDirectory>
        <includes>
            <include>${project.artifactId}-${project.version}.jar</include>
        </includes>
        <fileMode>0644</fileMode>
    </fileset>

    <!-- スクリプトファイルを含める -->
    <fileset>
        <directory>${project.basedir}/src/scripts</directory>
        <outputDirectory>bin</outputDirectory>
        <includes>
            <include>*.sh</include>
            <include>*.bat</include>
        </includes>
        <fileMode>0755</fileMode>
    </fileset>

    <!-- 設定ファイルを含める -->
    <fileset>
        <directory>${project.basedir}/src/config</directory>
        <outputDirectory>config</outputDirectory>
        <includes>
            <include>*.properties</include>
            <include>*.xml</include>
        </include>
        <fileMode>0644</fileMode>
    </fileset>

    <!-- プロジェクトルートにあるファイルを含める (READMEなど) -->
    <fileset>
        <directory>${project.basedir}</directory>
        <outputDirectory>/</outputDirectory>
        <includes>
            <include>README*</include>
            <include>LICENSE*</include>
        </includes>
        <fileMode>0644</fileMode>
        <excludes>
             <exclude>pom.xml</exclude>
             <exclude>target/</exclude>
        </excludes>
    </fileset>
</filesets>

<dependencySets>
    <dependencySet>
        <outputDirectory>lib</outputDirectory>
        <scope>runtime</scope>
        <useProjectArtifact>false</useProjectArtifact>
        <unpack>false</unpack>
    </dependencySet>
</dependencySets>


“`

この設定でビルドすると、アーカイブの構造は以下のようになります。

my-awesome-app-1.0.0-SNAPSHOT-custom/
├── bin/
│ └── startup.sh
├── config/
│ └── application.properties
├── lib/
│ ├── guava-31.1-jre.jar
│ ├── commons-io-2.11.0.jar
│ └── ... (その他の依存ライブラリ)
└── my-awesome-app-1.0.0-SNAPSHOT.jar <-- プロジェクト本体のJAR
└── README.md

この構造は、Javaアプリケーションの配布物として非常によく見られる形です。例えば、起動スクリプト (startup.sh) の中で、以下のようにしてアプリケーション本体のJARと依存ライブラリをクラスパスに追加してJavaを実行する、といった使い方ができます。

“`bash

!/bin/bash

APP_HOME=$(dirname “$0”)/.. # スクリプトの親ディレクトリの親ディレクトリ(例: bin/../ -> .)
APP_JAR=$APP_HOME/my-awesome-app-1.0.0-SNAPSHOT.jar
LIB_DIR=$APP_HOME/lib

クラスパスを構築 (本体JARと lib/*)

CLASSPATH=”$APP_JAR”
for jarfile in “$LIB_DIR”/*.jar; do
CLASSPATH=”$CLASSPATH:$jarfile”
done

アプリケーションを実行

java -cp “$CLASSPATH” com.example.myapp.MainApp “$@” # main メソッドがあるクラスを指定
“`
※ 上記スクリプトはあくまで概念的な例です。実際のスクリプトはOSや詳細な要件に合わせて調整が必要です。

5. <repositorySets>:リポジトリからの取得

<repositorySets> は、Mavenリポジトリ(ローカルまたはリモート)から直接アーティファクトを取得してアーカイブに含めるための要素です。これはあまり一般的ではありませんが、特定の外部アーティファクトを配布物に含めたい場合などに利用できます。

xml
<assembly ...>
...
<repositorySets>
<repositorySet>
<outputDirectory>external-libs</outputDirectory>
<includes>
<include>org.some.library:external-jar:jar:1.0</include>
</includes>
<!-- リポジトリのIDを指定 (settings.xml に定義されたもの) -->
<!-- <repositoryId>my-nexus-repo</repositoryId> -->
</repositorySet>
</repositorySets>
...
</assembly>

<repositorySet> 要素の詳細:

  • <outputDirectory>: 取得したアーティファクトを配置するアーカイブ内のディレクトリです。
  • <includes>, <excludes>: 取得したいアーティファクトを groupId:artifactId:type:classifier:version の形式で指定します。
  • <repositoryId>: 使用するリポジトリのIDを指定します。指定しない場合、すべての設定済みリポジトリが検索されます。

Assembly Plugin の詳細設定と高度な利用法

カスタムディスクリプタの要素以外にも、Assembly Plugin には様々な設定オプションがあります。

1. <appendAssemblyId> (再掲)

これは <configuration> の直下に記述する設定ですが、重要なので改めて。

  • <appendAssemblyId>true</appendAssemblyId>: 生成ファイル名に <id> を付与する (artifactId-version-id.format)。
  • <appendAssemblyId>false</appendAssemblyId>: 生成ファイル名に <id> を付与しない (artifactId-version.format)。デフォルトは true です。
    複数のアセンブリを生成する場合は true にしておかないとファイル名が衝突するので注意が必要です。

2. <includeBaseDirectory> (再掲)

これもディスクリプタのルート要素直下で定義する設定です。

  • <includeBaseDirectory>true</includeBaseDirectory>: アーカイブのルートに artifactId-version (または <baseDirectory> で指定した名前) のディレクトリを作成し、その中に全てを格納します。
  • <includeBaseDirectory>false</includeBaseDirectory>: アーカイブを解凍した際に、内容が直接カレントディレクトリに展開されます。デフォルトは true です。

3. <formats> (再掲)

これもディスクリプタのルート要素直下で定義します。

  • <formats>: 生成するアーカイブ形式を指定します。<format> 要素の中に zip, tar.gz, tar.bz2, jar, dir などを記述します。複数の <format> を指定すると、複数のアーカイブ形式で出力されます。
    xml
    <formats>
    <format>zip</format>
    <format>tar.gz</format>
    </formats>

4. <archive>:JAR固有の設定

カスタムディスクリプタの <assembly> 要素直下に <archive> 要素を記述すると、format として jar を指定した場合に、そのJARファイルに関する詳細な設定が可能です。特に、実行可能JARに必要な MANIFEST.MF の設定を行うことができます。

“`xml

executable-jar
jar
false

<!-- JAR固有の設定 -->
<archive>
    <manifest>
        <!-- Main-Class の指定 -->
        <mainClass>com.example.myapp.MainApp</mainClass>
        <!-- Class-Path の指定 (依存ライブラリが別途用意されている場合) -->
        <!-- <addClasspath>true</addClasspath> -->
        <!-- <classpathPrefix>lib/</classpathPrefix> -->
    </manifest>
    <!--
    <manifestEntries>
        <Build-Time>${maven.build.timestamp}</Build-Time>
        <Built-By>${user.name}</Built-By>
    </manifestEntries>
    -->
</archive>

<!-- jar-with-dependencies と同様に依存ライブラリを含める -->
<dependencySets>
    <dependencySet>
        <outputDirectory>/</outputDirectory> <!-- ルートに展開 -->
        <scope>runtime</scope>
        <useProjectArtifact>true</useProjectArtifact> <!-- プロジェクト本体も含める -->
        <unpack>true</unpack> <!-- 依存ライブラリをすべて展開 -->
    </dependencySet>
</dependencySets>

<!-- プロジェクトのリソースファイルなども含めたい場合は filesets を追加 -->
<!--
<filesets>
    <fileset>
        <directory>${project.build.directory}/classes</directory>
        <outputDirectory>/</outputDirectory>
        <includes>
            <include>**/*</include>
        </includes>
    </fileset>
</filesets>
-->


“`

この例は、jar-with-dependencies とほぼ同じ機能を持つカスタムディスクリプタの定義です。

  • <archive> -> <manifest>: JARの META-INF/MANIFEST.MF ファイルの設定です。
    • <mainClass>: 実行可能JARのエントリーポイントとなるクラスを指定します。
    • <addClasspath>: true にすると、依存ライブラリへのクラスパスがMANIFEST.MFに自動的に追記されます。ただし、これは依存ライブラリがJARファイルの外にある場合に有効です。
    • <classpathPrefix>: <addClasspath>true の場合に、クラスパスのエントリに付与するプレフィックスです。
  • <archive> -> <manifestEntries>: マニフェストファイルにカスタムエントリを追加できます。ビルド時刻やビルドユーザーなどを記録する場合に便利です。

この方法でカスタムディスクリプタを使って実行可能JARを作成することも可能ですが、依存性の衝突解決などの高度な機能が必要なければ、手軽な jar-with-dependencies ディスクリプタを使うか、あるいは maven-shade-plugin の方がよりJAR作成に特化しており多機能な場合があります。Assembly Pluginのカスタムディスクリプタは、JARとその他のファイルを組み合わせた複雑なアーカイブを作成する際に最も威力を発揮します。

5. <executions> を使用した複数アセンブリの生成

一つのプロジェクトで、複数の異なる形式や構造のアセンブリを作成したい場合があります。例えば、実行可能JARと配布用ZIPアーカイブの両方を作成したい、といった場合です。

pom.xml の Assembly Plugin 設定の <executions> セクションの中に、複数の <execution> 要素を定義することで、これを実現できます。

“`xml org.apache.maven.plugins
maven-assembly-plugin
3.5.0



make-executable-jar package
single




jar-with-dependencies

true


    <!-- 配布用ZIP/Tar.gzを作成する execution -->
    <execution>
        <id>make-distribution-archive</id>
        <phase>package</phase>
        <goals>
            <goal>single</goal>
        </goals>
        <configuration>
            <!-- 配布用アーカイブ用の設定 -->
            <descriptors>
                <descriptor>src/main/assembly/distribution.xml</descriptor>
            </descriptors>
            <appendAssemblyId>true</appendAssemblyId>
        </configuration>
    </execution>
</executions>

“`

このように、<executions> の中に異なる <id> を持つ <execution> を複数定義し、それぞれに異なる <configuration>(例えば異なる <descriptorRefs><descriptors>)を設定することで、mvn clean package コマンド一回で複数のアセンブリを同時に作成できます。

6. <ignoreDirFormatExtensions>

<configuration><ignoreDirFormatExtensions>true</ignoreDirFormatExtensions> を設定すると、formatdir を指定した場合に、生成されるディレクトリ名の末尾に拡張子(.dir など)が付かなくなります。デフォルトは false で、ディレクトリ名の末尾に .dir が付与されます。

7. プロパティの利用

ディスクリプタファイル (distribution.xml) の中では、Mavenプロパティ(例: ${project.version}, ${project.build.directory}, ${project.basedir}, ${user.name} など)を利用できます。これにより、ビルドごとに変わる値や環境に依存する値を動的に設定ファイルに含めることができます。

例えば、設定ファイルの内容をカスタマイズしてアーカイブに含めたい場合、src/main/resources にフィルタリング可能な設定ファイルを用意し、それを <filesets> でコピーする際に Assembly Plugin のフィルタリング機能を使うか、あるいは別のプラグイン(例: maven-resources-plugin)と組み合わせて利用します。

Assembly Plugin自体もリソースフィルタリング機能を持っています。ディスクリプタの <fileset><file> の中に <filtering>true</filtering> を追加することで、Mavenのプロパティを使ってファイルをフィルタリングしながらコピーできます。

例: src/main/config/application.propertiesapp.version=${project.version} という行がある場合。

xml
<fileset>
<directory>${project.basedir}/src/main/config</directory>
<outputDirectory>config</outputDirectory>
<includes>
<include>*.properties</include>
</includes>
<filtering>true</filtering> <!-- ここでフィルタリングを有効化 -->
</fileset>

これにより、コピーされる application.properties ファイル中の ${project.version} が、ビルド実行時のプロジェクトバージョンに置き換えられます。

一般的な問題とトラブルシューティング

Assembly Plugin を使っていると、意図した通りのアーカイブができない場合があります。ここでは、よくある問題とその解決方法をいくつか紹介します。

  1. 生成されたアーカイブの中身が期待と違う:

    • ディスクリプタファイルのパスが間違っている: pom.xml<descriptors> で指定したパスが正しいか確認してください。プロジェクトルートからの相対パスです。
    • <fileset>, <files>, <dependencySet> などの設定ミス: directory, outputDirectory, includes, excludes, scope などの設定が意図通りになっているか、XMLディスクリプタをよく確認してください。パスの区切り文字(/)やワイルドカードの使い方(*, **)が正しいかチェックしましょう。
    • Mavenビルドのログを確認する: mvn clean package -X のように -X オプションをつけてビルドを実行すると、詳細なデバッグログが表示されます。Assembly Pluginがどのファイルを収集しようとして、どこに配置しようとしているかなど、詳しい情報が得られます。
    • 効果的なPOMを確認する: mvn help:effective-pom を実行し、Assembly Pluginの設定がどのように解釈されているかを確認してください。親POMやプロファイルなどの影響で、想定外の設定になっている可能性があります。
  2. 実行可能JARが実行できない (java -jar ...):

    • Main-Class が正しく設定されていない: MANIFEST.MF ファイルに Main-Class 属性が正しく記載されているか確認してください。カスタムディスクリプタを使っている場合は <archive><manifest><mainClass> の設定、jar-with-dependencies を使っている場合は maven-jar-plugin の設定が正しいか確認してください。クラス名のスペルミスやパッケージ名の指定ミスがないかも重要です。
    • 依存ライブラリが見つからない: jar-with-dependencies を使っている場合は、すべての依存ライブラリがJARに同梱されているか確認してください。カスタムディスクリプタでJAR形式を作成している場合は、<dependencySets> で依存ライブラリが正しく含まれているか、<unpack>true</unpack> となっているか確認してください。もし依存ライブラリをJARの外に配置し、Class-Path を指定して実行可能JARを作成しようとしている場合は、MANIFEST.MFClass-Path 属性が正しく、かつそのパスに実際に依存ライブラリが存在するか確認が必要です。
    • 依存ライブラリの衝突: 複数のライブラリに含まれる同じクラスが原因でエラーになっている可能性があります。maven-dependency-plugin:tree ゴールを使って依存関係ツリーを確認し、衝突しているライブラリがないか調べます。衝突がある場合は、maven-shade-plugin の利用を検討するか、Assembly Pluginの <excludes> 設定で不要なクラスを含むライブラリを除外するなどの対応が必要です。
  3. ファイル権限が正しく設定されない (Tar.gz など):

    • カスタムディスクリプタの <fileset><file><fileMode><directoryMode> が正しく設定されているか確認してください。8進数表記 (例: 0755) で指定する必要があります。
  4. ビルド時に「[WARNING] Assembly file: … is not a file. It is a directory. Ignoring.」のような警告が出る**:

    • これは <descriptors> 要素の中にディレクトリを指定している場合に表示される警告です。<descriptor> 要素にはXMLファイルへのパスを指定する必要があります。

他のMavenプラグインとの比較 (再掲・詳細化)

Assembly Pluginの立ち位置をより明確にするために、関連する他のプラグインとの比較を再度詳細に行います。

  • maven-jar-plugin:
    • 目的: プロジェクトの主要な成果物であるJARファイルを作成すること。ソースコードやリソースをコンパイル・コピーしてJARにまとめる。
    • 依存関係: デフォルトでは依存ライブラリは含まないMANIFEST.MFClass-Path を追記することは可能だが、別途依存ライブラリを配置する必要がある。
    • アーカイブ構造: 単一のJARファイルのみを作成。
    • 適しているケース: ライブラリJARを作成する場合、または依存ライブラリを別途管理するアプリケーションの本体JARを作成する場合。
  • maven-dependency-plugin:
    • 目的: プロジェクトの依存ライブラリを操作すること。例えば、指定したディレクトリにコピーしたり、依存関係ツリーを表示したり。
    • 依存関係: 依存ライブラリをそのままのJARファイルとして指定したディレクトリにコピーできる (copy-dependencies ゴール)。
    • アーカイブ構造: アーカイブ(ZIP/Tar.gzなど)を作成する機能は持たない。ファイルをコピーするだけ。
    • 適しているケース: 依存ライブラリをまとめて特定のディレクトリに配置したい場合。Assembly Pluginの <dependencySets> と似た機能だが、コピーするだけでパッケージングはしない点が異なる。Assembly Pluginのカスタムディスクリプタで <dependencySets> を使う方が、他のファイル(本体JAR, スクリプトなど)と一緒にまとめてアーカイブ化できるため便利。
  • maven-shade-plugin:
    • 目的: Uber JAR (Fat JAR) を作成すること。プロジェクトのクラスとすべての依存ライブラリのクラスを一つのJARファイルにまとめる。依存性の衝突解決(Shading, Relocation)に強い。
    • 依存関係: すべての依存ライブラリの中身を一つのJARファイルに同梱する。依存ライブラリに含まれる同じ名前のファイルを適切に処理するための競合解決(Transformer)機能が豊富。
    • アーカイブ構造: 単一のJARファイルのみを作成。
    • 適しているケース: 依存ライブラリが多く、依存性の衝突が懸念される実行可能JARを作成したい場合。Assembly Pluginの jar-with-dependencies よりも高度な機能を持つ。ただし、設定ファイルやスクリプトなどを同梱したい場合は、Shade Pluginで実行可能JARを作成し、Assembly PluginでそのJARを他のファイルとともに配布用アーカイブにまとめる、というように組み合わせて使うことも可能。
  • maven-assembly-plugin:
    • 目的: プロジェクトの成果物、依存ライブラリ、その他のファイルを収集し、カスタマイズ可能な構造を持つアーカイブ(JAR, ZIP, Tar.gzなど)を作成する。
    • 依存関係: <dependencySets> を使って依存ライブラリを同梱できる。JARに同梱する場合は展開 (unpack>true) も、ディレクトリに配置する場合はそのまま (unpack>false) も可能。依存性の衝突解決機能は基本的に持たない。
    • アーカイブ構造: カスタムディスクリプタにより、非常に柔軟なディレクトリ構造を持つアーカイブを作成可能。実行可能JARも作成できるが、その柔軟性は他の形式(ZIP, Tar.gz)作成時に最も発揮される。
    • 適しているケース:
      • jar-with-dependencies のような簡単な実行可能JARを作成したい場合(手軽さ)。
      • プロジェクト本体JAR、依存ライブラリ群(lib/)、設定ファイル(config/)、起動スクリプト(bin/)、READMEなどをまとめて配布したい場合(配布用アーカイブ)。
      • アーカイブ内のファイル構成やファイル権限を細かく制御したい場合。

まとめると:

  • シンプルな実行可能JARで、依存性衝突が少なそう → jar-with-dependencies (Assembly Plugin) または maven-shade-plugin (より高機能)
  • 本体JARと依存JARを分けて配布したい、設定ファイルやスクリプトも一緒に配布したい → カスタムディスクリプタ (Assembly Plugin)
  • 依存ライブラリをただコピーしたいだけ → maven-dependency-plugin:copy-dependencies

このように、Assembly Pluginは「アーカイブの構造を柔軟に作り込みたい」という要件に対して最も強力なツールです。

まとめ

この記事では、Maven Assembly Plugin を使って、Javaアプリケーションの配布可能な形式である実行可能JARやZIP/Tar.gzアーカイブを作成する方法について、初心者向けに詳しく解説しました。

  1. はじめに、なぜ配布可能な形式が必要なのか、そしてAssembly Pluginの立ち位置を説明しました。
  2. 基本的な使い方として、定義済みディスクリプタ jar-with-dependencies を使って、依存ライブラリを同梱した実行可能JARを簡単に作成する手順を示しました。maven-jar-plugin と連携して Main-Class を設定する重要性も説明しました。
  3. より柔軟な配布物を作成するために、配布用アーカイブの作成方法として bin ディスクリプタを簡単に紹介し、さらに強力なカスタムXMLディスクリプタの導入方法を解説しました。
  4. カスタムディスクリプタにおいて、アーカイブに含める要素を定義するための主要な要素である <filesets>, <files>, <dependencySets>, <moduleSets>, <repositorySets> について、それぞれの目的、使い方、主要な子要素や属性を具体的なXMLの例を交えながら詳細に説明しました。特に、プロジェクト内の任意ファイルを含める <filesets>、依存ライブラリを含める <dependencySets>、そしてプロジェクト本体のJARを含める方法を中心に解説しました。
  5. Assembly Plugin の 詳細設定 として、<appendAssemblyId>, <includeBaseDirectory>, <formats> といった全体的な設定、そしてJAR形式に特化した <archive> 要素による MANIFEST.MF のカスタマイズ方法を説明しました。また、<executions> を使って複数のアセンブリを一度に作成する方法や、プロパティの利用についても触れました。
  6. ビルド時に発生しうる 一般的な問題とトラブルシューティング の方法(ログ確認、POM確認、ディスクリプタ確認など)を解説しました。
  7. 最後に、他の関連プラグイン (maven-jar-plugin, maven-dependency-plugin, maven-shade-plugin) と Assembly Plugin の違いを再確認し、それぞれのプラグインがどのような場合に適しているかをまとめました。

Assembly Pluginは機能が豊富であるため、最初は少し難しく感じるかもしれません。しかし、まずは jar-with-dependenciesbin のような定義済みディスクリプタから使い始めてみて、慣れてきたらカスタムディスクリプタに挑戦し、必要な要素定義(<filesets>, <dependencySets> など)を少しずつ追加していくのが良いでしょう。

この記事が、皆さんのMavenプロジェクトでアプリケーションの配布物を作成する一助となれば幸いです。さらに詳しく知りたい場合は、Maven Assembly Pluginの公式ドキュメントを参照することをおすすめします。

さあ、Assembly Plugin を活用して、あなたのアプリケーションを世界に配布しましょう!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール