たなかこういちの資料室

システム開発に携わる筆者があれこれ試したことや学んだことについてのまとめ

ローカル開発環境ではMulti-Module、CI環境では複数の単独プロジェクトとなるように工夫した、Multi-Profileなpom.xmlの説明

背景と目的
 
次のようなMavenプロジェクトの状況があるとします。
 
MavenプロジェクトA、Bがあり、AはBに依存しています。Aは一つのWebアプリ、Bは業務ロジック(ドメインモデル)のパッケージ、といったところです。
 
MavenプロジェクトA、Bは、保守観点からMavenプロジェクトとしては独立させていますが、実際の開発作業は二つのプロジェクトを同時に編集することが多いです。そこで、二つのプロジェクトの共通親となるsuper pom.xmlとそれを含む<packaging>が"pom"のMavenプロジェクトPを用意して、全体がMulti-ModuleなMavenプロジェクトとなるように構成したいと考えます。複数のMavenプロジェクトをMulti-Moduleに構成することで、被依存プロジェクトBを都度「mvn install」する必要は無くなり、全体を一発の「mvn package」でビルドすることができます。EclipseMavenプロジェクトを取り込むときも、Multi-Module構成であれば含まれる子プロジェクトを一括で取り込むことができます。
 
Apache Maven Project、"POM Reference - Aggregation (or Multi-Module)":
 
一方、これらMavenプロジェクトA、Bは、構成管理観点から、Git等のSCMにおいてはそれぞれ独立したリポジトリーに登録したいと考えます。加えてJenkins等のCI環境においても、SCMのリポジトリーと一対一対応で、ビルドジョブの構成を行いたいと考えます。この場合、Multi-Module構成だとかえって厄介で、単純に「依存関係のある複数の単独プロジェクト」として扱いたいと考えます。
 
要件をまとめると、「複数のMavenプロジェクトを、開発環境ではMulti-Module構成として、CI環境では単独プロジェクトとして扱いたい」という事になります。
 
結論
 
結論的には、Multi-ProfileMavenプロジェクトとして構成し、開発環境向けプロファイルでのみMulti-Moduleを有効化させることで、目的を達成できました。
 
Apache Maven Project、"POM Reference - Profiles":
 
super pomと各プロジェクトのpom.xml、記述のポイント
 
この節ではMulti-ProfileかつMulti-Moduleな構成のpom.xmlについて説明します。完成形の実物を下記に置いてあります。
 
※これらのサンプルプロジェクトの利用、改変、再配布などは一切自由です。
※サンプルプロジェクトは下記環境で動作確認しています。
 
- Mac OS X 10.10.5
- JDK 1.8.0
- Maven 3.3.3
 
"my-multi-module-web-app"(※以降、単に"web-app")は"my-multi-module-java-app"(※以降、単に"java-app")に依存しています。"my-multi-module-parent"(※以降、単に"parent")は親たるsuper pomを含むプロジェクトです。
 
以下、記述のポイントを何点か説明します。
 
"parent"のpom.xml(super pom)について
 
"parent"は親たるsuper pomを提供するMavenプロジェクトです。自身の<packaging>は"pom"となっています。
 
このpom.xmlには<profiles>要素があり、Multi-Profileな構成になっています。複数プロファイルの内、<id>が"dev"である<profile>要素の内部にのみMulti-Module構成の記述があります。
 
-----(ここから)-----
<modules>
    <module>../my-multi-module-web-app</module>
    <module>../my-multi-module-java-app</module>
</modules>
-----(ここまで)-----
 
デフォルトでアクティブになるプロファイル"dev"はローカル開発環境を指し、<id>が"ci"のプロファイルはJenkins等のCI環境を表しています。このように特定の<profile>要素内にのみ<modules>要素を記述することで、そのプロファイルでのみMulti-Module構成となるようにすることが実現できました。
 
 
他、super pomには、全ての子プロジェクト共通の設定として下記のような構成を含めています。
 
・ソースファイル類を格納するサブディレクトリを"src"ではなく"source"に変更している
 
<sourceDirectory>要素や<resource>要素等のパス設定を、まったくの私的な好みで、"src"ではなく"source"に変更しています。
 
・プロファイル固有のresourceファイル類で"main"や"test"のresourceファイル類を上書きするようにしている
 
<resources>要素および<testResources>要素下に、それぞれ二つの<resource>要素、<testResource>要素を記述しています。一つ目は(※サブディレクトリが"source"にリネームされている点を除いて)標準的なもの、二つ目は"${profile-id}"というpropertyを用いて参照されるサブのresourceファイル類です。「mvn」でビルドするとき、一つ目のresourceファイル類が"target/classes"下にコピーされた後に、二つ目のresourceファイル類がコピーされます。このとき同名のファイルは上書きされます。DBMS接続情報など、環境毎に異なる情報だけを特定の設定ファイルにまとめて、そのファイルだけをプロファイル毎の固有のサブディレクトリにそれぞれ格納することで、ビルド環境毎に適切な接続情報を参照させるといった構成を実現できます。
 
サンプルでは"dev"と"ci"しか記述していませんが、他に例えば"stg1"、"stg2"、"prd"などのプロファイルを構成することが考えられます。
 
"java-app"と"web-app"のpom.xmlについて
 
"java-app"の<packaging>は"jar"、"web-app"の<packaging>は"war"です。super pomを参照していることを表す<parent>要素があります。<relativePath>要素は"parent"のローカル環境上での所在を示します。
 
ひとつ留意すべき点があります。Multi-Moduleを構成する各子プロジェクトの側、ここでは"java-app"と"web-app"となりますが、Multi-Moduleを構成する子プロジェクトの側のpom.xmlに記述される<parent>要素は、各プロジェクトがsuper pomを参照していることを表しているだけで、(たとえ、参照しているsuper pomに<modules>要素があったとしても、)各プロジェクトがMulti-Moduleの参加者であるか否かを表していません
 
ローカル開発環境で、"parent"を配置したサブディレクトリに移動して「mvn package」でビルドしようとすると、Multi-Moduleを構成する配下の全子プロジェクトをビルドしようとします。対して、"web-app"のサブディレクトリに移動して「mvn package」すると、"web-app"だけをビルドしようとします。そして"java-app"を「mvn install」していないので、"java-app"の参照解決が失敗します。Multi-Module構成だとは見做されてないので、依存プロジェクトはMavenリポジトリー(※ローカル、またはリモート)に存在しなければなりません。
 
"parent"のサブディレクトリで「mvn package」する場合は、個々のプロジェクトが「mvn install」されていなくても、Multi-Moduleを構成するプロジェクト間の依存は解決されます。(※「mvn install」されていても、ローカルの兄弟位置のサブディレクトリに展開されているファイル類の方を参照します。)
 
ローカル開発環境で、Multi-Module構成として一括ビルドする手順について
 
これまで部分的に言及してきた、Mavenプロジェクトをプロファイル"dev"、即ちローカル開発環境で、Multi-Module構成として一括ビルドする手順を、サンプルプロジェクトを例にしてまとめます。
 
1. "my-multi-module-parent"、"my-multi-module-java-app"、"my-multi-module-web−app"の三つのプロジェクトを、“Workspace”とする一つのディレクトリー下に並べて配置します。
 
配置は、親たるsuper pomの<module>要素に記述した各子プロジェクトへの相対パスに一致するようにします。一般には、GitなどのSCMからチェックアウトする場合の都合との関係もあるので、(親プロジェクトの配置サブディレクトリ配下に一段下げて子プロジェクトを配置するような事にはせず、)サンプルのように親プロジェクトも含めて、全て兄弟位置に並べるのが、無難と云えます。
 
2. "my-multi-module-parent"を配置したサブディレクトリーへ移動します。常に親たるsuper pomのプロジェクトをカレントにしてコマンドを実行します。
 
3. 「mvn package」(などを)を実行します。全ての子プロジェクトについて順にビルドが実行されます。
 
4. 一部のプロジェクトのみビルドしたい場合は、下記のように「-pl」オプションで対象プロジェクトを指定します。この場合も、あくまで親たる"my-multi-module-parent"のサブディレクトリをカレントにして実行します。
 
-----(ここから)-----
$ mvn -pl my-multi-module-java-app package
-----(ここまで)-----
 
■ Jenkins等CI環境にて、非Multi-Module構成でビルドさせる手順について
 
pom.xmlに、"dev"、"ci"といった複数のプロファイルを構成し、特定のプロファイルの<profile>要素内にのみ<modules>要素を記述すれば、そのプロファイルがアクティブとなる環境においてのみ、Multi-Module構成が有効となります。他のプロファイルではMulti-Moduleとなりません。
 
サンプルプロジェクトでは、ローカル開発環境を表すプロファイル"dev"でのみMulti-Module構成となり、CI環境を表すプロファイル"ci"では非Multi-Moduleとなるように構成しています。
 
つまるところ、CI環境ではプロファイル"ci"を有効にしつつ「mvn」コマンドを実行することとなります。有効にするプロファイルの指定方法は、環境変数やmvnコマンドラインオプション指定などいくつかあります。詳しくはMavenのドキュメントを参照してみてください。
 
Apache Maven Project、"POM Reference - Profiles - Activation":
 
ここでは単にコマンドラインオプション「-P」を指定することとます。Jenkinsにジョブを構成するならば、「Build > Goals and options」欄に入力するコマンドラインオプションに「-P」オプションを明示します。
 
-----(ここから)-----
-P ci clean install
-----(ここまで)-----
 
JenkinsジョブはSCMのリポジトリーと一対一対応させるということで、実際には"my-multi-module-parent"、"my-multi-module-java-app"、"my-multi-module-web−app"の三つのプロジェクトそれぞれに対応して、ジョブも三つ作成することとなります。各ジョブの「Build > Goals and options」欄にそれぞれ入力することとなります。
 
 
<追記>
ただ、この記事や関連記事を書いた後の今時点では、本記事冒頭の「背景と目的」に示したような状況のとき、各プロジェクトをSCMの独立リポジトリーに格納するスタイルが本当に妥当なのか、若干疑問を持っています。リポジトリーを分けるということは、リリース単位として分離したいという意図の現れだと思います。Multi-Module構成にしたいような複数プロジェクトを、一方で単独にリリースできる運用が有用なのだという場面が、ほんとうにあるのでしょうか。もう少し抽象的に云えば、開発はほぼ同期的に行われるが、リリースは非同期的に行われる事がよくある、といったケースは、想定すべきものなのか、疑問がある、ということです。
 
◆以上