From c967fe28ec65e0d5a4b554cf968b2a60c82ca82c Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 23:52:33 +0200 Subject: [PATCH 1/2] Add regression test for Maven 3.10 lifecycle bindings Model Maven 3.10's empty default lifecycle map and verify that help:describe uses the packaging-specific lifecycle mapping. --- .../maven/plugins/help/DescribeMojoTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) 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 135ad5b9..d9db00ab 100644 --- a/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java @@ -21,10 +21,18 @@ 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.descriptor.MojoDescriptor; @@ -353,6 +361,104 @@ public void testLookupPluginDescriptorAMissingG() { } } + /** + * Regression test for Maven 3.10, where the default lifecycle has an empty map of built-in bindings. The mojo must + * use the packaging-specific lifecycle mapping in that case. + */ + @Test + public void testDescribeCommandPackagingSpecificPhaseShowsBindings() throws Exception { + Lifecycle lifecycle = mock(Lifecycle.class); + when(lifecycle.getId()).thenReturn("default"); + when(lifecycle.getPhases()).thenReturn(Arrays.asList("validate", "compile", "test", "package")); + when(lifecycle.getDefaultPhases()).thenReturn(Collections.emptyMap()); + when(lifecycle.getDefaultLifecyclePhases()).thenReturn(Collections.emptyMap()); + + Map phaseMap = new HashMap<>(); + for (String phase : Arrays.asList("validate", "compile", "test", "package")) { + phaseMap.put(phase, lifecycle); + } + DefaultLifecycles defaultLifecycles = mock(DefaultLifecycles.class); + when(defaultLifecycles.getPhaseToLifecycleMap()).thenReturn(phaseMap); + + Map legacyPhases = new LinkedHashMap<>(); + legacyPhases.put("compile", "org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile"); + Map 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 = + mock(org.apache.maven.lifecycle.mapping.Lifecycle.class); + when(mappingLifecycle.getPhases()).thenReturn(legacyPhases); + when(mappingLifecycle.getLifecyclePhases()).thenReturn(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(); + setFieldWithReflection(mojo, "cmd", "compile"); + setFieldWithReflection(mojo, "defaultLifecycles", defaultLifecycles); + setFieldWithReflection(mojo, "lifecycleMappings", Collections.singletonMap("jar", lifecycleMapping)); + setParentFieldWithReflection(mojo, "project", project); + + StringBuilder buffer = new StringBuilder(); + Method describeCommand = DescribeMojo.class.getDeclaredMethod("describeCommand", StringBuilder.class); + describeCommand.setAccessible(true); + boolean result = (boolean) describeCommand.invoke(mojo, buffer); + + String output = buffer.toString(); + assertFalse(result); + assertTrue("Should show the compiler plugin: " + output, output.contains("maven-compiler-plugin")); + assertFalse("compile should not be 'Not defined': " + output, output.contains("* compile: Not defined")); + assertTrue("validate has no binding: " + output, output.contains("* validate: Not defined")); + assertTrue(output, output.contains("It is a part of the lifecycle for the POM packaging 'jar'")); + } + + @Test + public void testDescribeCommandBuiltinLifecyclePhaseShowsBindings() throws Exception { + Map legacyPhases = new LinkedHashMap<>(); + legacyPhases.put("clean", "org.apache.maven.plugins:maven-clean-plugin:3.2.0:clean"); + Map lifecyclePhases = new LinkedHashMap<>(); + lifecyclePhases.put("pre-clean", null); + lifecyclePhases.put("clean", new LifecyclePhase("org.apache.maven.plugins:maven-clean-plugin:3.2.0:clean")); + lifecyclePhases.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.getDefaultPhases()).thenReturn(legacyPhases); + when(lifecycle.getDefaultLifecyclePhases()).thenReturn(lifecyclePhases); + + Map phaseMap = new HashMap<>(); + for (String phase : Arrays.asList("pre-clean", "clean", "post-clean")) { + phaseMap.put(phase, 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(); + setFieldWithReflection(mojo, "cmd", "clean"); + setFieldWithReflection(mojo, "defaultLifecycles", defaultLifecycles); + setFieldWithReflection(mojo, "lifecycleMappings", Collections.emptyMap()); + setParentFieldWithReflection(mojo, "project", project); + + StringBuilder buffer = new StringBuilder(); + Method describeCommand = DescribeMojo.class.getDeclaredMethod("describeCommand", StringBuilder.class); + describeCommand.setAccessible(true); + boolean result = (boolean) describeCommand.invoke(mojo, buffer); + + String output = buffer.toString(); + assertFalse(result); + assertTrue(output, output.contains("'clean' is a phase within the 'clean' lifecycle")); + assertTrue(output, output.contains("maven-clean-plugin")); + assertTrue(output, output.contains("* pre-clean: Not defined")); + assertTrue(output, output.contains("* post-clean: Not defined")); + } + private static void setParentFieldWithReflection( final DescribeMojo mojo, final String fieldName, final Object value) throws NoSuchFieldException, IllegalAccessException { From 3b7a960a0e4217fc8dec3af6a1114bed68aa2939 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Mon, 10 Aug 2026 23:53:23 +0200 Subject: [PATCH 2/2] Use current lifecycle mapping APIs Treat an empty default lifecycle map like a missing map so Maven 3.10 falls back to packaging-specific bindings. Replace the deprecated string mappings with the typed lifecycle APIs available since Maven 3.6.3. --- .../maven/plugins/help/DescribeMojo.java | 39 +++++++++---------- .../maven/plugins/help/DescribeMojoTest.java | 7 ---- 2 files changed, 19 insertions(+), 27 deletions(-) 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 7cf0ec8d..95ed9d21 100644 --- a/src/main/java/org/apache/maven/plugins/help/DescribeMojo.java +++ b/src/main/java/org/apache/maven/plugins/help/DescribeMojo.java @@ -27,7 +27,6 @@ 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; @@ -37,6 +36,8 @@ 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; @@ -633,14 +634,16 @@ private boolean describeCommand(StringBuilder descriptionBuffer) throws MojoExec throw new MojoExecutionException("The given phase '" + cmd + "' is an unknown phase."); } - Map defaultLifecyclePhases = lifecycleMappings - .get(project.getPackaging()) - .getLifecycles() - .get("default") - .getPhases(); List phases = lifecycle.getPhases(); - if (lifecycle.getDefaultPhases() == null) { + if (lifecycle.getDefaultLifecyclePhases() == null + || lifecycle.getDefaultLifecyclePhases().isEmpty()) { + Map defaultLifecyclePhases = lifecycleMappings + .get(project.getPackaging()) + .getLifecycles() + .get("default") + .getLifecyclePhases(); + descriptionBuffer.append("'").append(cmd); descriptionBuffer .append("' is a phase corresponding to this plugin:") @@ -662,17 +665,13 @@ private boolean describeCommand(StringBuilder descriptionBuffer) throws MojoExec 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); } @@ -685,9 +684,9 @@ private boolean describeCommand(StringBuilder descriptionBuffer) throws MojoExec 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 d9db00ab..23c3f2dd 100644 --- a/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java @@ -370,7 +370,6 @@ public void testDescribeCommandPackagingSpecificPhaseShowsBindings() throws Exce Lifecycle lifecycle = mock(Lifecycle.class); when(lifecycle.getId()).thenReturn("default"); when(lifecycle.getPhases()).thenReturn(Arrays.asList("validate", "compile", "test", "package")); - when(lifecycle.getDefaultPhases()).thenReturn(Collections.emptyMap()); when(lifecycle.getDefaultLifecyclePhases()).thenReturn(Collections.emptyMap()); Map phaseMap = new HashMap<>(); @@ -380,15 +379,12 @@ public void testDescribeCommandPackagingSpecificPhaseShowsBindings() throws Exce DefaultLifecycles defaultLifecycles = mock(DefaultLifecycles.class); when(defaultLifecycles.getPhaseToLifecycleMap()).thenReturn(phaseMap); - Map legacyPhases = new LinkedHashMap<>(); - legacyPhases.put("compile", "org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile"); Map 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 = mock(org.apache.maven.lifecycle.mapping.Lifecycle.class); - when(mappingLifecycle.getPhases()).thenReturn(legacyPhases); when(mappingLifecycle.getLifecyclePhases()).thenReturn(lifecyclePhases); LifecycleMapping lifecycleMapping = mock(LifecycleMapping.class); when(lifecycleMapping.getLifecycles()).thenReturn(Collections.singletonMap("default", mappingLifecycle)); @@ -417,8 +413,6 @@ public void testDescribeCommandPackagingSpecificPhaseShowsBindings() throws Exce @Test public void testDescribeCommandBuiltinLifecyclePhaseShowsBindings() throws Exception { - Map legacyPhases = new LinkedHashMap<>(); - legacyPhases.put("clean", "org.apache.maven.plugins:maven-clean-plugin:3.2.0:clean"); Map lifecyclePhases = new LinkedHashMap<>(); lifecyclePhases.put("pre-clean", null); lifecyclePhases.put("clean", new LifecyclePhase("org.apache.maven.plugins:maven-clean-plugin:3.2.0:clean")); @@ -427,7 +421,6 @@ public void testDescribeCommandBuiltinLifecyclePhaseShowsBindings() throws Excep Lifecycle lifecycle = mock(Lifecycle.class); when(lifecycle.getId()).thenReturn("clean"); when(lifecycle.getPhases()).thenReturn(Arrays.asList("pre-clean", "clean", "post-clean")); - when(lifecycle.getDefaultPhases()).thenReturn(legacyPhases); when(lifecycle.getDefaultLifecyclePhases()).thenReturn(lifecyclePhases); Map phaseMap = new HashMap<>();