TL;DR
WildFly/EAPのGalleonプロビジョニングを含むMavenビルドにおいて、CIのローカルリポジトリパスをデフォルト(${user.home}/.m2/repository)から変更すると、Galleonプロビジョニングが XMLPullParserException で失敗する場合がある。原因はGalleonのconfig生成フェーズで使われるJBoss Modulesの MavenResolver が、Mavenのsettings.xmlや -Dmaven.repo.local を一切参照せず、独自のシステムプロパティのみに依存しているため。
環境
| 項目 | OpenShift(成功) | GitLab CI(失敗) |
|---|---|---|
| ベースイメージ | ubi8-openjdk-17 | ubi9-minimal + OpenJDK 17 |
| Maven | 3.8系 | 3.9系 |
| Java | 17 | 17 |
| Galleonプラグイン | 同一バージョン | 同一バージョン |
| settings.xml | 同一 | 同一 |
| Nexus | 同一 | 同一 |
| ローカルリポジトリ |
${user.home}/.m2/repository(デフォルト) |
/builds/.../.m2/repository(カスタム) |
事象
GitLab CIでGalleonプロビジョニングを含むMavenビルドを実行すると、以下のエラーチェーンでビルドが失敗する。
org.jboss.modules.xml.XmlPullParserException:
Failed to resolve artifact 'org.jboss:jboss-vfs:3.3.0.Final-redhat-00001'
(position: END_TAG seen ...@30:72)
→ could not load module org.jboss.vfs
→ error loading module
→ failed to resolve artifact
→ provisioning failed
OpenShiftでは同一の設定・同一のpom.xmlで成功する。Maven/Java/Galleonのバージョンをすべて揃えても再現する。
調査の経緯
排除した仮説
| 仮説 | 検証結果 |
|---|---|
| Nexusのネットワーク疎通 | 問題なし。jarは取得できていた |
| settings.xml の差異 |
effective-settings で同一であることを確認 |
| Maven バージョン差異(3.8 vs 3.9) | GitLab CI上でMaven 3.8に変更しても同じエラー |
| Java バージョン差異 | 両環境ともOpenJDK 17 |
| Galleonプラグインバージョン | 同一 |
| Nexus上のpom/jarの破損 | 両方正常に存在(pom: 7.5KB, jar: 137.3KB) |
_remote.repositories のキャッシュ |
削除しても同じエラー。そもそもキャッシュゼロで再現 |
ブレークスルーとなった観察
-
ビルド後のローカルリポジトリにvfsのjarはあるが、pomがない。Maven本体の依存解決(warビルド)では、推移的依存の解決が不要な場合にpomの取得をスキップすることがある。
-
エラーの起点は
org.jboss.modules.xml.ModuleXmlParser。これはMaven本体のクラスではなく、JBoss Modulesのクラスである。 -
ビルド成果物(
target/bootable-jar-build-artifacts/)内のmodule.xmlに<artifact name="org.jboss:jboss-vfs:..."/>がそのまま残っていた。fat server(デフォルト)では<resource-root path="jboss-vfs-....jar"/>に書き換わるべきだが、書き換えに失敗していた。
根本原因
Galleonプロビジョニングのconfig生成フェーズでは、embedded WildFlyサーバーがフォークされて起動する。このプロセス内でmodule.xmlが読み込まれ、<artifact> 要素のアーティファクト解決にはJBoss Modulesの MavenResolver が使われる。
このResolverはMaven本体とは完全に独立した仕組みであり、以下の特性を持つ:
-
settings.xmlを一切読まない -
-s /path/to/settings.xml(Mavenの設定ファイル指定)を無視する -
-Dmaven.repo.local(Maven本体のローカルリポジトリ指定)を無視する - ローカルリポジトリは独自のシステムプロパティ
maven.repo.localで指定する(デフォルト:${user.home}/.m2/repository) - リモートリポジトリは独自のシステムプロパティ
remote.maven.repoで指定する(デフォルト: なし)
注意: JBoss Modulesのシステムプロパティ
maven.repo.localとMaven本体の-Dmaven.repo.localは同名だが、JBoss Modulesは自身のResolverインスタンス生成時にこのプロパティを読む。Mavenの-Dで渡したプロパティはMavenのJVMプロセスには設定されるが、Galleonからフォークされたembeddedサーバープロセスには引き継がれない場合がある。
GitLab CI環境では、ローカルリポジトリのパスがデフォルトの ${user.home}/.m2/repository から /builds/.../.m2/repository に変更されていた。Maven本体は -Dmaven.repo.local や settings.xml の <localRepository> でこのカスタムパスを認識するが、JBoss Modulesの MavenResolver はデフォルトの ${user.home}/.m2/repository を参照し続ける。そこにはアーティファクトが存在しないため、解決に失敗する。
OpenShiftではローカルリポジトリがデフォルトパスにあったため、同じビルドプロセス内でMaven本体がダウンロードしたアーティファクトを、JBoss Modulesの MavenResolver もそのまま参照でき、問題が発生しなかった。
対処
推奨: ローカルリポジトリをデフォルトパスに戻す
CI環境でローカルリポジトリのパスを変更している設定をすべてコメントアウトまたは削除し、デフォルトの ${user.home}/.m2/repository を使用する。これが最もシンプルかつ確実な対処。
代替: JBoss Modules固有のシステムプロパティを設定する
ローカルリポジトリのパスを変更する必要がある場合は、Mavenコマンドに以下のシステムプロパティを追加する。
mvn package verify \
-Dmaven.repo.local=/path/to/custom-repo \
-Dremote.maven.repo=https://nexus.example.com/repository/your-group-repo/
maven.repo.local だけでは不十分な場合がある。Maven本体がpomをダウンロードしないアーティファクトが存在し、JBoss Modulesがpomも含めて解決を試みるため、remote.maven.repo でリモートからの取得を可能にする必要がある。
さらに、integration-test/verifyフェーズでbootable jarを起動するプラグインがある場合は、フォークされるサーバーのJVMにも同じプロパティを渡す必要がある。
教訓
MavenのビルドにはMaven以外のアーティファクト解決が含まれている
Galleonを使うWildFly/EAPのビルドでは、以下の3つのアーティファクト解決メカニズムが混在する:
-
Maven本体のResolver(Apache Maven Resolver / Aether): pom.xmlの
<dependencies>を処理。settings.xmlを読む。 - GalleonのArtifactResolver: feature-packの取得やmodule.xmlテンプレートのartifact書き換えを処理。Maven本体のResolverを内部的に利用する。
-
JBoss Modulesの
MavenResolver: config生成やembedded server起動時にmodule.xmlの<artifact>要素を処理。Maven本体とは完全に独立。settings.xmlを読まない。
CI環境でローカルリポジトリのパスを変更すると、1と2は追従するが、3は追従しない。これが今回の問題の本質である。
既知の同類の問題
以前の調査で判明した「JBoss Modules の MavenResolver が ${user.home}/.m2/repository をハードコードする」という挙動は、Galleon以外の文脈でも問題になりうる。
参考リンク
-
JBoss Modules
MavenResolverJavaDoc —maven.repo.local、remote.maven.repoの仕様 -
JBoss Modules マニュアル — module.xmlの
<artifact>要素の仕様 -
WildFly Galleon Plugins ドキュメント — module.xmlテンプレートの
<artifact>→<resource-root>書き換えの説明 - WildFly Bootable JAR ドキュメント — fat/thin serverの違い
- WfInstallPlugin ソースコード — Galleonプラグインのアーティファクト解決の実装