This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-help-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new 2976b29  Fix issue 367 (#372)
2976b29 is described below

commit 2976b29088b810b042d80a67832c0419aa21576a
Author: Tamas Cservenak <[email protected]>
AuthorDate: Tue Jun 30 12:13:48 2026 +0200

    Fix issue 367 (#372)
    
    Fixes https://github.com/apache/maven/issues/12181
    
    Fix issue #367
    
    Lift UT from other PR
---
 pom.xml                                            |   6 +
 src/it/projects/describe-cmd/verify.groovy         |   2 +-
 .../apache/maven/plugins/help/DescribeMojo.java    |  44 ++++---
 .../maven/plugins/help/DescribeMojoTest.java       | 128 +++++++++++++++++++++
 4 files changed, 156 insertions(+), 24 deletions(-)

diff --git a/pom.xml b/pom.xml
index f31dae1..b35e6c2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -253,6 +253,12 @@
       <version>4.11.0</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>1.7.36</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/src/it/projects/describe-cmd/verify.groovy 
b/src/it/projects/describe-cmd/verify.groovy
index b46af06..de3cdb4 100644
--- a/src/it/projects/describe-cmd/verify.groovy
+++ b/src/it/projects/describe-cmd/verify.groovy
@@ -22,7 +22,7 @@ def result = new File(basedir, 'result-deploy.txt').text;
 def ls = System.getProperty( "line.separator" );
 
 // used deprecated methods - FIXME in DescribeMojo
-if (mavenVersion.startsWith('4.') || mavenVersion.startsWith('3.10.')) {
+if (mavenVersion.startsWith('4.')) {
     assert result.contains("'deploy' is a phase within the 'default' 
lifecycle, which has the following phases:")
 } else {
     assert result.contains("'deploy' is a phase corresponding to this plugin:" 
+ ls +
diff --git a/src/main/java/org/apache/maven/plugins/help/DescribeMojo.java 
b/src/main/java/org/apache/maven/plugins/help/DescribeMojo.java
index 9caa5cf..17948d6 100644
--- a/src/main/java/org/apache/maven/plugins/help/DescribeMojo.java
+++ b/src/main/java/org/apache/maven/plugins/help/DescribeMojo.java
@@ -29,7 +29,6 @@ import java.net.URLClassLoader;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -39,6 +38,8 @@ import org.apache.maven.lifecycle.DefaultLifecycles;
 import org.apache.maven.lifecycle.Lifecycle;
 import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
 import org.apache.maven.lifecycle.mapping.LifecycleMapping;
+import org.apache.maven.lifecycle.mapping.LifecycleMojo;
+import org.apache.maven.lifecycle.mapping.LifecyclePhase;
 import org.apache.maven.model.Plugin;
 import org.apache.maven.model.building.ModelBuildingRequest;
 import org.apache.maven.plugin.MavenPluginManager;
@@ -496,7 +497,7 @@ public class DescribeMojo extends AbstractHelpMojo {
             deprecation = NO_REASON;
         }
 
-        if (deprecation != null && !deprecation.isEmpty()) {
+        if (deprecation != null) {
             append(
                     buffer,
                     MessageUtils.buffer().warning("Deprecated. " + 
deprecation).build(),
@@ -624,7 +625,7 @@ public class DescribeMojo extends AbstractHelpMojo {
                 deprecation = NO_REASON;
             }
 
-            if (deprecation != null && !deprecation.isEmpty()) {
+            if (deprecation != null) {
                 append(
                         buffer,
                         MessageUtils.buffer()
@@ -650,15 +651,16 @@ public class DescribeMojo extends AbstractHelpMojo {
                 throw new MojoExecutionException("The given phase '" + cmd + 
"' is an unknown phase.");
             }
 
-            // FIXME don't use a deprecated methods
-            Map<String, String> defaultLifecyclePhases = lifecycleMappings
-                    .get(project.getPackaging())
-                    .getLifecycles()
-                    .get("default")
-                    .getPhases();
             List<String> phases = lifecycle.getPhases();
 
-            if (lifecycle.getDefaultPhases() == null) {
+            if (lifecycle.getDefaultLifecyclePhases() == null
+                    || lifecycle.getDefaultLifecyclePhases().isEmpty()) {
+                Map<String, LifecyclePhase> defaultLifecyclePhases = 
lifecycleMappings
+                        .get(project.getPackaging())
+                        .getLifecycles()
+                        .get("default")
+                        .getLifecyclePhases();
+
                 descriptionBuffer.append("'").append(cmd);
                 descriptionBuffer
                         .append("' is a phase corresponding to this plugin:")
@@ -680,17 +682,13 @@ public class DescribeMojo extends AbstractHelpMojo {
                 descriptionBuffer.append(LS);
                 for (String key : phases) {
                     descriptionBuffer.append("* ").append(key).append(": ");
-                    String value = defaultLifecyclePhases.get(key);
-                    if (value != null && !value.isEmpty()) {
-                        for (StringTokenizer tok = new StringTokenizer(value, 
","); tok.hasMoreTokens(); ) {
-                            descriptionBuffer.append(tok.nextToken().trim());
-
-                            if (!tok.hasMoreTokens()) {
-                                descriptionBuffer.append(LS);
-                            } else {
-                                descriptionBuffer.append(", ");
-                            }
-                        }
+                    LifecyclePhase phase = defaultLifecyclePhases.get(key);
+                    if (phase != null && !phase.getMojos().isEmpty()) {
+                        descriptionBuffer
+                                .append(phase.getMojos().stream()
+                                        .map(LifecycleMojo::getGoal)
+                                        .collect(Collectors.joining(", ")))
+                                .append(LS);
                     } else {
                         descriptionBuffer.append(NOT_DEFINED).append(LS);
                     }
@@ -703,9 +701,9 @@ public class DescribeMojo extends AbstractHelpMojo {
 
                 for (String key : phases) {
                     descriptionBuffer.append("* ").append(key).append(": ");
-                    if (lifecycle.getDefaultPhases().get(key) != null) {
+                    if (lifecycle.getDefaultLifecyclePhases().get(key) != 
null) {
                         descriptionBuffer
-                                .append(lifecycle.getDefaultPhases().get(key))
+                                
.append(lifecycle.getDefaultLifecyclePhases().get(key))
                                 .append(LS);
                     } else {
                         descriptionBuffer.append(NOT_DEFINED).append(LS);
diff --git a/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java 
b/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java
index fe458c6..5d64e37 100644
--- a/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java
@@ -21,12 +21,21 @@ package org.apache.maven.plugins.help;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import org.apache.maven.execution.MavenSession;
+import org.apache.maven.lifecycle.DefaultLifecycles;
+import org.apache.maven.lifecycle.Lifecycle;
 import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
+import org.apache.maven.lifecycle.mapping.LifecycleMapping;
+import org.apache.maven.lifecycle.mapping.LifecyclePhase;
 import org.apache.maven.model.Plugin;
 import org.apache.maven.plugin.MavenPluginManager;
+import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.descriptor.MojoDescriptor;
 import org.apache.maven.plugin.descriptor.Parameter;
 import org.apache.maven.plugin.descriptor.PluginDescriptor;
@@ -40,6 +49,7 @@ import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -348,6 +358,124 @@ class DescribeMojoTest {
         }
     }
 
+    /**
+     * Regression test for Maven 3.10.0 behavior where {@code 
Lifecycle.getDefaultLifecyclePhases()}
+     * returns an empty map (not null) for the "default" lifecycle.
+     * The mojo must fall through to the packaging-specific lifecycle mapping 
in that case.
+     */
+    @Test
+    void testDescribeCommandPackagingSpecificPhaseShowsBindings() throws 
Exception {
+        // default lifecycle: built-in phases are empty (3.10.0 behavior)
+        Lifecycle lifecycle = mock(Lifecycle.class);
+        when(lifecycle.getId()).thenReturn("default");
+        when(lifecycle.getPhases()).thenReturn(Arrays.asList("validate", 
"compile", "test", "package"));
+        
when(lifecycle.getDefaultLifecyclePhases()).thenReturn(Collections.emptyMap());
+
+        Map<String, Lifecycle> phaseMap = new HashMap<>();
+        for (String p : Arrays.asList("validate", "compile", "test", 
"package")) {
+            phaseMap.put(p, lifecycle);
+        }
+        DefaultLifecycles defaultLifecycles = mock(DefaultLifecycles.class);
+        when(defaultLifecycles.getPhaseToLifecycleMap()).thenReturn(phaseMap);
+
+        // packaging-specific bindings: only "compile" phase is bound
+        Map<String, LifecyclePhase> lifecyclePhases = new LinkedHashMap<>();
+        lifecyclePhases.put(
+                "compile", new 
LifecyclePhase("org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile"));
+        org.apache.maven.lifecycle.mapping.Lifecycle mappingLifecycle =
+                new org.apache.maven.lifecycle.mapping.Lifecycle();
+        mappingLifecycle.setLifecyclePhases(lifecyclePhases);
+
+        LifecycleMapping lifecycleMapping = mock(LifecycleMapping.class);
+        
when(lifecycleMapping.getLifecycles()).thenReturn(Collections.singletonMap("default",
 mappingLifecycle));
+
+        MavenProject project = mock(MavenProject.class);
+        when(project.getPackaging()).thenReturn("jar");
+
+        DescribeMojo mojo = new DescribeMojo(
+                null, null, null, null, null, defaultLifecycles, 
Collections.singletonMap("jar", lifecycleMapping));
+        setFieldWithReflection(mojo, "cmd", "compile");
+        setParentFieldWithReflection(mojo, "project", project);
+
+        StringBuilder sb = new StringBuilder();
+        Method describeCommand = 
DescribeMojo.class.getDeclaredMethod("describeCommand", StringBuilder.class);
+        describeCommand.setAccessible(true);
+        boolean result = (boolean) describeCommand.invoke(mojo, sb);
+
+        String output = sb.toString();
+        assertFalse(result);
+        assertTrue(output.contains("maven-compiler-plugin"), "Should show 
compiler plugin: " + output);
+        assertFalse(output.contains("* compile: Not defined"), "compile should 
not be 'Not defined': " + output);
+        assertTrue(output.contains("* validate: Not defined"), "validate has 
no binding: " + output);
+        assertTrue(output.contains("It is a part of the lifecycle for the POM 
packaging 'jar'"), output);
+    }
+
+    /**
+     * Built-in lifecycle (e.g. "clean") has non-empty {@code 
getDefaultLifecyclePhases()} —
+     * the mojo should use those directly without consulting lifecycle 
mappings.
+     */
+    @Test
+    void testDescribeCommandBuiltinLifecyclePhaseShowsBindings() throws 
Exception {
+        Map<String, LifecyclePhase> builtinPhases = new LinkedHashMap<>();
+        builtinPhases.put("pre-clean", null);
+        builtinPhases.put("clean", new 
LifecyclePhase("org.apache.maven.plugins:maven-clean-plugin:3.2.0:clean"));
+        builtinPhases.put("post-clean", null);
+
+        Lifecycle lifecycle = mock(Lifecycle.class);
+        when(lifecycle.getId()).thenReturn("clean");
+        when(lifecycle.getPhases()).thenReturn(Arrays.asList("pre-clean", 
"clean", "post-clean"));
+        when(lifecycle.getDefaultLifecyclePhases()).thenReturn(builtinPhases);
+
+        Map<String, Lifecycle> phaseMap = new HashMap<>();
+        for (String p : Arrays.asList("pre-clean", "clean", "post-clean")) {
+            phaseMap.put(p, lifecycle);
+        }
+        DefaultLifecycles defaultLifecycles = mock(DefaultLifecycles.class);
+        when(defaultLifecycles.getPhaseToLifecycleMap()).thenReturn(phaseMap);
+
+        MavenProject project = mock(MavenProject.class);
+        when(project.getPackaging()).thenReturn("jar");
+
+        DescribeMojo mojo = new DescribeMojo(null, null, null, null, null, 
defaultLifecycles, Collections.emptyMap());
+        setFieldWithReflection(mojo, "cmd", "clean");
+        setFieldWithReflection(mojo, "lifecycleMappings", 
Collections.emptyMap());
+        setParentFieldWithReflection(mojo, "project", project);
+
+        StringBuilder sb = new StringBuilder();
+        Method describeCommand = 
DescribeMojo.class.getDeclaredMethod("describeCommand", StringBuilder.class);
+        describeCommand.setAccessible(true);
+        boolean result = (boolean) describeCommand.invoke(mojo, sb);
+
+        String output = sb.toString();
+        assertFalse(result);
+        assertTrue(output.contains("'clean' is a phase within the 'clean' 
lifecycle"), output);
+        assertTrue(output.contains("maven-clean-plugin"), output);
+        assertTrue(output.contains("* pre-clean: Not defined"), output);
+        assertTrue(output.contains("* post-clean: Not defined"), output);
+    }
+
+    @Test
+    void testDescribeCommandUnknownPhaseThrows() throws Exception {
+        DefaultLifecycles defaultLifecycles = mock(DefaultLifecycles.class);
+        
when(defaultLifecycles.getPhaseToLifecycleMap()).thenReturn(Collections.emptyMap());
+
+        DescribeMojo mojo = new DescribeMojo(null, null, null, null, null, 
defaultLifecycles, Collections.emptyMap());
+        setFieldWithReflection(mojo, "cmd", "nonexistent-phase");
+        setParentFieldWithReflection(mojo, "project", 
mock(MavenProject.class));
+
+        Method describeCommand = 
DescribeMojo.class.getDeclaredMethod("describeCommand", StringBuilder.class);
+        describeCommand.setAccessible(true);
+        try {
+            describeCommand.invoke(mojo, new StringBuilder());
+            fail("Expected MojoExecutionException");
+        } catch (InvocationTargetException e) {
+            assertTrue(
+                    e.getTargetException() instanceof MojoExecutionException,
+                    "Expected MojoExecutionException, got: " + 
e.getTargetException());
+            
assertTrue(e.getTargetException().getMessage().contains("nonexistent-phase"));
+        }
+    }
+
     private static void setParentFieldWithReflection(
             final DescribeMojo mojo, final String fieldName, final Object 
value)
             throws NoSuchFieldException, IllegalAccessException {

Reply via email to