gnodet-bot commented on code in PR #11029:
URL: https://github.com/apache/maven/pull/11029#discussion_r4062356486


##########
impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java:
##########
@@ -529,21 +549,41 @@ public <T> T getConfiguredMojo(Class<T> mojoInterface, 
MavenSession session, Moj
         // We are forcing the use of the plugin realm for all lookups that 
might occur during
         // the lifecycle that is part of the lookup. Here we are specifically 
trying to keep
         // lookups that occur in contextualize calls in line with the right 
realm.
-        ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
+        ClassRealm oldLookupRealm =
+                
container.setLookupRealm((org.codehaus.plexus.classworlds.realm.ClassRealm) 
pluginRealm);
 
+        // For modular plugins, set TCCL to the ModuleLayer's classloader
         ClassLoader oldClassLoader = 
Thread.currentThread().getContextClassLoader();
-        Thread.currentThread().setContextClassLoader(pluginRealm);
+        ClassLoader effectiveLoader = getEffectiveClassLoader(pluginRealm);
+        Thread.currentThread().setContextClassLoader(effectiveLoader);
 
         try {
-            if (mojoDescriptor.isV4Api()) {
+            if (pluginDescriptor.isModular()) {
+                // Modular plugins always use the v4 Mojo loading path with 
maven-di
+                return loadV4Mojo(mojoInterface, session, mojoExecution, 
mojoDescriptor, pluginDescriptor, pluginRealm);
+            } else if (mojoDescriptor.isV4Api()) {
                 return loadV4Mojo(mojoInterface, session, mojoExecution, 
mojoDescriptor, pluginDescriptor, pluginRealm);
             } else {
                 return loadV3Mojo(mojoInterface, session, mojoExecution, 
mojoDescriptor, pluginDescriptor, pluginRealm);
             }
         } finally {
             Thread.currentThread().setContextClassLoader(oldClassLoader);
-            container.setLookupRealm(oldLookupRealm);
+            
container.setLookupRealm((org.codehaus.plexus.classworlds.realm.ClassRealm) 
oldLookupRealm);
+        }
+    }
+
+    /**
+     * Returns the effective classloader for a plugin realm.
+     * For modular plugins, returns the ModuleLayer's classloader;
+     * for classic plugins, returns the realm's own classloader.
+     */
+    private static ClassLoader getEffectiveClassLoader(ClassRealm pluginRealm) 
{
+        if (pluginRealm.isModular()) {
+            ModuleLayer layer = pluginRealm.getModuleLayer();
+            // defineModulesWithOneLoader uses a single loader for all modules 
in the layer
+            return layer.modules().iterator().next().getClassLoader();

Review Comment:
   ⚠️ **Fragile: `layer.modules().iterator().next()` is order-dependent**
   
   `ModuleLayer.modules()` returns a `Set<Module>` with unspecified iteration 
order. For a single-module plugin this is harmless today, but for any plugin 
with multiple modules in its layer this picks an arbitrary module's classloader.
   
   `ModuleLayer.defineModulesWithOneLoader()` guarantees **one shared 
ClassLoader** for all modules in the layer, so `module.getClassLoader()` 
returns the same instance regardless of which module you pick. The intent is 
obscured and the code will silently break if `defineModulesWithManyLoaders` is 
used.
   
   Use `pluginRealm.getClassLoader()` directly — the realm's URLClassLoader is 
the same parent passed to `defineModulesWithOneLoader`, already holding the 
full classpath from `populateRealm`:
   
   ```suggestion
               return pluginRealm.getClassLoader();
   ```



##########
apache-maven/src/assembly/component.xml:
##########
@@ -23,14 +23,25 @@ under the License.
       <useProjectArtifact>false</useProjectArtifact>
       <outputDirectory>boot</outputDirectory>
       <includes>
-        <include>org.codehaus.plexus:plexus-classworlds</include>
+        <include>org.apache.maven:maven-classworlds</include>
+        <include>org.apache.maven:maven-api-classworlds</include>
       </includes>
     </dependencySet>
+
+    <dependencySet>
+      <useProjectArtifact>false</useProjectArtifact>
+      <outputDirectory>lib/modules</outputDirectory>
+      <includes>
+        <include>org.jline:*</include>
+      </includes>
+    </dependencySet>
+
     <dependencySet>
       <useProjectArtifact>false</useProjectArtifact>
       <outputDirectory>lib</outputDirectory>
       <excludes>
-        <exclude>org.codehaus.plexus:plexus-classworlds</exclude>
+        <exclude>org.apache.maven:maven-classworlds</exclude>
+        <exclude>org.apache.maven:maven-api-classworlds</exclude>

Review Comment:
   ⚠️ **Bug: JLine deployed to both `lib/modules/` and `lib/`**
   
   The second `dependencySet` places all `org.jline:*` artifacts into 
`lib/modules/`. The third `dependencySet` (for `lib/`) has no exclusion for 
`org.jline`, so the Maven assembly plugin copies JLine to **both** directories.
   
   At runtime, `lib/*.jar` lands on the unnamed-module classpath (loaded via 
`m2.conf` `load ${maven.home}/lib/*.jar`), while `lib/modules/*.jar` is placed 
on the `--module-path`. Running the same JLine classes as both a named module 
(from `lib/modules/`) and an unnamed-module class (from `lib/`) produces a 
split-package violation — the JVM will refuse to create the module layer 
(`LayerInstantiationException`) or silently shadow the named module, defeating 
the purpose of this change.
   
   Fix: add `<exclude>org.jline:*</exclude>` to the `lib/` dependencySet:
   
   ```suggestion
           <exclude>org.apache.maven:maven-classworlds</exclude>
           <exclude>org.apache.maven:maven-api-classworlds</exclude>
           <exclude>org.jline:*</exclude>
   ```



##########
impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginRealmCache.java:
##########
@@ -29,11 +29,11 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.maven.RepositoryUtils;
+import org.apache.maven.api.classworlds.ClassRealm;

Review Comment:
   ⚠️ **Cache correctness: `CacheKey` does not include the `modular` flag**
   
   `CacheKey` is keyed on `(plugin, parentRealm, foreignImports, filter, 
repositories, session)` with no `modular` field. If the same plugin coordinate 
appears in two projects in a multi-module build — one with 
`<modular>true</modular>` and one without — the second lookup gets a cache hit 
and receives the wrong realm type. A classpath realm returned where a 
`ModuleLayer` realm was expected (or vice versa) causes a `ClassCastException` 
in `getConfiguredMojo` or `Injector.discover()` failing to find module 
descriptors.
   
   Add `boolean modular` to `CacheKey` and include it in `hashCode`/`equals`. 
The `createKey(...)` factory needs a corresponding `boolean modular` parameter, 
propagated from `PluginDescriptor.isModular()` at the call site in 
`DefaultMavenPluginManager.setupPluginRealm`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to