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 {