Add warnings for deprecation of aggregator-mojos bound to the lifecycle, and add error/error-logging (error when directly in the pom, error-logging when brought in via lifecycle mapping, etc.) when a direct-invocation mojo (@requiresDirectInvocation) is bound to the lifecycle.

git-svn-id: https://svn.apache.org/repos/asf/maven/components/trunk@619711 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
John Dennis Casey 2008-02-08 00:46:16 +00:00
parent 621d238877
commit cc6b280343
8 changed files with 272 additions and 61 deletions

View File

@ -25,6 +25,7 @@ import org.apache.maven.NoGoalsSpecifiedException;
import org.apache.maven.ProjectBuildFailureException;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ReactorManager;
import org.apache.maven.lifecycle.binding.MojoBindingFactory;
@ -62,8 +63,15 @@ import java.util.List;
import java.util.Stack;
/**
* Responsible for orchestrating the process of building the ordered list of
* steps required to achieve the specified set of tasks passed into Maven, then
* executing these mojos in order. This class also manages the various error messages
* that may occur during this process, and directing the behavior of the build
* according to what's specified in {@link MavenExecutionRequest#getReactorFailureBehavior()}.
*
* @author Jason van Zyl
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @author jdcasey
* @version $Id$
* @todo because of aggregation, we ended up with cli-ish stuff in here (like line() and the project logging, without
* much of the event handling)
@ -92,12 +100,7 @@ public class DefaultLifecycleExecutor
// ----------------------------------------------------------------------
/**
* Execute a task. Each task may be a phase in the lifecycle or the execution of a mojo.
*
* @param session
* @param reactorManager
* @param dispatcher
* @throws MojoFailureException
* {@inheritDoc}
*/
public void execute( final MavenSession session,
final ReactorManager reactorManager,
@ -151,6 +154,16 @@ public class DefaultLifecycleExecutor
dispatcher );
}
/**
* After the list of goals from {@link MavenSession#getGoals()} is segmented into
* contiguous sets of aggregated and non-aggregated mojos and lifecycle phases,
* this method is used to execute each task-segment. Its logic has a top-level fork
* for each segment, which basically varies the project used to run the execution
* according to aggregation needs. If the segment is aggregated, the root project
* will be used to construct and execute the mojo bindings. Otherwise, this
* method will iterate through each project, and execute all the goals implied
* by the current task segment.
*/
private void executeTaskSegments( final List taskSegments,
final ReactorManager reactorManager,
final MavenSession session,
@ -216,7 +229,8 @@ public class DefaultLifecycleExecutor
event,
reactorManager,
buildStartTime,
target );
target,
true );
}
catch ( MojoFailureException e )
{
@ -317,7 +331,8 @@ public class DefaultLifecycleExecutor
{
executeGoalAndHandleFailures( binding, session, dispatcher,
event, reactorManager,
buildStartTime, target );
buildStartTime, target,
false);
}
catch ( MojoFailureException e )
{
@ -367,6 +382,17 @@ public class DefaultLifecycleExecutor
}
}
/**
* Since each project can have its own {@link ClassRealm} instance that inherits
* from the core Maven realm, and contains the specific build-extension
* components referenced in that project, the lookup realms must be managed for
* each project that's used to fire off a mojo execution. This helps ensure
* that unsafe {@link PlexusContainer#lookup(String)} and related calls will
* have access to these build-extension components.
* <br />
* This method simply restores the original Maven-core lookup realm when a
* project-specific realm is in use.
*/
private void restoreLookupRealm( ClassRealm oldLookupRealm )
{
if ( oldLookupRealm != null )
@ -375,32 +401,28 @@ public class DefaultLifecycleExecutor
}
}
/**
* Since each project can have its own {@link ClassRealm} instance that inherits
* from the core Maven realm, and contains the specific build-extension
* components referenced in that project, the lookup realms must be managed for
* each project that's used to fire off a mojo execution. This helps ensure
* that unsafe {@link PlexusContainer#lookup(String)} and related calls will
* have access to these build-extension components.
* <br />
* This method is meant to find a project-specific realm, if one exists, for
* use as the lookup realm for unsafe component lookups, using {@link PlexusContainer#setLookupRealm(ClassRealm)}.
*/
private ClassRealm setProjectLookupRealm( MavenSession session,
MavenProject rootProject )
throws LifecycleExecutionException
{
// MavenProjectSession projectSession;
// try
// {
// projectSession = session.getProjectSession( rootProject );
// }
// catch ( PlexusContainerException e )
// {
// throw new LifecycleExecutionException(
// "Failed to create project-specific session for: "
// + rootProject.getId(),
// rootProject, e );
// }
// if ( projectSession != null )
// {
// return container.setLookupRealm( projectSession.getProjectRealm() );
// }
// else
// {
// return null;
// }
ClassRealm projectRealm = session.getRealmManager().getProjectRealm( rootProject.getGroupId(), rootProject.getArtifactId(), rootProject.getVersion() );
if ( projectRealm != null )
{
return container.setLookupRealm( projectRealm );
}
// TODO: Fix this to use project-level realm!
return container.getLookupRealm();
}
@ -444,13 +466,22 @@ public class DefaultLifecycleExecutor
return mojoBindings;
}
/**
* Lookup the plugin containing the referenced mojo, validate that it is
* allowed to execute in the current environment (according to whether
* it's a direct-invocation-only or aggregator mojo, and the allowAggregators
* flag), and execute the mojo. If any of these steps fails, this method will
* consult with the {@link ReactorManager} to determine whether the build
* should be stopped.
*/
private void executeGoalAndHandleFailures( final MojoBinding mojoBinding,
final MavenSession session,
final EventDispatcher dispatcher,
final String event,
final ReactorManager rm,
final long buildStartTime,
final String target )
final String target,
boolean allowAggregators )
throws LifecycleExecutionException, MojoFailureException
{
MavenProject project = session.getCurrentProject();
@ -487,6 +518,9 @@ public class DefaultLifecycleExecutor
}
MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( mojoBinding.getGoal() );
validateMojoExecution( mojoBinding, mojoDescriptor, project, allowAggregators );
MojoExecution mojoExecution = new MojoExecution( mojoDescriptor );
mojoExecution.setConfiguration( (Xpp3Dom) mojoBinding.getConfiguration() );
@ -546,6 +580,90 @@ public class DefaultLifecycleExecutor
}
}
/**
* Verify that the specified {@link MojoBinding} is legal for execution under
* the current circumstances. Currently, this mainly checks that aggregator
* mojos and direct-invocation-only mojos are not bound to lifecycle phases.
* <br/>
* If an invalid mojo is detected, and it is brought in via the user's POM
* (this will be checked using {@link MojoBinding#POM_ORIGIN} and {@link MojoBinding#getOrigin()}),
* then a {@link LifecycleExecutionException} will be thrown. Otherwise, the mojo
* was brought in via a lifecycle mapping or overlay, or as part of a forked execution.
* In these cases, the error will be logged to the console, using the ERROR log-level (since the
* user cannot fix this sort of problem easily).
*/
private void validateMojoExecution( MojoBinding mojoBinding,
MojoDescriptor mojoDescriptor,
MavenProject project,
boolean allowAggregators )
throws LifecycleExecutionException
{
if ( mojoDescriptor.isAggregator() && !allowAggregators )
{
if ( MojoBinding.POM_ORIGIN.equals( mojoBinding.getOrigin() ) )
{
StringBuffer buffer = new StringBuffer();
buffer.append( "\n\nDEPRECATED: Binding aggregator mojos to lifecycle phases in the POM is considered dangerous." );
buffer.append( "\nThis feature has been deprecated. Please adjust your POM files accordingly." );
buffer.append( "\n\nOffending mojo:\n\n" );
buffer.append( MojoBindingUtils.toString( mojoBinding ) );
buffer.append( "\n\nProject: " ).append( project.getId() );
buffer.append( "\nPOM File: " ).append( String.valueOf( project.getFile() ) );
buffer.append( "\n" );
getLogger().warn( buffer.toString() );
}
else
{
StringBuffer buffer = new StringBuffer();
buffer.append( "\n\nDEPRECATED: An aggregator mojo has been bound to your project's build lifecycle." );
buffer.append( "\nThis feature is dangerous, and has been deprecated." );
buffer.append( "\n\nOffending mojo:\n\n" );
buffer.append( MojoBindingUtils.toString( mojoBinding ) );
buffer.append( "\n\nDirect binding of aggregator mojos to the lifecycle is not allowed, but this binding was not configured from within in your POM." );
buffer.append( "\n\nIts origin was: " ).append( mojoBinding.getOrigin() );
if ( mojoBinding.getOriginDescription() != null )
{
buffer.append( " (" ).append( mojoBinding.getOriginDescription() ).append( ")" );
}
buffer.append( "\n" );
getLogger().warn( buffer.toString() );
}
}
else if ( mojoDescriptor.isDirectInvocationOnly() && !MojoBinding.DIRECT_INVOCATION_ORIGIN.equals( mojoBinding.getOrigin() ) )
{
if ( MojoBinding.POM_ORIGIN.equals( mojoBinding.getOrigin() ) )
{
throw new LifecycleExecutionException( "Mojo:\n\n" + MojoBindingUtils.toString( mojoBinding ) + "\n\ncan only be invoked directly by the user. Binding it to lifecycle phases in the POM is not allowed.", project );
}
else
{
StringBuffer buffer = new StringBuffer();
buffer.append( "\n\nSKIPPING execution of mojo:\n\n" ).append( MojoBindingUtils.toString( mojoBinding ) );
buffer.append( "\n\nIt specifies direct-invocation only, but has been bound to the build lifecycle." );
buffer.append( "\n\nDirect-invocation mojos can only be called by the user. This binding was not configured from within in your POM." );
buffer.append( "\n\nIts origin was: " ).append( mojoBinding.getOrigin() );
if ( mojoBinding.getOriginDescription() != null )
{
buffer.append( " (" ).append( mojoBinding.getOriginDescription() ).append( ")" );
}
buffer.append( "\n" );
getLogger().error( buffer.toString() );
}
}
}
/**
* In the event that an error occurs during executeGoalAndHandleFailure(..),
* this method is called to handle logging the error in the {@link ReactorManager},
* then determining (again, from the reactor-manager) whether to stop the build.
*
* @return true if the build should stop, false otherwise.
*/
private boolean handleExecutionFailure( final ReactorManager rm,
final MavenProject project,
final Exception e,
@ -571,6 +689,9 @@ public class DefaultLifecycleExecutor
return false;
}
/**
* {@inheritDoc}
*/
public TaskValidationResult isTaskValid( String task,
MavenSession session,
MavenProject rootProject )
@ -636,6 +757,15 @@ public class DefaultLifecycleExecutor
return new TaskValidationResult();
}
/**
* Split up the list of goals from {@link MavenSession#getGoals()} according
* to aggregation needs. Each adjacent goal in the list is included in a single
* task segment. When the next goal references a different type of mojo or
* lifecycle phase (eg. previous goal wasn't an aggregator, but next one is...or the reverse),
* a new task segment is started and the new goal is added to that.
*
* @return the list of task-segments, each flagged according to aggregation needs.
*/
private List segmentTaskListByAggregationNeeds( final List tasks,
final MavenSession session,
final MavenProject rootProject )
@ -735,6 +865,12 @@ public class DefaultLifecycleExecutor
return segments;
}
/**
* Retrieve the {@link MojoDescriptor} that corresponds to a given direct mojo
* invocation. This is used during the fail-fast method isTaskValid(..), and also
* during task-segmentation, to allow the lifecycle executor to determine whether
* the mojo is an aggregator.
*/
private MojoDescriptor getMojoDescriptorForDirectInvocation( String task,
MavenSession session,
MavenProject project )

View File

@ -20,21 +20,39 @@ package org.apache.maven.lifecycle;
*/
import org.apache.maven.BuildFailureException;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ReactorManager;
import org.apache.maven.monitor.event.EventDispatcher;
import org.apache.maven.project.MavenProject;
/**
* Responsible for orchestrating the process of building the ordered list of
* steps required to achieve the specified set of tasks passed into Maven, then
* executing these mojos in order. This class also manages the various error messages
* that may occur during this process, and directing the behavior of the build
* according to what's specified in {@link MavenExecutionRequest#getReactorFailureBehavior()}.
*
* @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
* @author jdcasey
* @version $Id$
*/
public interface LifecycleExecutor
{
String ROLE = LifecycleExecutor.class.getName();
/**
* Provides a fail-fast way to check that all goals specified in {@link MavenExecutionRequest#getGoals()}
* or {@link MavenSession#getGoals()} is valid.
*/
TaskValidationResult isTaskValid( String task, MavenSession session, MavenProject rootProject );
/**
* Order and execute mojos associated with the current set of projects in the
* reactor. Specific lifecycle phases and mojo invocations that determine what
* phases and mojos this method will attempt to execute are provided in {@link MavenSession#getGoals()},
* which is populated from {@link MavenExecutionRequest#getGoals()}.
*/
void execute( MavenSession session, ReactorManager rm, EventDispatcher dispatcher )
throws LifecycleExecutionException, BuildFailureException;

View File

@ -189,7 +189,7 @@ public class DefaultLifecycleBindingManager
mojoBinding.setGoal( goal );
mojoBinding.setConfiguration( BindingUtils.mergeConfigurations( plugin, execution ) );
mojoBinding.setExecutionId( execution.getId() );
mojoBinding.setOrigin( "POM" );
mojoBinding.setOrigin( MojoBinding.POM_ORIGIN );
logger.debug( "Mojo: " + MojoBindingUtils.toString( mojoBinding ) + ": determining binding phase." );
@ -384,7 +384,8 @@ public class DefaultLifecycleBindingManager
}
binding.setConfiguration( configuration );
binding.setOrigin( lifecycleId );
binding.setOrigin( MojoBinding.LIFECYCLE_MAPPING_ORIGIN );
binding.setOriginDescription( "Lifecycle overlay: " + lifecycleId );
LifecycleUtils.addMojoBinding( phase.getId(), binding, bindings );
phaseBindings.add( binding );
@ -572,7 +573,7 @@ public class DefaultLifecycleBindingManager
binding.setVersion( pluginDescriptor.getVersion() );
binding.setGoal( mojoDescriptor.getGoal() );
binding.setExecutionId( id );
binding.setOrigin( "POM" );
binding.setOrigin( MojoBinding.POM_ORIGIN );
BindingUtils.injectProjectConfiguration( binding, project );

View File

@ -132,6 +132,7 @@ public final class BuildPlanUtils
if ( extendedInfo )
{
listing.append( "\nOrigin: " ).append( binding.getOrigin() );
listing.append( "\nOrigin Description: " ).append( binding.getOriginDescription() );
listing.append( "\nConfiguration:\n\t" ).append(
String.valueOf( binding.getConfiguration() ).replaceAll(
"\\n",

View File

@ -126,6 +126,10 @@ public class DefaultBuildPlanner
project,
session,
true );
binding.setOrigin( MojoBinding.DIRECT_INVOCATION_ORIGIN );
binding.setOriginDescription( "Original reference from user: " + task );
plan.addDirectInvocationBinding( task, binding );
}
}
@ -469,7 +473,8 @@ public class DefaultBuildPlanner
executeGoal,
project );
binding.setOrigin( "Forked from " + referencingGoal );
binding.setOrigin( MojoBinding.FORKED_DIRECT_REFERENCE_ORIGIN );
binding.setOriginDescription( "Forked from: " + MojoBindingUtils.toString( mojoBinding ) );
plan.addForkedExecution( mojoBinding, Collections.singletonList( binding ) );
}

View File

@ -5,9 +5,9 @@ import org.codehaus.plexus.util.xml.Xpp3Dom;
/**
* Constructs and matches MojoBinding instances that refer to the forked-execution context manager mojos.
*
*
* @author jdcasey
*
*
*/
public final class StateManagementUtils
{
@ -16,8 +16,6 @@ public final class StateManagementUtils
public static final String ARTIFACT_ID = "maven-state-management";
public static final String ORIGIN = "Maven build-state management";
public static final String END_FORKED_EXECUTION_GOAL = "end-fork";
public static final String START_FORKED_EXECUTION_GOAL = "start-fork";
@ -46,7 +44,7 @@ public final class StateManagementUtils
binding.setArtifactId( ARTIFACT_ID );
binding.setVersion( VERSION );
binding.setGoal( START_FORKED_EXECUTION_GOAL );
binding.setOrigin( ORIGIN );
binding.setOrigin( MojoBinding.INTERNAL_ORIGIN );
CURRENT_FORK_ID = (int) System.currentTimeMillis();
@ -73,7 +71,7 @@ public final class StateManagementUtils
binding.setArtifactId( ARTIFACT_ID );
binding.setVersion( VERSION );
binding.setGoal( END_FORKED_EXECUTION_GOAL );
binding.setOrigin( ORIGIN );
binding.setOrigin( MojoBinding.INTERNAL_ORIGIN );
Xpp3Dom config = new Xpp3Dom( "configuration" );
Xpp3Dom forkId = new Xpp3Dom( "forkId" );
@ -98,7 +96,7 @@ public final class StateManagementUtils
binding.setArtifactId( ARTIFACT_ID );
binding.setVersion( VERSION );
binding.setGoal( CLEAR_FORKED_EXECUTION_GOAL );
binding.setOrigin( ORIGIN );
binding.setOrigin( MojoBinding.INTERNAL_ORIGIN );
Xpp3Dom config = new Xpp3Dom( "configuration" );
Xpp3Dom forkId = new Xpp3Dom( "forkId" );
@ -156,7 +154,7 @@ public final class StateManagementUtils
binding.setArtifactId( ARTIFACT_ID );
binding.setVersion( VERSION );
binding.setGoal( RESOLVE_LATE_BOUND_PLUGIN_GOAL );
binding.setOrigin( ORIGIN );
binding.setOrigin( MojoBinding.INTERNAL_ORIGIN );
Xpp3Dom config = new Xpp3Dom( "configuration" );
Xpp3Dom param = new Xpp3Dom( "groupId" );

View File

@ -845,13 +845,25 @@ public class DefaultPluginManager
if ( param.getDeprecated() != null )
{
boolean warnOfDeprecation = false;
if ( extractedMojoConfiguration.getChild( param.getName() ) != null )
PlexusConfiguration child = extractedMojoConfiguration.getChild( param.getName() );
try
{
warnOfDeprecation = true;
if ( ( child != null ) && ( child.getValue() != null ) )
{
warnOfDeprecation = true;
}
else if ( param.getAlias() != null)
{
child = extractedMojoConfiguration.getChild( param.getAlias() );
if ( ( child != null ) && ( child.getValue() != null ) )
{
warnOfDeprecation = true;
}
}
}
else if ( ( param.getAlias() != null ) && ( extractedMojoConfiguration.getChild( param.getAlias() ) != null ) )
catch ( PlexusConfigurationException e )
{
warnOfDeprecation = true;
// forget it, this is just for deprecation checking, after all...
}
if ( warnOfDeprecation )

View File

@ -547,17 +547,8 @@
</field>
</fields>
</class>
<class>
<name>LifecycleStep</name>
<version>1.0.0</version>
<description><![CDATA[
Some step in the build process. This could be a mojo, or it could be a signal to start/stop
forked-mode of execution, etc.
]]></description>
</class>
<class>
<name>MojoBinding</name>
<superClass>LifecycleStep</superClass>
<version>1.0.0</version>
<description>A binding of one mojo to one lifecycle phase, possibly including configuration.</description>
<fields>
@ -600,12 +591,6 @@
<description>A name for this mojo binding, for purposes of merging configurations via inheritance, etc.</description>
<type>String</type>
</field>
<field>
<version>1.0.0</version>
<name>origin</name>
<type>String</type>
<description>Specific location from which this set of mojo binding was loaded.</description>
</field>
<field>
<name>configuration</name>
<version>1.0.0</version>
@ -623,8 +608,63 @@
<codeSegment>
<version>1.0.0</version>
<code><![CDATA[
/** origin marker for mojos referenced directly by the user or embedded-mode consumer. */
public static final String DIRECT_INVOCATION_ORIGIN = "Direct invocation";
/** origin marker for single (non-lifecycle) mojos referenced by a forked-execution annotation in another mojo. */
public static final String FORKED_DIRECT_REFERENCE_ORIGIN = "Direct forking reference";
/** origin marker for mojos bound to the lifecycle through POM configuration (not packaging defaults). */
public static final String POM_ORIGIN = "POM";
/** origin marker for mojos bound to the lifecycle by a standard (or default) lifecycle mapping. */
public static final String LIFECYCLE_MAPPING_ORIGIN = "Lifecycle mapping";
/** origin marker for mojos injected into the lifecycle by Maven, to manage lifecycle state. */
public static final String INTERNAL_ORIGIN = "Maven internal state-management";
private String origin;
/**
* Return the origin marker set for this MojoBinding, or null. This will give a general idea of where this
* binding came from.
*/
public String getOrigin()
{
return origin;
}
public void setOrigin( String origin )
{
this.origin = origin;
}
private String originDescription;
/**
* Return specific information about where exactly this binding came from, if it's available.
* This might be used to record the specific forking mojo or the lifecycle
* overlay name that included this mojo, for instance.
*/
public String getOriginDescription()
{
return originDescription;
}
public void setOriginDescription( String originDescription )
{
this.originDescription = originDescription;
}
private boolean lateBound = false;
/**
* Mark this mojo binding as late-bound, meaning the plugin for this mojo
* could not be resolved when it was included in a build plan (the class
* responsible for calculating the ordered list of mojos to execute for a build).
* This flag should result in Maven injecting an internal step into the build
* just ahead of this mojo's execution, to resolve the plugin it refers to.
*/
public boolean isLateBound()
{
return lateBound;