Skip to content

[#3393] Support multiple Java modules per POM with Maven 4 module source hierarchy - #3394

Closed
ascheman wants to merge 6 commits into
apache:masterfrom
aschemaven:3345-multi-module-source-hierarchy
Closed

[#3393] Support multiple Java modules per POM with Maven 4 module source hierarchy#3394
ascheman wants to merge 6 commits into
apache:masterfrom
aschemaven:3345-multi-module-source-hierarchy

Conversation

@ascheman

Copy link
Copy Markdown
Contributor

Fixes #3393. Stacked on #3392 (single-module Module Source Hierarchy support) — draft until #3392 is merged; only the last commit is specific to this PR.

Changes

  • ResolvePathResultWrapper additionally carries the descriptors of sibling modules found under target/classes/<module>/.
  • AbstractSurefireMojo.findModuleDescriptor(...) resolves ALL nested module descriptors; the primary module (driving scanning and --patch-module) is the first one that has a nested test output directory.
  • scanDirectories() unions the test-class scan over every nested target/test-classes/<module>/ directory.
  • newStartupConfigWithModularPath(...):
    • dependency split (classpath vs. module path) is computed per module descriptor and unioned — an element required on the module path by ANY module ends up there, since the fork has a single boot layer;
    • the primary module's --add-opens list is restricted to its own test packages;
    • each sibling module with tests gets --patch-module <m>=<its test dir>, --add-reads <m>=ALL-UNNAMED and --add-opens <m>/<pkg>=ALL-UNNAMED, passed to the fork through the existing StartupConfiguration#getJpmsArguments() channel (no change to the surefire-booter API or its serialization).

Tests

  • Unit: shouldFindAllNestedModuleDescriptors (all nested modules, deterministic order).
  • IT: Surefire3393MultiModuleSourceHierarchyIT — two modules in one POM (com.example.extra requires com.example.core, core requires transitive jakarta.json to cover the external-modular-dependency boot failure), whitebox tests in both modules, 5 tests in ONE execution; skips itself under Maven 3.

Verification (local, JDK 17 for surefire build/ITs, macOS)

  • maven-surefire-common unit suite: 849 tests, 0 failures.
  • IT matrix:
Maven classic IT single-module MSH IT multi-module MSH IT
3.10.0-rc-1 pass skip skip
4.0.0-rc-5 pass pass pass (5 tests, one execution)
4.0.x-SNAPSHOT (rc-6 candidate) pass pass pass

Following this checklist to help us incorporate your contribution quickly and easily:

  • Each commit in the pull request should have a meaningful subject line and body.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Run mvn clean install to make sure basic checks pass. A more thorough check will be performed on your pull request automatically.
  • You have run the integration tests successfully (the new ITs plus the matrix above; the full suite runs in CI).
  • I hereby declare this contribution to be licenced under the Apache License Version 2.0, January 2004

The IT fixture defaults to embedded launching via Embedded3xLauncher
(maven-shared-verifier), which looks up org.apache.maven.cli.MavenCli.
Maven 4 renamed the entry point to org.apache.maven.cling.MavenCling
with a different signature, so every embedded launch against Maven 4
aborts with NoSuchMethodException before any test runs.

Detect Maven 4 by the presence of maven-api-core in ${maven.home}/lib
and default forkJvm to true in that case; Maven 3 keeps embedded mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ascheman ascheman added this to the 3.6.0-M2 milestone Jul 18, 2026
Maven 4 with maven-compiler-plugin 4.x compiles module source
hierarchy projects to a nested layout (target/classes/<module>/,
target/test-classes/<module>/) and emits a runtime handoff file
META-INF/maven/module-info-patch.args derived from
module-info-patch.maven. Surefire handled neither:

- findModuleDescriptor() only looked for module-info.class at the
  build output root, so detection failed and execution silently fell
  back to the classpath.
- DirectoryScanner read the module directory as a package prefix,
  producing doubled FQCNs and "Tests run: 0" or "Unable to create
  test class" failures.
- The compiler-generated module-info-patch.args was ignored, dropping
  the --add-exports directives declared in module-info-patch.maven.

Detect the nested module directory, scan its test classes, patch the
module with target/test-classes/<module>, and merge the handoff file
into the fork argfile. --add-reads/--add-modules lines from the file
are skipped (one-line and two-line argfile forms): they may reference
named modules that surefire places on the classpath; surefire keeps
generating --add-reads <module>=ALL-UNNAMED, --add-opens for test
packages and --add-modules ALL-MODULE-PATH itself.

The nested layout is decided by the MAIN build output only (no root
module-info.class plus a module subdirectory containing one): a
classic modular project whose module is named after its root package
(module "it", package "it") has target/test-classes/it/ as a package
directory, which must not be mistaken for a nested test output and
patched into the module (would break class loading of every test).

Covered by unit tests and two ITs: ModulePathWhiteboxIT (classic
layout, runs on Maven 3+4) and Surefire3345ModuleSourceHierarchyIT
(nested layout, skips itself below Maven 4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ascheman
ascheman force-pushed the 3345-multi-module-source-hierarchy branch from 052fcc4 to 4b03dae Compare July 18, 2026 14:48
@ascheman
ascheman requested a review from Copilot July 19, 2026 07:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Surefire support for multiple JPMS modules produced from a single Maven 4 Module Source Hierarchy POM, enabling one test execution to discover and run tests across all nested target/test-classes/<module>/ outputs while producing a single, unified JPMS boot layer for the forked JVM.

Changes:

  • Detect and resolve all nested module descriptors under target/classes/<module>/, selecting a primary module and carrying sibling module descriptors alongside it.
  • Scan and union test classes from each nested target/test-classes/<module>/ directory, and generate per-sibling --patch-module / --add-reads / --add-opens JPMS arguments.
  • Add/extend IT fixtures and integration tests covering single-module MSH and multi-module-in-one-POM MSH scenarios (Maven 4-only where appropriate).

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/test/java/module-info-patch.maven Adds compiler patch input for the com.example.extra module (whitebox access).
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/test/java/com/example/extra/internal/TwiceHelperWhiteboxTest.java Adds whitebox test validating internal-package access in com.example.extra.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/test/java/com/example/extra/DoublerTest.java Adds regular test for com.example.extra exported API.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/main/java/module-info.java Declares the com.example.extra module and its dependency on com.example.core.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/main/java/com/example/extra/internal/TwiceHelper.java Adds internal helper used by the extra module and its whitebox test.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/main/java/com/example/extra/Doubler.java Adds exported class exercising a cross-module call into com.example.core.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/test/java/module-info-patch.maven Adds compiler patch input for the com.example.core module (whitebox access).
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/test/java/com/example/core/internal/MathHelperWhiteboxTest.java Adds whitebox test validating internal-package access in com.example.core.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/test/java/com/example/core/CalculatorTest.java Adds tests exercising both basic behavior and an external module dependency (jakarta.json).
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/main/java/module-info.java Declares the com.example.core module and a transitive dependency on jakarta.json.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/main/java/com/example/core/internal/MathHelper.java Adds internal helper used for whitebox coverage in core.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/main/java/com/example/core/Calculator.java Adds exported class referencing jakarta.json to validate module-path dependency placement.
surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/pom.xml Defines a Maven 4.1.0 multi-module-source-hierarchy test project with two modules in one POM.
surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/test/java/module-info-patch.maven Adds compiler patch input for the single-module MSH IT fixture.
surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/test/java/com/example/internal/MathHelperWhiteboxTest.java Adds whitebox tests for the single-module MSH IT fixture.
surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/test/java/com/example/CalculatorTest.java Adds a basic exported-API test for the single-module MSH IT fixture.
surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/main/java/module-info.java Declares the module for the single-module MSH IT fixture.
surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/main/java/com/example/internal/MathHelper.java Adds internal helper for whitebox coverage in the single-module MSH fixture.
surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/main/java/com/example/Calculator.java Adds exported class used by tests in the single-module MSH fixture.
surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/pom.xml Defines the Maven 4.1.0 single-module-source-hierarchy test project fixture.
surefire-its/src/test/resources/modulepath-whitebox/src/test/java/com/example/internal/MathHelperWhiteboxTest.java Adds/updates baseline modular whitebox fixture test content (classic layout).
surefire-its/src/test/resources/modulepath-whitebox/src/test/java/com/example/CalculatorTest.java Adds/updates baseline modular fixture test content (classic layout).
surefire-its/src/test/resources/modulepath-whitebox/src/main/java/module-info.java Declares the module for the baseline modular whitebox fixture.
surefire-its/src/test/resources/modulepath-whitebox/src/main/java/com/example/internal/MathHelper.java Adds internal helper used by baseline whitebox tests.
surefire-its/src/test/resources/modulepath-whitebox/src/main/java/com/example/Calculator.java Adds exported class used by baseline tests.
surefire-its/src/test/resources/modulepath-whitebox/pom.xml Defines the baseline modular whitebox fixture POM (Maven 3-compatible).
surefire-its/src/test/java/org/apache/maven/surefire/its/ModulePathWhiteboxIT.java Adds an integration test validating classic modular whitebox behavior via --patch-module.
surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire3393MultiModuleSourceHierarchyIT.java Adds Maven 4-only IT validating multi-module-in-one-POM MSH behavior (single execution).
surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire3345ModuleSourceHierarchyIT.java Adds/updates Maven 4-only IT validating single-module MSH + module-info-patch args handling.
surefire-its/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncher.java Switches IT launcher default to forked mode when running under Maven 4+.
maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java Adds unit tests around reading/skipping directives from module-info-patch.args.
maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java Adds unit tests for nested module descriptor detection and multi-descriptor ordering.
maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/ResolvePathResultWrapper.java Extends wrapper to carry sibling module descriptor resolutions.
maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java Reads compiler-generated module-info-patch.args and appends JPMS args into the fork args file.
maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java Resolves multiple nested module descriptors, unions nested test scans, and generates per-module JPMS args.

Comment on lines +269 to +274
/**
* Reads the module-info-patch.args file and appends its --add-exports and --add-opens
* directives to the args builder. The --add-reads directive is handled by surefire itself
* (always adds ALL-UNNAMED) to avoid referencing modules that may be on the classpath
* rather than the module-path.
*
Address Copilot review feedback on apache#3392:

- Reword the inline comment and the appendModuleInfoPatchArgs Javadoc:
  the method forwards ALL directives from module-info-patch.args except
  --add-reads/--add-modules, not only --add-exports.
- Sort the nested module directory candidates so the picked module no
  longer depends on filesystem iteration order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ascheman
ascheman force-pushed the 3345-multi-module-source-hierarchy branch from 4b03dae to 0203470 Compare July 19, 2026 07:32
The compiler-generated handoff file is UTF-8; FileReader uses the
platform default charset and could misparse non-ASCII module or
package names. Addresses Copilot review feedback on apache#3394.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ascheman and others added 2 commits July 20, 2026 13:11
Address desruisseaux's review comments on apache#3392:

- Javadoc for createArgsFile, documenting in particular the patchFile
  parameter (test output directory, or its per-module subdirectory
  for a Maven 4 module source hierarchy build).
- Correct the findModuleInfoPatchArgs documentation: there is exactly
  one module-info-patch.args per project, at
  target/test-classes/META-INF/maven/; the parent lookup exists
  because the patch directory is the nested per-module directory in
  a module source hierarchy build.
- Drop the explicit charset: Files.newBufferedReader(Path) already
  reads UTF-8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Maven 4 module source hierarchy build may declare several Java
modules in one POM; the single-module support from apache#3345 detected
only the first nested module, scanned only its tests, and decided
classpath-vs-module-path dependency placement with one descriptor,
so the fork died at the Java Modules boot layer when a sibling module
required a dependency that stayed on the classpath.

- findModuleDescriptor() resolves ALL nested module descriptors;
  the primary module (driving scanning and --patch-module) is the
  first one with a nested test output directory, siblings travel as
  additional results in ResolvePathResultWrapper.
- scanDirectories() unions the scan over every nested
  target/test-classes/<module>/ directory.
- The classpath/module-path split is the union over all module
  descriptors: an element required on the module path by ANY module
  goes there, since the fork has a single boot layer.
- The primary module only opens its own test packages; each sibling
  module with tests gets --patch-module, --add-reads and per-package
  --add-opens, passed through the existing
  StartupConfiguration#getJpmsArguments() channel - no change to the
  surefire-booter API or its serialization.

Covered by a unit test for the multi-descriptor detection and the IT
Surefire3393MultiModuleSourceHierarchyIT: two modules in one POM
(com.example.extra requires com.example.core, core requires
transitive jakarta.json to cover the boot-layer failure), whitebox
tests in both modules, five tests in one execution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ascheman
ascheman force-pushed the 3345-multi-module-source-hierarchy branch from abce838 to ec267cb Compare July 20, 2026 11:11
@ascheman ascheman closed this Jul 20, 2026
@github-actions github-actions Bot removed this from the 3.6.0-M2 milestone Jul 20, 2026
@ascheman

Copy link
Copy Markdown
Contributor Author

Merged into #3392 as requested by @desruisseaux (via Slack) — the stacked split created review noise without adding value: the shared commits showed up in both diffs and review comments landed on either PR.

The multi-module commit now lives directly on the #3392 branch (6ac070cc7, unchanged content), and #3392's description covers both scopes (fixes #3345 and #3393). All review comments made here stay linked and will be addressed over there. Closing.

@ascheman
ascheman deleted the 3345-multi-module-source-hierarchy branch July 21, 2026 09:24
ascheman added a commit to aschemaven/maven-surefire that referenced this pull request Jul 24, 2026
The compiler-generated handoff file is UTF-8; FileReader uses the
platform default charset and could misparse non-ASCII module or
package names. Addresses Copilot review feedback on apache#3394.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple Java modules per POM with Maven 4 Module Source Hierarchy

3 participants