From 83aa2b8806926d40a83ed2a214aba1423884af35 Mon Sep 17 00:00:00 2001 From: goutamadwant Date: Sun, 9 Aug 2026 22:50:01 -0700 Subject: [PATCH 1/2] [MNG-8693] Avoid resolving unused plugins for direct goals Skip project-wide plugin version resolution for direct-goal-only plans while preserving it for lifecycle phases. Resolve versionless project prefix candidates lazily and add regression coverage for both paths. --- ...faultLifecycleExecutionPlanCalculator.java | 4 +- ...DefaultLifecycleTaskSegmentCalculator.java | 8 +- .../internal/MojoDescriptorCreator.java | 14 +- .../ConcurrentLifecycleStarter.java | 6 - .../internal/DefaultPluginPrefixResolver.java | 16 ++- ...tLifecycleExecutionPlanCalculatorTest.java | 64 +++++++++ .../LifecycleExecutionPlanCalculatorTest.java | 5 +- .../LifecycleTaskSegmentCalculatorStub.java | 2 +- .../DefaultPluginPrefixResolverTest.java | 123 ++++++++++++++++++ 9 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java create mode 100644 impl/maven-core/src/test/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolverTest.java diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java index e35bd8df1ca3..ceed80112a20 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java +++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculator.java @@ -122,7 +122,9 @@ public MavenExecutionPlan calculateExecutionPlan( throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException, PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException, NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException { - lifecyclePluginResolver.resolveMissingPluginVersions(project, session); + if (tasks.stream().anyMatch(LifecycleTask.class::isInstance)) { + lifecyclePluginResolver.resolveMissingPluginVersions(project, session); + } final List executions = calculateMojoExecutions(session, project, tasks); diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleTaskSegmentCalculator.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleTaskSegmentCalculator.java index 6d17a835db70..72cd9af62b7b 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleTaskSegmentCalculator.java +++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleTaskSegmentCalculator.java @@ -60,13 +60,9 @@ public class DefaultLifecycleTaskSegmentCalculator implements LifecycleTaskSegme private final MojoDescriptorCreator mojoDescriptorCreator; - private final LifecyclePluginResolver lifecyclePluginResolver; - @Inject - public DefaultLifecycleTaskSegmentCalculator( - MojoDescriptorCreator mojoDescriptorCreator, LifecyclePluginResolver lifecyclePluginResolver) { + public DefaultLifecycleTaskSegmentCalculator(MojoDescriptorCreator mojoDescriptorCreator) { this.mojoDescriptorCreator = mojoDescriptorCreator; - this.lifecyclePluginResolver = lifecyclePluginResolver; } @Override @@ -108,8 +104,6 @@ public List calculateTaskSegments(MavenSession session, List plugins) { @@ -237,15 +234,6 @@ public MojoDescriptor getMojoDescriptor(String task, MavenSession session, Maven public Plugin findPluginForPrefix(String prefix, MavenSession session) throws NoPluginFoundForPrefixException { // [prefix]:[goal] - if (session.getCurrentProject() != null) { - try { - lifecyclePluginResolver.resolveMissingPluginVersions(session.getCurrentProject(), session); - } catch (PluginVersionResolutionException e) { - // not critical here - logger.debug(e.getMessage(), e); - } - } - PluginPrefixRequest prefixRequest = new DefaultPluginPrefixRequest(prefix, session); PluginPrefixResult prefixResult = pluginPrefixResolver.resolve(prefixRequest); diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLifecycleStarter.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLifecycleStarter.java index 85067ac02cf3..b430f07d264d 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLifecycleStarter.java +++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLifecycleStarter.java @@ -35,7 +35,6 @@ import org.apache.maven.lifecycle.NoGoalSpecifiedException; import org.apache.maven.lifecycle.internal.ExecutionEventCatapult; import org.apache.maven.lifecycle.internal.GoalTask; -import org.apache.maven.lifecycle.internal.LifecyclePluginResolver; import org.apache.maven.lifecycle.internal.LifecycleStarter; import org.apache.maven.lifecycle.internal.LifecycleTask; import org.apache.maven.lifecycle.internal.MojoDescriptorCreator; @@ -61,7 +60,6 @@ public class ConcurrentLifecycleStarter implements LifecycleStarter { private final ExecutionEventCatapult eventCatapult; private final DefaultLifecycles defaultLifeCycles; private final BuildPlanExecutor executor; - private final LifecyclePluginResolver lifecyclePluginResolver; private final MojoDescriptorCreator mojoDescriptorCreator; @Inject @@ -69,12 +67,10 @@ public ConcurrentLifecycleStarter( ExecutionEventCatapult eventCatapult, DefaultLifecycles defaultLifeCycles, BuildPlanExecutor executor, - LifecyclePluginResolver lifecyclePluginResolver, MojoDescriptorCreator mojoDescriptorCreator) { this.eventCatapult = eventCatapult; this.defaultLifeCycles = defaultLifeCycles; this.executor = executor; - this.lifecyclePluginResolver = lifecyclePluginResolver; this.mojoDescriptorCreator = mojoDescriptorCreator; } @@ -149,8 +145,6 @@ public List calculateTaskSegments(MavenSession session, List plugins) { for (Plugin plugin : plugins) { try { + if (plugin.getVersion() == null) { + DefaultPluginVersionRequest versionRequest = new DefaultPluginVersionRequest( + plugin, request.getRepositorySession(), request.getRepositories()) + .setPom(request.getPom()); + plugin.setVersion( + pluginVersionResolver.resolve(versionRequest).getVersion()); + } PluginDescriptor pluginDescriptor = pluginManager.loadPlugin(plugin, request.getRepositories(), request.getRepositorySession()); diff --git a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java new file mode 100644 index 000000000000..90f3e2e06751 --- /dev/null +++ b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.lifecycle.internal; + +import java.util.List; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.lifecycle.DefaultLifecycles; +import org.apache.maven.plugin.BuildPluginManager; +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.project.MavenProject; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class DefaultLifecycleExecutionPlanCalculatorTest { + @Test + void doesNotResolveProjectPluginsForDirectGoal() throws Exception { + MojoDescriptorCreator mojoDescriptorCreator = mock(MojoDescriptorCreator.class); + LifecyclePluginResolver lifecyclePluginResolver = mock(LifecyclePluginResolver.class); + MavenSession session = mock(MavenSession.class); + MavenProject project = new MavenProject(); + + PluginDescriptor pluginDescriptor = new PluginDescriptor(); + pluginDescriptor.setGroupId("org.apache.maven.plugins"); + pluginDescriptor.setArtifactId("maven-help-plugin"); + pluginDescriptor.setVersion("1.0"); + MojoDescriptor mojoDescriptor = new MojoDescriptor(); + mojoDescriptor.setPluginDescriptor(pluginDescriptor); + mojoDescriptor.setGoal("help"); + when(mojoDescriptorCreator.getMojoDescriptor("help:help", session, project)) + .thenReturn(mojoDescriptor); + + DefaultLifecycleExecutionPlanCalculator calculator = new DefaultLifecycleExecutionPlanCalculator( + mock(BuildPluginManager.class), + mock(DefaultLifecycles.class), + mojoDescriptorCreator, + lifecyclePluginResolver); + + calculator.calculateExecutionPlan(session, project, List.of(new GoalTask("help:help")), false); + + verify(lifecyclePluginResolver, never()).resolveMissingPluginVersions(project, session); + } +} diff --git a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleExecutionPlanCalculatorTest.java b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleExecutionPlanCalculatorTest.java index de70d7321d8a..be34aeec0522 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleExecutionPlanCalculatorTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleExecutionPlanCalculatorTest.java @@ -71,10 +71,7 @@ public static LifecycleExecutionPlanCalculator createExecutionPlaceCalculator( public static MojoDescriptorCreator createMojoDescriptorCreator() { return new MojoDescriptorCreator( - new PluginVersionResolverStub(), - new BuildPluginManagerStub(), - new PluginPrefixResolverStub(), - new LifecyclePluginResolver(new PluginVersionResolverStub())); + new PluginVersionResolverStub(), new BuildPluginManagerStub(), new PluginPrefixResolverStub()); } @Override diff --git a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/LifecycleTaskSegmentCalculatorStub.java b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/LifecycleTaskSegmentCalculatorStub.java index 75091493390a..edf5b2d49bc5 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/LifecycleTaskSegmentCalculatorStub.java +++ b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/LifecycleTaskSegmentCalculatorStub.java @@ -44,7 +44,7 @@ public class LifecycleTaskSegmentCalculatorStub extends DefaultLifecycleTaskSegm public static final String INSTALL = "install"; public LifecycleTaskSegmentCalculatorStub() { - super(null, null); + super(null); } @Override diff --git a/impl/maven-core/src/test/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolverTest.java b/impl/maven-core/src/test/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolverTest.java new file mode 100644 index 000000000000..b4f3aedabb84 --- /dev/null +++ b/impl/maven-core/src/test/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolverTest.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.plugin.prefix.internal; + +import java.util.List; + +import org.apache.maven.artifact.repository.metadata.io.MetadataReader; +import org.apache.maven.model.Build; +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.plugin.BuildPluginManager; +import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugin.prefix.DefaultPluginPrefixRequest; +import org.apache.maven.plugin.prefix.PluginPrefixResult; +import org.apache.maven.plugin.version.PluginVersionResolver; +import org.apache.maven.plugin.version.PluginVersionResult; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class DefaultPluginPrefixResolverTest { + @Mock + private BuildPluginManager pluginManager; + + @Mock + private RepositorySystem repositorySystem; + + @Mock + private MetadataReader metadataReader; + + @Mock + private PluginVersionResolver pluginVersionResolver; + + @Mock + private RepositorySystemSession repositorySession; + + @Mock + private PluginVersionResult pluginVersionResult; + + @Test + void resolvesVersionOnlyForMatchingProjectPlugin() throws Exception { + Plugin matchingPlugin = plugin("com.example", "custom-maven-plugin"); + Plugin unrelatedPlugin = plugin("org.apache.maven.plugins", "maven-release-plugin"); + + Build build = new Build(); + build.setPlugins(List.of(unrelatedPlugin, matchingPlugin)); + Model model = new Model(); + model.setBuild(build); + + when(pluginVersionResult.getVersion()).thenReturn("1.0"); + when(pluginVersionResolver.resolve(any())).thenReturn(pluginVersionResult); + + PluginDescriptor descriptor = new PluginDescriptor(); + descriptor.setGroupId(matchingPlugin.getGroupId()); + descriptor.setArtifactId(matchingPlugin.getArtifactId()); + descriptor.setVersion("1.0"); + descriptor.setGoalPrefix("custom"); + when(pluginManager.loadPlugin( + argThat(plugin -> matchingPlugin.getArtifactId().equals(plugin.getArtifactId()) + && "1.0".equals(plugin.getVersion())), + eq(List.of()), + eq(repositorySession))) + .thenReturn(descriptor); + + DefaultPluginPrefixResolver resolver = + new DefaultPluginPrefixResolver(pluginManager, repositorySystem, metadataReader, pluginVersionResolver); + PluginPrefixResult result = resolver.resolve(new DefaultPluginPrefixRequest() + .setPrefix("custom") + .setPom(model) + .setRepositorySession(repositorySession)); + + assertEquals(matchingPlugin.getGroupId(), result.getGroupId()); + assertEquals(matchingPlugin.getArtifactId(), result.getArtifactId()); + + verify(pluginVersionResolver) + .resolve(argThat(request -> + matchingPlugin.getArtifactId().equals(request.getArtifactId()) && request.getPom() == model)); + verify(pluginVersionResolver, never()) + .resolve(argThat(request -> unrelatedPlugin.getArtifactId().equals(request.getArtifactId()))); + verify(pluginManager, never()) + .loadPlugin( + argThat(plugin -> unrelatedPlugin.getArtifactId().equals(plugin.getArtifactId())), + any(), + eq(repositorySession)); + verifyNoInteractions(repositorySystem); + } + + private Plugin plugin(String groupId, String artifactId) { + Plugin plugin = new Plugin(); + plugin.setGroupId(groupId); + plugin.setArtifactId(artifactId); + return plugin; + } +} From 84fcb7651e4165dc169a64bd39ef2915b4637ce7 Mon Sep 17 00:00:00 2001 From: goutamadwant Date: Sun, 9 Aug 2026 23:13:40 -0700 Subject: [PATCH 2/2] [MNG-8693] Verify lifecycle plugin resolution Add the complementary positive regression that lifecycle tasks continue to resolve missing project plugin versions before lifecycle mapping. --- ...tLifecycleExecutionPlanCalculatorTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java index 90f3e2e06751..0a6951a61f46 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/DefaultLifecycleExecutionPlanCalculatorTest.java @@ -19,9 +19,12 @@ package org.apache.maven.lifecycle.internal; import java.util.List; +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.LifecycleMappingDelegate; import org.apache.maven.plugin.BuildPluginManager; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptor; @@ -61,4 +64,31 @@ void doesNotResolveProjectPluginsForDirectGoal() throws Exception { verify(lifecyclePluginResolver, never()).resolveMissingPluginVersions(project, session); } + + @Test + void resolvesProjectPluginsForLifecycleTask() throws Exception { + LifecyclePluginResolver lifecyclePluginResolver = mock(LifecyclePluginResolver.class); + MavenSession session = mock(MavenSession.class); + MavenProject project = new MavenProject(); + DefaultLifecycles defaultLifecycles = mock(DefaultLifecycles.class); + Lifecycle lifecycle = new Lifecycle("default", List.of("validate"), Map.of()); + LifecycleMappingDelegate lifecycleMappingDelegate = mock(LifecycleMappingDelegate.class); + + when(defaultLifecycles.get("validate")).thenReturn(lifecycle); + when(lifecycleMappingDelegate.calculateLifecycleMappings(session, project, lifecycle, "validate")) + .thenReturn(Map.of()); + + DefaultLifecycleExecutionPlanCalculator calculator = new DefaultLifecycleExecutionPlanCalculator( + mock(BuildPluginManager.class), + defaultLifecycles, + mock(MojoDescriptorCreator.class), + lifecyclePluginResolver, + lifecycleMappingDelegate, + Map.of(), + Map.of()); + + calculator.calculateExecutionPlan(session, project, List.of(new LifecycleTask("validate")), false); + + verify(lifecyclePluginResolver).resolveMissingPluginVersions(project, session); + } }