Skip to content

Internalize plexus-classworlds and add JPMS module support (fixes #11028) - #11029

Draft
gnodet wants to merge 14 commits into
apache:masterfrom
gnodet:feature/maven-classworlds-11028
Draft

gnodet wants to merge 14 commits into
apache:masterfrom
gnodet:feature/maven-classworlds-11028

Conversation

@gnodet

@gnodet gnodet commented Aug 7, 2025 •

Copy link
Copy Markdown
Contributor

Summary

Replaces the external plexus-classworlds dependency with an internalized maven-classworlds module, adds JPMS module support for Maven's runtime (targeted --enable-native-access for JLine), and introduces per-plugin JPMS ModuleLayer support for modular plugins.

Internalize plexus-classworlds

  • Copy plexus-classworlds source into impl/maven-classworlds, maintaining original package structure
  • Extract org.apache.maven.api.classworlds into a separate API module (api/maven-api-classworlds)
  • Replace all plexus-classworlds dependencies across the build with maven-classworlds
  • Fully eliminate plexus-classworlds from the dependency tree

Targeted --enable-native-access for JLine (fixes #11028)

  • Move JLine jars to lib/modules/ and load them on the boot --module-path via a new module directive in m2.conf
  • Add --add-modules ALL-MODULE-PATH to resolve modules on the module-path
  • Replace --enable-native-access=ALL-UNNAMED with targeted --enable-native-access=org.jline.terminal.ffm,org.jline.nativ
  • Add Java version detection in launcher scripts — skip module flags on JDKs that don't support them

Runtime ModuleLayer with module access control

  • Add add-exports, add-opens, add-reads directives to m2.conf configuration parser
  • Create a runtime ModuleLayer in Configurator for modules not already in the boot layer, with Controller-based access control
  • Store ModuleLayer and Controller on ClassWorld for realm-level access
  • Add ClassRealm.addExports(), ClassRealm.addOpens(), and ClassRealm.addReads() methods for plugins/extensions to control module access at runtime

Per-plugin JPMS ModuleLayer support for modular plugins

  • Add <modular>true</modular> boolean field to the plugin descriptor model (plugin.mdo, version 1.2.0+)
  • Add isModular() and getModuleLayer() default methods to the ClassRealm API interface
  • Store per-realm ModuleLayer and ModuleLayer.Controller on ClassRealm (not ClassWorld) — each modular plugin gets its own isolated layer
  • New createModularPluginRealm() in DefaultClassRealmManager:
    • Collects plugin artifact paths, creates ModuleFinder
    • Uses parent layer from ClassWorld (runtime layer if available, else boot layer)
    • Calls resolveAndBind() for module resolution + service provider binding
    • Creates layer with defineModulesWithOneLoader() — one classloader per plugin, isolation between plugins
    • Flat layer hierarchy — all plugin layers are siblings under the boot/runtime layer
  • Hard error, no fallback: ModuleLayerCreationException if the module graph cannot be resolved (split packages, missing module-info.class, etc.)
  • DefaultMavenPluginManager branches on isModular():
    • Modular plugins: loaded via ModuleLayer, TCCL set to layer's classloader, DI via maven-di Injector.discover() only (no Plexus/Sisu component discovery)
    • Classic plugins: unchanged classpath-based ClassRealm loading with full Sisu support
  • Layer classloader is closed when the realm is disposed

Configuration and metadata formats

m2.conf directives

Directive Syntax Description
module module <glob> Load JARs on the JPMS module-path (used for JLine)
add-exports add-exports <module>/<package>=<target> Export a package from a named module to a target (defaults to ALL-UNNAMED)
add-opens add-opens <module>/<package>=<target> Open a package for deep reflection
add-reads add-reads <module>=<target> Add a reads edge between modules

Example (m2.conf):

module ${maven.home}/lib/modules/*.jar

META-INF/maven/module-access descriptor

Plugins and extensions can declare module access requirements via a META-INF/maven/module-access resource. This is processed for all realm types (plugin, extension, project) and for modular plugin realms.

# Export a package from a named module to the plugin's classloader
add-exports org.jline.terminal/org.jline.terminal.spi
# Open a package for deep reflection
add-opens org.jline.reader/org.jline.reader.impl
# Add a reads edge
add-reads org.jline.terminal=ALL-UNNAMED

The direction is: packages are exported/opened from a named module in the boot/runtime layer to the plugin's classloader (unnamed module). This is semantically identical to JVM --add-exports / --add-opens flags.

Plugin descriptor (plugin.xml)

<plugin>
  <!-- ... -->
  <modular>true</modular>  <!-- opt-in to JPMS ModuleLayer loading (default: false) -->
</plugin>

Files changed (key files)

File Change
apache-maven/src/assembly/maven/bin/mvn Module-path, add-modules, targeted enable-native-access
apache-maven/src/assembly/maven/bin/mvn.cmd Same for Windows
apache-maven/src/assembly/maven/bin/m2.conf module directive for JLine jars
impl/maven-classworlds/ Full internalized classworlds implementation
api/maven-api-classworlds/ New classworlds API module with isModular(), getModuleLayer()
api/maven-api-plugin/src/main/mdo/plugin.mdo modular boolean field (version 1.2.0+)
ConfigurationParser.java Parse module, add-exports, add-opens, add-reads directives
Configurator.java Runtime ModuleLayer creation and directive application
ClassWorld.java ModuleLayer + Controller storage (boot/runtime layer)
ClassRealm.java (impl) Per-realm ModuleLayer + Controller, addExports(), addOpens(), addReads(), close() cleanup
ClassRealm.java (API) isModular(), getModuleLayer() default methods
ClassRealmManager.java New createModularPluginRealm() interface method
DefaultClassRealmManager.java createModularPluginRealm() implementation + META-INF/maven/module-access processing
ModuleLayerCreationException.java Hard error for modular plugin loading failures
DefaultMavenPluginManager.java Modular vs classic plugin branching, TCCL, DI discovery
PluginDescriptor.java modular field, getter/setter
PluginDescriptorBuilder.java extractModular() parser method

Test plan

  • mvn test -pl impl/maven-classworlds — 93 tests pass
  • mvn test -pl compat/maven-plugin-api — 2 tests pass
  • mvn test -pl impl/maven-core — 562 tests pass
  • mvn install -DskipTests — full build succeeds

Closes #11029
Fixes #11028

Claude Code on behalf of Guillaume Nodet

@gnodet
gnodet force-pushed the feature/maven-classworlds-11028 branch 3 times, most recently from b4c6955 to fe2ac45 Compare August 9, 2025 16:07
@cstamas

cstamas commented Aug 11, 2025

Copy link
Copy Markdown
Member

Should we maybe at the same time archive/EOL plexus-classworlds?
Also, is this a fork of it? Or rewrite? As if some third-party may use plexus-classworlds, is this new module drop in replacement?

@slawekjaranowski

Copy link
Copy Markdown
Member

I'm not sure if we need copy it to Maven core ...
Why not simply update plexus-classworlds?

Especially that old java package and old licenses header are preserved

@gnodet

gnodet commented Aug 12, 2025

Copy link
Copy Markdown
Contributor Author

Should we maybe at the same time archive/EOL plexus-classworlds?
Also, is this a fork of it? Or rewrite? As if some third-party may use plexus-classworlds, is this new module drop in replacement?

This is currently a fork, and I removed the old layer already.

I'm not sure if we need copy it to Maven core ... Why not simply update plexus-classworlds?

Especially that old java package and old licenses header are preserved

This was really a first step. I was toying a bit to solve a few limitations:

One important point is that extensions should be able to provide controlled access to some API, which could be consumed by plugins. Plugins themselves could export some packages, I think we had some use cases for that (we currently need an extension to do that).

So not sure yet, this is just experiments... but ideas welcomed. Overall, I think plexus-classworlds won't be able to solve the above problems, and given Maven is really the only user afaik, I'm tempted to merge ...

@gnodet gnodet added this to the 4.1.0 milestone Oct 6, 2025
@gnodet gnodet added mvn4 enhancement New feature or request labels Jun 22, 2026
@gnodet
gnodet force-pushed the feature/maven-classworlds-11028 branch from fe2ac45 to c5ab33f Compare June 24, 2026 07:32
@gnodet gnodet changed the title Add maven-classworlds module to replace plexus-classworlds dependency [ISSUE-11028] Internalize plexus-classworlds and add JPMS module support Jun 24, 2026
@gnodet gnodet changed the title [ISSUE-11028] Internalize plexus-classworlds and add JPMS module support Internalize plexus-classworlds and add JPMS module support (fixes #11028) Jun 24, 2026
@gnodet
gnodet requested review from Copilot and desruisseaux June 24, 2026 21:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR internalizes plexus-classworlds into a new maven-classworlds implementation module, introduces a new public API module (maven-api-classworlds), and updates Maven’s distribution/launching to support JPMS modules (notably loading JLine from a module path to enable targeted --enable-native-access).

Changes:

  • Replace external plexus-classworlds with internal maven-classworlds + new maven-api-classworlds API and update imports/usages across core/cli/compat.
  • Update Maven distribution + launcher scripts (mvn, mvn.cmd, m2.conf, assembly component) to load JLine from lib/modules and use targeted native-access flags.
  • Add/port classworlds tests and legacy compatibility shims (org.codehaus.classworlds.*) to preserve ecosystem compatibility (e.g., Sisu).

Reviewed changes

Copilot reviewed 141 out of 161 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/graph/ReactorGraph.java Include maven-classworlds in clustering
pom.xml Swap deps to maven-classworlds and add exclusions
its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java Import reorder/update for classworlds
its/core-it-suite/src/test/resources/mng-5771-core-extensions/repo-src/maven-it-core-extensions-no-descriptor/src/main/java/org/apache/maven/its/core_extensions/TestClassRealmManagerDelegate.java Import reorder/update for classworlds
its/core-it-suite/src/test/resources/mng-5530-mojo-execution-scope/extension/src/main/java/org/apache/maven/its/mng5530/mojoexecutionscope/extension/TestClassRealmManagerDelegate.java Import reorder/update for classworlds
impl/pom.xml Add maven-classworlds module
impl/maven-core/src/test/java/org/apache/maven/plugin/PluginManagerTest.java Switch to Maven ClassRealm API
impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/BuildPluginManagerStub.java Switch to Maven ClassRealm API
impl/maven-core/src/test/java/org/apache/maven/classrealm/DefaultClassRealmManagerTest.java Update realm construction for new API
impl/maven-core/src/main/resources/META-INF/maven/extension.xml Export maven-classworlds instead of plexus
impl/maven-core/src/main/java/org/apache/maven/project/ProjectRealmCache.java Switch to Maven ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java Switch to Maven ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectRealmCache.java Switch to Maven ClassRealm API/exceptions
impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java Use ClassRealm#getClassLoader + import updates
impl/maven-core/src/main/java/org/apache/maven/plugin/PluginRealmCache.java Switch to Maven ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/plugin/PluginManagerException.java Switch to Maven NoSuchRealmException API
impl/maven-core/src/main/java/org/apache/maven/plugin/PluginContainerException.java Switch to Maven ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/plugin/ExtensionRealmCache.java Switch to Maven ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginRealmCache.java Switch to Maven ClassRealm API/exceptions
impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultExtensionRealmCache.java Switch to Maven ClassRealm API/exceptions
impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java Use ClassRealm#getClassLoader in execution paths
impl/maven-core/src/main/java/org/apache/maven/plugin/BuildPluginManager.java Switch to Maven ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java Use ClassRealm#getClassLoader for TCCL
impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java Use ClassRealm#getClassLoader for TCCL
impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java Use ClassRealm#getClassLoader for TCCL
impl/maven-core/src/main/java/org/apache/maven/internal/impl/internal/DefaultCoreRealm.java Switch to Maven ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java Use realm.getClassLoader for TCCL
impl/maven-core/src/main/java/org/apache/maven/internal/CoreRealm.java Switch to Maven ClassWorld/ClassRealm API
impl/maven-core/src/main/java/org/apache/maven/extension/internal/CoreExtensionEntry.java Use realm.getClassLoader for resource lookup
impl/maven-core/src/main/java/org/apache/maven/extension/internal/CoreExports.java Store exports as ClassLoader map
impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java Use realm.getClassLoader for scanning
impl/maven-core/src/main/java/org/apache/maven/configuration/internal/EnhancedComponentConfigurator.java Pass ClassLoader instead of casting realm
impl/maven-core/src/main/java/org/apache/maven/classrealm/ClassRealmManager.java Switch to Maven ClassRealm API
impl/maven-core/pom.xml Add revapi excludes for signature changes
impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java Switch to Maven ClassWorld API
impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java Switch to Maven ClassWorld API + impl wiring
impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java Switch to Maven ClassWorld API
impl/maven-cli/src/main/java/org/apache/maven/cling/MavenUpCling.java Update ClassWorld typing in entrypoints
impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java Update ClassWorld typing in entrypoints
impl/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java Update ClassWorld typing in entrypoints
impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java Update ClassWorld typing in entrypoints
impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java Bridge Maven API realms to Plexus container
impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java Use ClassLoader-based realm imports/parenting
impl/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java Construct impl ClassWorld explicitly
impl/maven-cli/pom.xml Depend on maven-api-classworlds + maven-classworlds
impl/maven-classworlds/src/test/test-data/valid.conf Add parser test data
impl/maven-classworlds/src/test/test-data/valid-launch.conf Add launcher test data
impl/maven-classworlds/src/test/test-data/valid-launch-exitCode.conf Add launcher test data
impl/maven-classworlds/src/test/test-data/valid-from-from-from.conf Add parser test data
impl/maven-classworlds/src/test/test-data/valid-enh-launch.conf Add launcher test data
impl/maven-classworlds/src/test/test-data/valid-enh-launch-exitCode.conf Add launcher test data
impl/maven-classworlds/src/test/test-data/unhandled.conf Add parser test data
impl/maven-classworlds/src/test/test-data/set-using-nonexistent.conf Add parser test data
impl/maven-classworlds/src/test/test-data/set-using-missing.conf Add parser test data
impl/maven-classworlds/src/test/test-data/set-using-existent.properties Add parser test data
impl/maven-classworlds/src/test/test-data/set-using-existent.conf Add parser test data
impl/maven-classworlds/src/test/test-data/resources/classworlds.conf Add resource test data
impl/maven-classworlds/src/test/test-data/realm-syntax.conf Add parser test data
impl/maven-classworlds/src/test/test-data/optionally-nonexistent.conf Add parser test data
impl/maven-classworlds/src/test/test-data/optionally-existent.conf Add parser test data
impl/maven-classworlds/src/test/test-data/nested.properties Add resource test data
impl/maven-classworlds/src/test/test-data/launch-nomethod.conf Add launcher test data
impl/maven-classworlds/src/test/test-data/launch-noclass.conf Add launcher test data
impl/maven-classworlds/src/test/test-data/inheritance.conf Add parser test data
impl/maven-classworlds/src/test/test-data/early-import.conf Add parser test data
impl/maven-classworlds/src/test/test-data/dupe-realm.conf Add parser test data
impl/maven-classworlds/src/test/test-data/dupe-main.conf Add parser test data
impl/maven-classworlds/src/test/test-data/a.properties Add resource test data
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/UrlUtilsTest.java Add UrlUtils unit test
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/TestUtil.java Add test utility
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/strategy/StrategyTest.java Add strategy unit tests
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealmTest.java Add filtered realm tests
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/LauncherTest.java Add launcher tests
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/launcher/ConfigurationParserTest.java Add parser tests
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassWorldTest.java Add ClassWorld tests
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/ClassView.java Add legacy debug utility
impl/maven-classworlds/src/test/java/org/codehaus/plexus/classworlds/AbstractClassWorldsTestCase.java Add shared test base
impl/maven-classworlds/src/test/java/org/apache/maven/api/classworlds/ApiTest.java Add API usage test
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/UrlUtils.java Add internalized UrlUtils
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/StrategyFactory.java Add internalized strategy factory
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/Strategy.java Bridge impl Strategy to API Strategy
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/SelfFirstStrategy.java Add internalized strategy
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/ParentFirstStrategy.java Add internalized strategy
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/OsgiBundleStrategy.java Add internalized strategy
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/strategy/AbstractStrategy.java Add internalized base strategy
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/NoSuchRealmException.java Bridge impl exception to API exception
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/FilteredClassRealm.java Add filtered realm implementation
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/realm/DuplicateRealmException.java Bridge impl exception to API exception
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationHandler.java Extend config handler for modules/exports/opens/reads
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/launcher/ConfigurationException.java Add internalized config exception
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldListener.java Bridge impl listener to API listener
impl/maven-classworlds/src/main/java/org/codehaus/plexus/classworlds/ClassWorldException.java Bridge impl exception to API exception
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/package-info.java Document legacy API compatibility constraints
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/NoSuchRealmException.java Legacy compatibility exception
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Launcher.java Legacy compatibility launcher wrapper
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DuplicateRealmException.java Legacy compatibility exception
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/DefaultClassRealm.java Legacy compatibility realm wrapper
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfiguratorAdapter.java Legacy compatibility adapter
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/Configurator.java Legacy compatibility configurator
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ConfigurationException.java Legacy compatibility exception
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldReverseAdapter.java Legacy reverse adapter
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldException.java Legacy compatibility exception
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorldAdapter.java Legacy adapter
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassWorld.java Legacy compatibility ClassWorld
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmReverseAdapter.java Legacy reverse adapter
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealmAdapter.java Legacy adapter for Sisu compatibility
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/ClassRealm.java Legacy interface for Sisu compatibility
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLStreamHandler.java Legacy byte-array URL handler
impl/maven-classworlds/src/main/java/org/codehaus/classworlds/BytesURLConnection.java Legacy byte-array URL connection
impl/maven-classworlds/pom.xml New module build/test configuration
compat/maven-resolver-provider/pom.xml Add maven-classworlds test dependency
compat/maven-plugin-api/pom.xml Replace plexus-classworlds dependency
compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java Bridge API ClassWorld to Plexus container
compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java Use ClassLoader-based realm imports/parenting
compat/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java Switch to Maven ClassWorld API
compat/maven-embedder/pom.xml Depend on maven-api-classworlds + maven-classworlds
compat/maven-compat/pom.xml Depend on maven-api-classworlds + maven-classworlds
api/pom.xml Register new maven-api-classworlds module
api/maven-api-classworlds/src/main/java/org/apache/maven/api/classworlds/Strategy.java New API Strategy interface
api/maven-api-classworlds/src/main/java/org/apache/maven/api/classworlds/package-info.java New API package docs
api/maven-api-classworlds/src/main/java/org/apache/maven/api/classworlds/NoSuchRealmException.java New API exception
api/maven-api-classworlds/src/main/java/org/apache/maven/api/classworlds/DuplicateRealmException.java New API exception
api/maven-api-classworlds/src/main/java/org/apache/maven/api/classworlds/ClassWorldListener.java New API listener
api/maven-api-classworlds/src/main/java/org/apache/maven/api/classworlds/ClassWorldException.java New API base exception
api/maven-api-classworlds/src/main/java/org/apache/maven/api/classworlds/ClassWorld.java New API ClassWorld interface
api/maven-api-classworlds/pom.xml New API module POM
apache-maven/src/main/appended-resources/META-INF/LICENSE.vm Adjust license output dirs for boot/modules
apache-maven/src/assembly/maven/bin/mvn.cmd Add module-path + targeted native access
apache-maven/src/assembly/maven/bin/mvn Add module-path + targeted native access
apache-maven/src/assembly/maven/bin/m2.conf Add module directive for lib/modules
apache-maven/src/assembly/component.xml Add boot/module dependency sets

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread impl/maven-classworlds/pom.xml Outdated
Comment thread apache-maven/src/main/appended-resources/META-INF/LICENSE.vm Outdated
Comment thread apache-maven/src/assembly/component.xml

@desruisseaux desruisseaux left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My main concern is this META-INF/maven/module-access file parsed by the applyModuleAccessDescriptors(ClassRealm) method in impl/maven-core/src/main/java/org/apache/maven/classrealm/DefaultClassRealmManager.java. I would like to see a documentation about what is the intent, which module is exporting to which module, and why it is not done in module-info.

gnodet and others added 10 commits September 21, 2026 09:07
…assworld to separate API module

- Remove jline-native references from Maven launcher scripts (mvn and mvn.cmd)
- Remove jline-native dependencies and configurations from POMs
- Remove module-info.java file from maven-classworlds
- Remove jline-native assembly configurations and README
- Create new maven-api-classworlds module in api/ directory
- Move org.apache.maven.api.classworlds package to new API module
- Update dependency management and module references
- Add maven-api-classworlds dependency to modules that use the API

This change separates the public API from the implementation, following Maven 4
architecture patterns, and removes the JPMS and jline-native dependencies that
were causing complexity in the build and runtime environment.
- Exclude plexus-classworlds transitive dependency from Sisu and
  plexus-testing in root pom dependencyManagement
- Add maven-classworlds test dependency to maven-resolver-provider
  (needed by plexus-testing at runtime)
- Copy legacy org.codehaus.classworlds adapter classes into
  maven-classworlds (required by Sisu's ClassRealmConverter and
  AbstractComponentConfigurator)
- Add checkstyle exclusion for legacy package and RAT exclusion
  for test data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move JLine jars from lib/ to lib/modules/ and load them via
--module-path + --add-modules ALL-MODULE-PATH so that
--enable-native-access can target org.jline.terminal.ffm and
org.jline.nativ by name instead of using the broad ALL-UNNAMED.

Changes:
- Add `module` directive to classworlds ConfigurationParser with
  glob support, mirroring the existing `load` directive
- Separate JLine jars into lib/modules/ in the assembly descriptor
- Update m2.conf with `module ${maven.home}/lib/modules/*.jar`
- Update mvn/mvn.cmd launcher scripts to use --module-path,
  --add-modules ALL-MODULE-PATH, and targeted --enable-native-access
- Update LICENSE.vm to classify JLine under lib/modules/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add JPMS ModuleLayer creation in classworlds for runtime module loading
with add-exports/add-opens/add-reads directives in m2.conf. Plugins and
extensions can declare module access requirements via
META-INF/maven/module-access descriptors, processed for all realm types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move module access logic from ClassRealm into ClassWorld to encapsulate
the Controller. Add addExports/addOpens/addReads to the API interface as
default methods returning boolean for success feedback. Synchronize
setModuleLayer. Support add-reads in module-access descriptors and log
on failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use void return type for addExports/addOpens/addReads to preserve
  binary compatibility on the ClassRealm class
- Remove redundant @SInCE tags (interface already has @SInCE 4.1.0)
- Conditionally enable --module-path and --enable-native-access only
  on Java 21+ (FFM API not available on Java 17)
- Add classpath fallback in Configurator when ModuleLayer creation
  fails (e.g. module jars reference unavailable APIs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Include --module-path in launcher probe so it fails correctly on JDKs
  that cannot read the module descriptors (class version 66.0)
- Revert CustomComponentConfigurator import to impl ClassRealm (needed
  for @OverRide compatibility with AbstractComponentConfigurator)
- Add maven-classworlds to ReactorGraph cluster patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…flection compat

Launcher and maven-executor use reflection to find main(String[], ClassWorld)
using the impl ClassWorld class. The API ClassWorld type doesn't match, causing
NoSuchMethodException at runtime. Use FQ org.codehaus.plexus.classworlds.ClassWorld
in all main() method parameters while keeping the API import for ProtoLookup mapping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Build the classpath from individual jar files instead of using a wildcard,
since cygpath --windows with a wildcard in the path can cause issues on
MinGW/Git Bash. Also convert MODULES_DIR to Windows format and construct
MAVEN_MODULE_OPTS after path conversion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gnodet and others added 3 commits September 21, 2026 09:09
ClassRealmManagerDelegate.setupRealm() is an SPI that extensions implement.
Changing its ClassRealm parameter type from impl to API breaks pre-compiled
extensions. Revert the interface to use the impl ClassRealm type and cast
in DefaultClassRealmManager. Also revert IT test sources and remove
temporary CI debug logging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
JLine jars were exclusively placed in lib/modules/ for JPMS module
path usage, but the EmbeddedMavenExecutor constructs its classpath
from lib/ only. Include JLine in both locations — the JVM's module
system prefers module path over classpath, so there's no conflict
when both paths are active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gins

Plugins that declare <modular>true</modular> in their descriptor are loaded
in their own ModuleLayer via resolveAndBind, with no fallback to classpath.
Non-modular plugins are unchanged (ClassRealm/URLClassLoader as before).

- Add `modular` boolean field to plugin descriptor model (plugin.mdo)
- Add isModular()/getModuleLayer() to ClassRealm API interface
- Add per-realm ModuleLayer/Controller storage in ClassRealm impl
- Add createModularPluginRealm() to ClassRealmManager/DefaultClassRealmManager
  using ModuleFinder + resolveAndBind + defineModulesWithOneLoader
- Add ModuleLayerCreationException for hard errors (split packages, etc.)
- Update DefaultMavenPluginManager: modular plugins skip Sisu/Plexus
  discovery and use maven-di Injector with the layer's classloader
- Set TCCL to ModuleLayer's loader for modular plugin execution
- Close layer's classloader on realm disposal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnodet
gnodet force-pushed the feature/maven-classworlds-11028 branch 2 times, most recently from 6e47113 to 61434a6 Compare September 21, 2026 12:51
gnodet-bot

This comment was marked as low quality.

gnodet-bot

This comment was marked as low quality.

gnodet-bot

This comment was marked as low quality.

@gnodet-bot gnodet-bot 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.

Three concrete issues to address before this lands.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Comment thread apache-maven/src/assembly/component.xml
@gnodet
gnodet force-pushed the feature/maven-classworlds-11028 branch from 61434a6 to d74461c Compare September 21, 2026 13:30

@gnodet-bot gnodet-bot 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.

Re-review after d74461c.

Previous findings — status:

  • ✅ JLine dual-deployment (lib/ + lib/modules/) — fixed: org.jline:* exclusion added to the lib/ dependencySet, maven-api-classworlds correctly routed to boot/.
  • ✅ CacheKey missing modular flag — fixed: boolean modular added with correct hashCode/equals, new createKey(..., boolean modular) overload in both interface and impl, propagated from PluginDescriptor.isModular() at the call site.
  • ✅ Javadoc on applyModuleAccessDescriptors — comprehensive Javadoc added explaining purpose, direction, file format, and rationale.
  • ⚠️ iterator().next() order-dependent — partially addressed (see inline comment below).

One remaining issue.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

…te license header

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnodet
gnodet force-pushed the feature/maven-classworlds-11028 branch from d74461c to 952614d Compare September 21, 2026 16:50
@gnodet
gnodet dismissed gnodet-bot’s stale review September 21, 2026 16:50

All issues addressed in latest push.

@gnodet

gnodet commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

All review comments addressed in the latest push:

@copilot-pull-request-reviewer:

  • Fixed invalid -ea:pkg1:pkg2 argLine → split into -ea:org.codehaus.classworlds... -ea:org.codehaus.plexus.classworlds...
  • Fixed Javadoc typos in ConfigurationException ("configuraton"/"occured")
  • Fixed LICENSE.vm to also map maven-api-classworlds → boot/ directory
  • Fixed JLine dual-copy: added org.jline:* exclusion from the lib/ dependencySet so JLine only lands in lib/modules/

@desruisseaux:

  • Added comprehensive Javadoc to applyModuleAccessDescriptors explaining the purpose, direction of access (named boot-layer module → plugin unnamed module, not the reverse), file format, and why module-info.java cannot be used instead
  • The ClassRealm API question: no further action needed — the discussion in the thread explains why ClassRealms and JPMS ModuleLayers are complementary, not duplicates

Fragile classloader lookups (gnodet-bot):

  • getEffectiveClassLoader: replaced iterator().next().getClassLoader() with layer.findLoader(anyModuleName) via stream().findFirst()
  • close(): same fix — replaced loop-with-break with stream().findFirst().ifPresent(...)
  • CacheKey missing modular flag: added field + hash + equals; kept old constructor and added default overload on the PluginRealmCache interface to preserve binary compatibility (japicmp was catching this)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request mvn4

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upgrade to JLine 4.x and Replace Broad Native Access with Targeted Module Access

6 participants