Fixing infinite recursion when a mojo bound to the lifecycle forks a new phase that contains implies another phase that contains the bound mojo. This was triggered by the assembly:assembly mojo, and should look familiar.

git-svn-id: https://svn.apache.org/repos/asf/maven/components/trunk@551696 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
John Dennis Casey 2007-06-28 20:53:55 +00:00
parent 407f924f83
commit f3f3c70321
7 changed files with 444 additions and 41 deletions

View File

@ -150,4 +150,18 @@ under the License.
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/testutils/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -11,6 +11,7 @@ import org.apache.maven.lifecycle.model.MojoBinding;
import org.apache.maven.lifecycle.statemgmt.StateManagementUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@ -127,6 +128,11 @@ public class BuildPlan
invoke.addAll( forkedInvocations );
}
public BuildPlan copy( String task )
{
return copy( Collections.singletonList( task ) );
}
public BuildPlan copy( final List newTasks )
{
return new BuildPlan( bindings, forkedDirectInvocations, forkedPhases, directInvocationBindings, newTasks );
@ -272,4 +278,10 @@ public class BuildPlan
return bindings;
}
public void removeAll( List mojoBindings )
throws NoSuchPhaseException
{
LifecycleUtils.removeMojoBindings( mojoBindings, bindings, false );
}
}

View File

@ -59,8 +59,8 @@ public class DefaultBuildPlanner
initializeDirectInvocations( plan, project );
// Inject forked lifecycles as plan modifiers for each mojo that has @execute in it.
addForkedLifecycleModifiers( plan, project );
addReportingLifecycleModifiers( plan, project );
addForkedLifecycleModifiers( plan, project, new LinkedList() );
addReportingLifecycleModifiers( plan, project, new LinkedList() );
// TODO: Inject relative-ordered project/plugin executions as plan modifiers.
@ -94,8 +94,9 @@ public class DefaultBuildPlanner
/**
* Traverses all MojoBinding instances discovered from the POM and its packaging-mappings, and orchestrates the
* process of injecting any modifiers that are necessary to accommodate forked execution.
* @param callStack
*/
private void addForkedLifecycleModifiers( final BuildPlan plan, final MavenProject project )
private void addForkedLifecycleModifiers( final BuildPlan plan, final MavenProject project, LinkedList callStack )
throws LifecyclePlannerException, LifecycleSpecificationException, LifecycleLoaderException
{
List planBindings = plan.renderExecutionPlan( new Stack() );
@ -105,11 +106,11 @@ public class DefaultBuildPlanner
{
MojoBinding mojoBinding = (MojoBinding) it.next();
findForkModifiers( mojoBinding, plan, project );
findForkModifiers( mojoBinding, plan, project, callStack );
}
}
private void findForkModifiers( final MojoBinding mojoBinding, final BuildPlan plan, final MavenProject project )
private void findForkModifiers( final MojoBinding mojoBinding, final BuildPlan plan, final MavenProject project, LinkedList callStack )
throws LifecyclePlannerException, LifecycleSpecificationException, LifecycleLoaderException
{
PluginDescriptor pluginDescriptor = loadPluginDescriptor( mojoBinding, plan, project );
@ -126,15 +127,16 @@ public class DefaultBuildPlanner
+ pluginDescriptor.getId() + "." );
}
findForkModifiers( mojoBinding, pluginDescriptor, plan, project, false );
findForkModifiers( mojoBinding, pluginDescriptor, plan, project, false, callStack );
}
/**
* Traverses all MojoBinding instances discovered from the POM and its packaging-mappings, and orchestrates the
* process of injecting any modifiers that are necessary to accommodate mojos that require access to the project's
* configured reports.
* @param callStack
*/
private void addReportingLifecycleModifiers( final BuildPlan plan, final MavenProject project )
private void addReportingLifecycleModifiers( final BuildPlan plan, final MavenProject project, LinkedList callStack )
throws LifecyclePlannerException, LifecycleSpecificationException, LifecycleLoaderException
{
List planBindings = plan.renderExecutionPlan( new Stack() );
@ -174,7 +176,7 @@ public class DefaultBuildPlanner
if ( pd != null )
{
findForkModifiers( reportBinding, pd, plan, project, true );
findForkModifiers( reportBinding, pd, plan, project, true, callStack );
}
}
}
@ -218,9 +220,10 @@ public class DefaultBuildPlanner
/**
* Explores a single MojoBinding, and injects any necessary plan modifiers to accommodate any of the three types of
* forked execution, along with any new mojos/lifecycles that entails.
* @param callStack
*/
private void findForkModifiers( final MojoBinding mojoBinding, final PluginDescriptor pluginDescriptor,
final BuildPlan plan, final MavenProject project, final boolean includeReportConfig )
final BuildPlan plan, final MavenProject project, final boolean includeReportConfig, LinkedList callStack )
throws LifecyclePlannerException, LifecycleSpecificationException, LifecycleLoaderException
{
String referencingGoal = mojoBinding.getGoal();
@ -239,7 +242,7 @@ public class DefaultBuildPlanner
}
else if ( mojoDescriptor.getExecutePhase() != null )
{
recursePhaseMojoFork( mojoBinding, pluginDescriptor, plan, project, includeReportConfig );
recursePhaseMojoFork( mojoBinding, pluginDescriptor, plan, project, includeReportConfig, callStack );
}
}
@ -254,48 +257,59 @@ public class DefaultBuildPlanner
*/
private void recursePhaseMojoFork( final MojoBinding mojoBinding, final PluginDescriptor pluginDescriptor,
final BuildPlan plan, final MavenProject project,
final boolean includeReportConfig )
final boolean includeReportConfig, LinkedList callStack )
throws LifecyclePlannerException, LifecycleSpecificationException, LifecycleLoaderException
{
String referencingGoal = mojoBinding.getGoal();
callStack.addFirst( mojoBinding );
MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( referencingGoal );
String phase = mojoDescriptor.getExecutePhase();
if ( phase == null )
try
{
return;
}
String referencingGoal = mojoBinding.getGoal();
if ( !LifecycleUtils.isValidPhaseName( phase ) )
{
throw new LifecyclePlannerException( "Cannot find lifecycle for phase: " + phase );
}
MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( referencingGoal );
BuildPlan clonedPlan = plan.copy( Collections.singletonList( phase ) );
String phase = mojoDescriptor.getExecutePhase();
String executeLifecycle = mojoDescriptor.getExecuteLifecycle();
if ( executeLifecycle != null )
{
LifecycleBindings overlayBindings;
try
if ( phase == null )
{
overlayBindings =
lifecycleBindingManager.getPluginLifecycleOverlay( pluginDescriptor, executeLifecycle, project, includeReportConfig );
}
catch ( LifecycleLoaderException e )
{
throw new LifecyclePlannerException( "Failed to load overlay lifecycle: " + executeLifecycle
+ ". Reason: " + e.getMessage(), e );
return;
}
clonedPlan.addLifecycleOverlay( overlayBindings );
if ( !LifecycleUtils.isValidPhaseName( phase ) )
{
throw new LifecyclePlannerException( "Cannot find lifecycle for phase: " + phase );
}
BuildPlan clonedPlan = plan.copy( phase );
clonedPlan.removeAll( callStack );
String executeLifecycle = mojoDescriptor.getExecuteLifecycle();
if ( executeLifecycle != null )
{
LifecycleBindings overlayBindings;
try
{
overlayBindings =
lifecycleBindingManager.getPluginLifecycleOverlay( pluginDescriptor, executeLifecycle, project, includeReportConfig );
}
catch ( LifecycleLoaderException e )
{
throw new LifecyclePlannerException( "Failed to load overlay lifecycle: " + executeLifecycle
+ ". Reason: " + e.getMessage(), e );
}
clonedPlan.addLifecycleOverlay( overlayBindings );
}
plan.addForkedExecution( mojoBinding, clonedPlan );
addForkedLifecycleModifiers( clonedPlan, project, callStack );
}
finally
{
callStack.removeFirst();
}
plan.addForkedExecution( mojoBinding, clonedPlan );
addForkedLifecycleModifiers( clonedPlan, project );
}
/**
@ -303,6 +317,7 @@ public class DefaultBuildPlanner
* off to the
* {@link DefaultBuildPlanner#modifyBuildPlanForForkedDirectInvocation(MojoBinding, MojoBinding, PluginDescriptor, ModifiablePlanElement, LifecycleBindings, MavenProject, LinkedList, List)}
* method to actually inject the modification.
* @param callStack
*/
private void recurseSingleMojoFork( final MojoBinding mojoBinding, final PluginDescriptor pluginDescriptor,
final BuildPlan plan, final MavenProject project, final boolean includeReportConfig )

View File

@ -76,11 +76,64 @@ public class BuildPlanTest
assertBindings( check, executionPlan );
}
public void testRender_MojoBoundToPackagePhaseAndForkingPackagePhaseGetsFilteredOut()
throws LifecycleSpecificationException, LifecycleLoaderException
{
MojoBinding mb = new MojoBinding();
mb.setGroupId( "test" );
mb.setArtifactId( "test-plugin" );
mb.setVersion( "1" );
mb.setGoal( "validate" );
BuildBinding binding = new BuildBinding();
binding.getValidate().addBinding( mb );
MojoBinding mb2 = new MojoBinding();
mb2.setGroupId( "test" );
mb2.setArtifactId( "test-plugin" );
mb2.setVersion( "1" );
mb2.setGoal( "generate-sources" );
binding.getGenerateSources().addBinding( mb2 );
MojoBinding assemblyBinding = new MojoBinding();
assemblyBinding.setGroupId( "org.apache.maven.plugins" );
assemblyBinding.setArtifactId( "maven-assembly-plugin" );
assemblyBinding.setVersion( "2.1" );
assemblyBinding.setGoal( "assembly" );
binding.getCreatePackage().addBinding( assemblyBinding );
List check = new ArrayList();
check.add( mb );
check.add( mb2 );
check.add( StateManagementUtils.createStartForkedExecutionMojoBinding() );
check.add( mb );
check.add( mb2 );
check.add( StateManagementUtils.createEndForkedExecutionMojoBinding() );
check.add( assemblyBinding );
check.add( StateManagementUtils.createClearForkedExecutionMojoBinding() );
LifecycleBindings bindings = new LifecycleBindings();
bindings.setBuildBinding( binding );
List tasks = Collections.singletonList( "package" );
BuildPlan plan = new BuildPlan( bindings, tasks );
plan.addForkedExecution( assemblyBinding, plan.copy( "package" ) );
List executionPlan = plan.renderExecutionPlan( new Stack() );
assertBindings( check, executionPlan );
}
private void assertBindings( final List check, final List executionPlan )
{
assertNotNull( executionPlan );
System.out.println( "Expected execution plan:\n" + String.valueOf( check ).replace( ',', '\n' ) );
System.out.println( "\n\nExpected execution plan:\n" + String.valueOf( check ).replace( ',', '\n' ) );
System.out.println( "\nActual execution plan:\n" + String.valueOf( executionPlan ).replace( ',', '\n' ) );
assertEquals( "Execution plan does not contain the expected number of mojo bindings.", check.size(),

View File

@ -0,0 +1,109 @@
package org.apache.maven.lifecycle.plan;
import org.apache.maven.lifecycle.LifecycleLoaderException;
import org.apache.maven.lifecycle.LifecycleSpecificationException;
import org.apache.maven.lifecycle.MojoBindingUtils;
import org.apache.maven.lifecycle.model.MojoBinding;
import org.apache.maven.lifecycle.plan.testutils.TestPluginLoader;
import org.apache.maven.model.Build;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.loader.PluginLoader;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.PlexusTestCase;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
public class DefaultBuildPlannerTest
extends PlexusTestCase
{
private DefaultBuildPlanner buildPlanner;
private TestPluginLoader pluginLoader;
protected void setUp()
throws Exception
{
super.setUp();
buildPlanner = (DefaultBuildPlanner) lookup( BuildPlanner.class.getName(), "default" );
pluginLoader = (TestPluginLoader) lookup( PluginLoader.class.getName(), "default" );
}
public void test_constructBuildPlan_ForkedPhaseFromMojoBoundInThatPhase()
throws LifecycleLoaderException, LifecycleSpecificationException, LifecyclePlannerException
{
Model model = new Model();
Build build = new Build();
model.setBuild( build );
Plugin plugin = new Plugin();
plugin.setGroupId( "org.apache.maven.plugins" );
plugin.setArtifactId( "maven-assembly-plugin" );
plugin.setVersion( "1" );
build.addPlugin( plugin );
PluginExecution exec = new PluginExecution();
exec.setId( "assembly" );
exec.setPhase( "package" );
exec.addGoal( "assembly" );
plugin.addExecution( exec );
PluginDescriptor pd = TestPluginLoader.createPluginDescriptor( plugin.getArtifactId(), "assembly",
plugin.getGroupId(), plugin.getVersion() );
MojoDescriptor md = TestPluginLoader.createMojoDescriptor( pd, "assembly" );
md.setExecutePhase( "package" );
pluginLoader.addPluginDescriptor( pd );
MavenProject project = new MavenProject( model );
BuildPlan plan = buildPlanner.constructBuildPlan( Collections.singletonList( "package" ), project );
List rendered = plan.renderExecutionPlan( new Stack() );
List checkIds = new ArrayList();
checkIds.add( "org.apache.maven.plugins:maven-resources-plugin:resources" );
checkIds.add( "org.apache.maven.plugins:maven-compiler-plugin:compile" );
checkIds.add( "org.apache.maven.plugins:maven-resources-plugin:testResources" );
checkIds.add( "org.apache.maven.plugins:maven-compiler-plugin:testCompile" );
checkIds.add( "org.apache.maven.plugins:maven-surefire-plugin:test" );
checkIds.add( "org.apache.maven.plugins:maven-jar-plugin:jar" );
checkIds.add( "org.apache.maven.plugins.internal:maven-state-management:2.1:start-fork" );
checkIds.add( "org.apache.maven.plugins:maven-resources-plugin:resources" );
checkIds.add( "org.apache.maven.plugins:maven-compiler-plugin:compile" );
checkIds.add( "org.apache.maven.plugins:maven-resources-plugin:testResources" );
checkIds.add( "org.apache.maven.plugins:maven-compiler-plugin:testCompile" );
checkIds.add( "org.apache.maven.plugins:maven-surefire-plugin:test" );
checkIds.add( "org.apache.maven.plugins:maven-jar-plugin:jar" );
checkIds.add( "org.apache.maven.plugins.internal:maven-state-management:2.1:end-fork" );
checkIds.add( "org.apache.maven.plugins:maven-assembly-plugin:1:assembly" );
checkIds.add( "org.apache.maven.plugins.internal:maven-state-management:2.1:clear-fork-context" );
assertBindingIds( rendered, checkIds );
}
private void assertBindingIds( List bindings, List checkIds )
{
assertEquals( checkIds.size(), bindings.size() );
for ( int i = 0; i < bindings.size(); i++ )
{
MojoBinding binding = (MojoBinding) bindings.get( i );
String checkId = (String) checkIds.get( i );
assertEquals( checkId, MojoBindingUtils.toString( binding ) );
}
}
}

View File

@ -0,0 +1,172 @@
package org.apache.maven.lifecycle.plan.testutils;
import org.apache.maven.lifecycle.MojoBindingUtils;
import org.apache.maven.lifecycle.model.MojoBinding;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.loader.PluginLoader;
import org.apache.maven.plugin.loader.PluginLoaderException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import java.util.HashMap;
import java.util.Map;
public class TestPluginLoader
implements PluginLoader
{
private static final Map DEFAULT_PLUGIN_DESCRIPTORS;
private static final Map DEFAULT_PLUGIN_PREFIXES;
static
{
Map descriptors = new HashMap();
Map prefixes = new HashMap();
{
PluginDescriptor pd = createPluginDescriptor( "maven-resources-plugin", "resources",
"org.apache.maven.plugins", "1" );
createMojoDescriptor( pd, "resources" );
createMojoDescriptor( pd, "testResources" );
descriptors.put( pd.getId(), pd );
prefixes.put( pd.getGoalPrefix(), pd );
}
{
PluginDescriptor pd = createPluginDescriptor( "maven-compiler-plugin", "compiler",
"org.apache.maven.plugins", "1" );
createMojoDescriptor( pd, "compile" );
createMojoDescriptor( pd, "testCompile" );
descriptors.put( pd.getId(), pd );
prefixes.put( pd.getGoalPrefix(), pd );
}
{
PluginDescriptor pd = createPluginDescriptor( "maven-surefire-plugin", "surefire",
"org.apache.maven.plugins", "1" );
createMojoDescriptor( pd, "test" );
descriptors.put( pd.getId(), pd );
prefixes.put( pd.getGoalPrefix(), pd );
}
{
PluginDescriptor pd = createPluginDescriptor( "maven-jar-plugin", "jar", "org.apache.maven.plugins", "1" );
createMojoDescriptor( pd, "jar" );
descriptors.put( pd.getId(), pd );
prefixes.put( pd.getGoalPrefix(), pd );
}
DEFAULT_PLUGIN_DESCRIPTORS = descriptors;
DEFAULT_PLUGIN_PREFIXES = prefixes;
}
private Map pluginDescriptors = new HashMap( DEFAULT_PLUGIN_DESCRIPTORS );
private Map pluginPrefixes = new HashMap( DEFAULT_PLUGIN_PREFIXES );
private Map components = new HashMap();
public static MojoDescriptor createMojoDescriptor( PluginDescriptor pd, String goal )
{
MojoDescriptor md = new MojoDescriptor();
md.setPluginDescriptor( pd );
md.setGoal( goal );
pd.addComponentDescriptor( md );
return md;
}
public static PluginDescriptor createPluginDescriptor( String artifactId, String goalPrefix, String groupId,
String version )
{
PluginDescriptor pd = new PluginDescriptor();
pd.setGroupId( groupId );
pd.setArtifactId( artifactId );
pd.setGoalPrefix( goalPrefix );
pd.setVersion( version );
return pd;
}
public PluginDescriptor findPluginForPrefix( String prefix, MavenProject project )
throws PluginLoaderException
{
// System.out.println( "Find plugin for prefix: " + prefix + " in project: " + project.getId() );
return (PluginDescriptor) pluginPrefixes.get( prefix );
}
public PluginDescriptor loadPlugin( Plugin plugin, MavenProject project )
throws PluginLoaderException
{
// System.out.println( "Load plugin from model definition: " + plugin.getKey() + " in project: " + project.getId() );
return (PluginDescriptor) pluginDescriptors.get( plugin.getKey() );
}
public PluginDescriptor loadPlugin( MojoBinding mojoBinding, MavenProject project )
throws PluginLoaderException
{
// System.out.println( "Load plugin for mojo binding: " + MojoBindingUtils.toString( mojoBinding )
// + " in project: " + project.getId() );
return (PluginDescriptor) pluginDescriptors.get( MojoBindingUtils.createPluginKey( mojoBinding ) );
}
public Object loadPluginComponent( String role, String roleHint, Plugin plugin, MavenProject project )
throws ComponentLookupException, PluginLoaderException
{
// System.out.println( "Load plugin component: " + role + "/" + roleHint + " from plugin: " + plugin.getKey()
// + " in project: " + project.getId() );
String key = createKey( role, roleHint, plugin.getGroupId(), plugin.getArtifactId() );
return components.get( key );
}
private String createKey( String role, String roleHint, String groupId, String artifactId )
{
return groupId + ":" + artifactId + ":" + role + ":" + roleHint;
}
public PluginDescriptor loadReportPlugin( ReportPlugin plugin, MavenProject project )
throws PluginLoaderException
{
System.out.println( "Load report plugin from model definition: " + plugin.getKey() + " in project: "
+ project.getId() );
return (PluginDescriptor) pluginDescriptors.get( plugin.getKey() );
}
public PluginDescriptor loadReportPlugin( MojoBinding mojoBinding, MavenProject project )
throws PluginLoaderException
{
System.out.println( "Load report plugin for mojo binding: " + MojoBindingUtils.toString( mojoBinding )
+ " in project: " + project.getId() );
return (PluginDescriptor) pluginDescriptors.get( MojoBindingUtils.createPluginKey( mojoBinding ) );
}
public void addPluginDescriptor( PluginDescriptor pd )
{
pluginDescriptors.put( pd.getId(), pd );
pluginPrefixes.put( pd.getGoalPrefix(), pd );
}
public void addPluginComponent( String groupId, String artifactId, String role, String roleHint, Object component )
{
components.put( createKey( role, roleHint, groupId, artifactId ), component );
}
}

View File

@ -0,0 +1,28 @@
<!--
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.
-->
<component-set>
<components>
<component>
<role>org.apache.maven.plugin.loader.PluginLoader</role>
<role-hint>default</role-hint>
<implementation>org.apache.maven.lifecycle.plan.testutils.TestPluginLoader</implementation>
</component>
</components>
</component-set>