Maven でマルチモジュール管理をする


monorepo 構成で1つのリポジトリで複数のプロジェクトを管理する場合、ビルドをまとめて実行したり、設定を共通化して管理を簡単にしたくなると思います。そういった場合は pom.xml ファイルを書き替えて複数のプロジェクトを関連付けることができます。

Maven プロジェクト構成の考え方

まず、1つの pom.xml で管理するファイル群についてパッケージやモジュールなどいろいろな呼び方があると思いますが、ここではプロジェクトと呼ぶことにします。

Maven で複数のプロジェクトを紐づけるための仕組みとして、以下の2つの概念を理解する必要があります。

  • 集約・マルチモジュール (modules 要素)
  • 継承 (parent 要素)

modules (マルチモジュール)

複数のモジュールを管理したいという目的があるので、最初に modules 要素に目が行くと思います。これは何をする物かというと、mvn コマンドを実行してこの pom.xml を読み込む場合は、このフォルダにある pom.xml も参照してくださいという指示になります。

pom.xml を見てくださいという設定なので、実行順序は依存関係によって入れ替えられます。また設定は継承されません。あくまで一緒にビルドしてくださいという設定になります。

<?xml version="1.0" encoding="UTF-8"?>
<!-- projects/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- ... -->

  <packaging>pom</packaging>
  <modules>
    <module>a</module>
    <module>b</module>
    <module>c</module>
  </modules>

  <!-- ... -->
</project>

parent (継承)

pom.xml の設定値、例えば properties や dependencies, plugins などの継承は parent を使って指定可能です。

<?xml version="1.0" encoding="UTF-8"?>
<!-- projects/a/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.zu_min.mvn_modules</groupId>
    <artifactId>module-b</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <relativePath>../b</relativePath>
  </parent>
  <groupId>com.zu_min.mvn_modules</groupId>
  <artifactId>module-a</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <!-- ... -->
</project>

parent には親プロジェクトへの参照 (groupId, artifactId, version) と、 pom.xml へのパス relativePath を記述します。

relativePath は省略可能です。省略した場合は以下を探索します。

  • リモートリポジトリ
  • ローカルリポジトリ (~/.m2/repository)
  • 親ディレクトリ (../)

継承する内容

parent を指定すると、指定したプロジェクトの各種設定を継承します。継承する設定は以下の通りです(Maven – POM Reference より)。

  • groupId
  • version
  • description
  • url
  • inceptionYear
  • organization
  • licenses
  • developers
  • contributors
  • mailingLists
  • scm
  • issueManagement
  • ciManagement
  • properties
  • dependencyManagement
  • dependencies
  • repositories
  • pluginRepositories
  • build
    • <id> が一致する plugin executions
    • plugin configuration
    • その他
  • reporting
  • profiles

以下のものは継承されません

  • artifactId
  • name
  • prerequisites

※ dependencyManagement を継承する仕組みとして、 parent 以外に BOM プロジェクトを使う方法もあります。

ビルド順

モジュールのビルド順は、「ビルドに含まれる」モジュール内 、つまり 「modules で定義されている」ものの中で以下を考慮して決定されます ( Maven – Guide to Working with Multiple Modules in Maven 4 より)。

  • 依存関係 (dependency)
  • プラグイン (plugin)
  • プラグインの依存関係 (plugin の dependency)
  • ビルド拡張宣言 (build の extension)
  • modules の定義順序(上記に該当しない場合)

ドキュメントには書かれていませんが parent も考慮されている動きをします。具体的な仕様は不明です。

対して dependencyManagement と pluginManagement は順序に影響しないようです。

また「ビルドに含まれる」パッケージというのは modules で指定されたパッケージのみです。そのため、 dependency にプロジェクトを指定していても modules に入っていない場合は、 dependency に指定したプロジェクトは「ビルド対象外」になります。

実行例

下記のようなディレクトリ構造で実行例を考えてみます。

  • projects/
    • pom.xml
    • a/
      • pom.xml
    • b/
      • pom.xml
    • c/
      • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- projects/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.zu_min.mvn_modules</groupId>
  <artifactId>root-module</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <modules>
    <module>a</module>
    <module>b</module>
    <module>c</module>
  </modules>

  <!-- ... -->
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!-- projects/a/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.zu_min.mvn_modules</groupId>
    <artifactId>module-b</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <relativePath>../b</relativePath>
  </parent>
  <groupId>com.zu_min.mvn_modules</groupId>
  <artifactId>module-a</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>com.zu_min.mvn_modules</groupId>
      <artifactId>module-c</artifactId>
      <version>1.0.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  <!-- ... -->
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!-- projects/b/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.zu_min.mvn_modules</groupId>
  <artifactId>module-b</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <!-- ... -->
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!-- projects/c/pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.zu_min.mvn_modules</groupId>
  <artifactId>module-c</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>jar</packaging>
</project>

上記の設定で実行すると、 modules の順番は

  1. a
  2. b
  3. c

の順で定義していますが、プロジェクト a が c に依存しているのと、 a の parent が b であることから c と b が先にビルドされます。

projects$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] module-c                                                           [jar]
[INFO] module-b                                                           [pom]
[INFO] module-a                                                           [jar]
[INFO] root-module                                                        [pom]

...

mvn コマンドに a/pom.xml を指定して実行すると、プロジェクト a の pom.xml には modules 要素がないため、 プロジェクト a のみがビルド対象となります。

そのため、ローカルリポジトリ (~/.m2/repository) が空の状態でプロジェクト a のみビルドしようとすると、 a が依存している c が存在しないためビルドエラーとなります。

projects$ mvn install -f ./a/pom.xml 
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< com.zu_min.mvn_modules:module-a >-------------------
[INFO] Building module-a 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[WARNING] The POM for com.zu_min.mvn_modules:module-c:jar:1.0.0-SNAPSHOT is missing, no dependency information available
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.099 s
[INFO] Finished at: 2022-09-04T13:52:29+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project module-a: Could not resolve dependencies for project com.zu_min.mvn_modules:module-a:jar:1.0.0-SNAPSHOT: Could not find artifact com.zu_min.mvn_modules:module-c:jar:1.0.0-SNAPSHOT -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException

parent (module-b) については何も言われないので、 parent に指定するプロジェクトは未ビルド、未インストールでも問題ないようです。

まとめ

maven には継承と集約という独立したプロジェクト管理の概念があり、両方理解し活用することで管理が楽になります。

使用したバージョン

  • Apache Maven 3.8.5
  • Java version: 11.0.16, vendor: Eclipse Adoptium

ソースは GitHub にもあります
https://github.com/zu-min-g/maven-multi-modules


コメントを残す

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

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください