You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by st...@apache.org on 2020/03/18 13:10:01 UTC
[maven] 01/06: [MNG-5668] Proof of concept implementation of
dynamic phases
This is an automated email from the ASF dual-hosted git repository.
stephenc pushed a commit to branch mng-5668-poc
in repository https://gitbox.apache.org/repos/asf/maven.git
commit c107911816f4b62ca5f2c9ca883ad336bf856976
Author: Stephen Connolly <st...@gmail.com>
AuthorDate: Mon Nov 11 18:46:52 2019 +0000
[MNG-5668] Proof of concept implementation of dynamic phases
---
.../internal/DefaultLifecycleMappingDelegate.java | 38 +++-
.../maven/lifecycle/internal/MojoExecutor.java | 49 +++++-
.../maven/lifecycle/internal/PhaseComparator.java | 84 +++++++++
.../lifecycle/internal/PhaseExecutionPoint.java | 54 ++++++
.../apache/maven/lifecycle/internal/PhaseId.java | 196 +++++++++++++++++++++
.../maven/lifecycle/internal/PhaseRecorder.java | 12 +-
maven-plugin-api/src/main/mdo/lifecycle.mdo | 62 ++++++-
7 files changed, 474 insertions(+), 21 deletions(-)
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java
index 892e4f1..a8c6c4b 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DefaultLifecycleMappingDelegate.java
@@ -19,12 +19,6 @@ package org.apache.maven.lifecycle.internal;
* under the License.
*/
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.Lifecycle;
import org.apache.maven.lifecycle.LifecycleMappingDelegate;
@@ -42,6 +36,12 @@ import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
/**
* Lifecycle mapping delegate component interface. Calculates project build execution plan given {@link Lifecycle} and
* lifecycle phase. Standard lifecycles use plugin execution {@code <phase>} or mojo default lifecycle phase to
@@ -67,7 +67,7 @@ public class DefaultLifecycleMappingDelegate
*/
Map<String, Map<Integer, List<MojoExecution>>> mappings =
- new LinkedHashMap<>();
+ new TreeMap<>( new PhaseComparator( lifecycle.getPhases() ) );
for ( String phase : lifecycle.getPhases() )
{
@@ -96,7 +96,8 @@ public class DefaultLifecycleMappingDelegate
// to examine the phase it is associated to.
if ( execution.getPhase() != null )
{
- Map<Integer, List<MojoExecution>> phaseBindings = mappings.get( execution.getPhase() );
+ Map<Integer, List<MojoExecution>> phaseBindings =
+ getPhaseBindings( mappings, execution.getPhase() );
if ( phaseBindings != null )
{
for ( String goal : execution.getGoals() )
@@ -116,7 +117,8 @@ public class DefaultLifecycleMappingDelegate
pluginManager.getMojoDescriptor( plugin, goal, project.getRemotePluginRepositories(),
session.getRepositorySession() );
- Map<Integer, List<MojoExecution>> phaseBindings = mappings.get( mojoDescriptor.getPhase() );
+ Map<Integer, List<MojoExecution>> phaseBindings =
+ getPhaseBindings( mappings, mojoDescriptor.getPhase() );
if ( phaseBindings != null )
{
MojoExecution mojoExecution = new MojoExecution( mojoDescriptor, execution.getId() );
@@ -146,6 +148,24 @@ public class DefaultLifecycleMappingDelegate
}
+ private Map<Integer, List<MojoExecution>> getPhaseBindings( Map<String, Map<Integer, List<MojoExecution>>> mappings,
+ String phase )
+ {
+ Map<Integer, List<MojoExecution>> result = mappings.get( phase );
+ if ( result == null )
+ {
+ // check if this is related to an phase in the plan (pre/post or different priority)
+ PhaseId id = PhaseId.of( phase );
+ if ( mappings.containsKey( id.phase() ) )
+ {
+ // lazy add the phases we need
+ result = new TreeMap<>();
+ mappings.put( phase, result );
+ }
+ }
+ return result;
+ }
+
private void addMojoExecution( Map<Integer, List<MojoExecution>> phaseBindings, MojoExecution mojoExecution,
int priority )
{
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
index b78f54d..df3f34c 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
@@ -44,6 +44,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -143,9 +144,50 @@ public class MojoExecutor
PhaseRecorder phaseRecorder = new PhaseRecorder( session.getCurrentProject() );
- for ( MojoExecution mojoExecution : mojoExecutions )
+ Iterator<MojoExecution> iterator = mojoExecutions.iterator();
+ try
{
- execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
+ while ( iterator.hasNext() )
+ {
+ MojoExecution mojoExecution = iterator.next();
+ execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
+ }
+ }
+ catch ( LifecycleExecutionException failure )
+ {
+ // run any post: executions for the current phase
+ while ( iterator.hasNext() )
+ {
+ MojoExecution mojoExecution = iterator.next();
+ String lifecyclePhase = mojoExecution.getLifecyclePhase();
+ if ( lifecyclePhase == null )
+ {
+ // we have reached an execution that is not bound to a phase, thus there is no post: for last
+ // executed phase
+ break;
+ }
+ if ( phaseRecorder.isDifferentPhase( mojoExecution ) )
+ {
+ // this is a different phase from the last executed phase, thus no more post:
+ break;
+ }
+ PhaseId phaseId = PhaseId.of( lifecyclePhase );
+ if ( phaseId.executionPoint() != PhaseExecutionPoint.AFTER )
+ {
+ // only interested in post: executions
+ continue;
+ }
+ try
+ {
+ execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
+ }
+ catch ( LifecycleExecutionException postFailure )
+ {
+ // failures are tagged as suppressed
+ failure.addSuppressed( postFailure );
+ }
+ }
+ throw failure;
}
}
@@ -209,8 +251,7 @@ public class MojoExecutor
{
pluginManager.executeMojo( session, mojoExecution );
}
- catch ( MojoFailureException | PluginManagerException | PluginConfigurationException
- | MojoExecutionException e )
+ catch ( MojoFailureException | PluginManagerException | PluginConfigurationException | MojoExecutionException e )
{
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java
new file mode 100644
index 0000000..3670c28
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseComparator.java
@@ -0,0 +1,84 @@
+package org.apache.maven.lifecycle.internal;
+
+/*
+ * 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.
+ */
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Compares phases within the context of a specific lifecycle with secondary sorting based on the {@link PhaseId}.
+ */
+public class PhaseComparator
+ implements Comparator<String>
+{
+ /**
+ * The lifecycle phase ordering.
+ */
+ private final List<String> lifecyclePhases;
+
+ /**
+ * Constructor.
+ *
+ * @param lifecyclePhases the lifecycle phase ordering.
+ */
+ public PhaseComparator( List<String> lifecyclePhases )
+ {
+ this.lifecyclePhases = lifecyclePhases;
+ }
+
+ @Override
+ public int compare( String o1, String o2 )
+ {
+ PhaseId p1 = PhaseId.of( o1 );
+ PhaseId p2 = PhaseId.of( o2 );
+ int i1 = lifecyclePhases.indexOf( p1.phase() );
+ int i2 = lifecyclePhases.indexOf( p2.phase() );
+ if ( i1 == -1 && i2 == -1 )
+ {
+ // unknown phases, leave in existing order
+ return 0;
+ }
+ if ( i1 == -1 )
+ {
+ // second one is known, so it comes first
+ return 1;
+ }
+ if ( i2 == -1 )
+ {
+ // first one is known, so it comes first
+ return -1;
+ }
+ int rv = Integer.compare( i1, i2 );
+ if ( rv != 0 )
+ {
+ return rv;
+ }
+ // same phase, now compare execution points
+ i1 = p1.executionPoint().ordinal();
+ i2 = p2.executionPoint().ordinal();
+ rv = Integer.compare( i1, i2 );
+ if ( rv != 0 )
+ {
+ return rv;
+ }
+ // same execution point, now compare priorities (highest wins, so invert)
+ return -Integer.compare( p1.priority(), p2.priority() );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java
new file mode 100644
index 0000000..65313a5
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseExecutionPoint.java
@@ -0,0 +1,54 @@
+package org.apache.maven.lifecycle.internal;
+
+/*
+ * 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.
+ */
+
+/**
+ * Represents where a dynamic phase should be executed within a static phase.
+ */
+public enum PhaseExecutionPoint
+{
+ /**
+ * Execution must occur before any executions of the phase proper. Failure of any {@link #BEFORE} dynamic phase
+ * execution will prevent the {@link #AS} phase but will not prevent any {@link #AFTER} dynamic phases.
+ */
+ BEFORE( "before:" ),
+ /**
+ * Execution is the execution of the phase proper. Failure of any {@link #AS} dynamic phase execution will fail
+ * the phase. Any {@link #AFTER} phases will still be execution.
+ */
+ AS( "" ),
+ /**
+ * Guaranteed execution dynamic phases on completion of the static phase. All {@link #AFTER} dynamic phases will
+ * be executed provided at least one {@link #BEFORE} or {@link #AS} dynamic phase has started execution.
+ */
+ AFTER( "after:" );
+
+ private final String prefix;
+
+ PhaseExecutionPoint( String prefix )
+ {
+ this.prefix = prefix;
+ }
+
+ public String prefix()
+ {
+ return prefix;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java
new file mode 100644
index 0000000..646ac56
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseId.java
@@ -0,0 +1,196 @@
+package org.apache.maven.lifecycle.internal;
+
+/*
+ * 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.
+ */
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Represents a parsed phase identifier.
+ */
+public class PhaseId
+{
+ /**
+ * Interned {@link PhaseId} instances.
+ */
+ private static final Map<String, PhaseId> instances = new WeakHashMap<>();
+
+ /**
+ * The execution point of this {@link PhaseId}.
+ */
+ private final PhaseExecutionPoint executionPoint;
+
+ /**
+ * The static phase that this dynamic phase belongs to.
+ */
+ private final String phase;
+
+ /**
+ * The priority of this dynamic phase within the static phase.
+ */
+ private final int priority;
+
+ /**
+ * Parses the phase identifier.
+ *
+ * @param phase the phase identifier.
+ * @return the {@link PhaseId}.
+ */
+ public static synchronized PhaseId of( String phase )
+ {
+ PhaseId result = instances.get( phase );
+ if ( result == null )
+ {
+ result = new PhaseId( phase );
+ instances.put( phase, result );
+ }
+ return result;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param phase the phase identifier string.
+ */
+ private PhaseId( String phase )
+ {
+ int executionPointEnd = phase.indexOf( ':' );
+ int phaseStart;
+ if ( executionPointEnd == -1 )
+ {
+ executionPoint = PhaseExecutionPoint.AS;
+ phaseStart = 0;
+ }
+ else
+ {
+ switch ( phase.substring( 0, executionPointEnd ) )
+ {
+ case "before":
+ executionPoint = PhaseExecutionPoint.BEFORE;
+ phaseStart = executionPointEnd + 1;
+ break;
+ case "after":
+ executionPoint = PhaseExecutionPoint.AFTER;
+ phaseStart = executionPointEnd + 1;
+ break;
+ default:
+ executionPoint = PhaseExecutionPoint.AS;
+ phaseStart = 0;
+ break;
+ }
+ }
+ int phaseEnd = phase.indexOf( '[' );
+ if ( phaseEnd == -1 )
+ {
+ priority = 0;
+ this.phase = phase.substring( phaseStart );
+ }
+ else
+ {
+ int priorityEnd = phase.lastIndexOf( ']' );
+ boolean havePriority;
+ int priority;
+ if ( priorityEnd < phaseEnd + 1 )
+ {
+ priority = 0;
+ havePriority = false;
+ }
+ else
+ {
+ try
+ {
+ priority = Integer.parseInt( phase.substring( phaseEnd + 1, priorityEnd ) );
+ havePriority = true;
+ }
+ catch ( NumberFormatException e )
+ {
+ // priority must be an integer
+ priority = 0;
+ havePriority = false;
+ }
+ }
+ if ( havePriority )
+ {
+ this.phase = phase.substring( phaseStart, phaseEnd );
+ this.priority = priority;
+ }
+ else
+ {
+ this.phase = phase.substring( phaseStart );
+ this.priority = 0;
+ }
+ }
+ }
+
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o )
+ {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() )
+ {
+ return false;
+ }
+
+ PhaseId phaseId = (PhaseId) o;
+
+ if ( priority() != phaseId.priority() )
+ {
+ return false;
+ }
+ if ( executionPoint() != phaseId.executionPoint() )
+ {
+ return false;
+ }
+ return phase().equals( phaseId.phase() );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = executionPoint().hashCode();
+ result = 31 * result + phase().hashCode();
+ result = 31 * result + priority();
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return executionPoint().prefix() + phase() + ( priority() != 0 ? "[" + priority() + ']' : "" );
+ }
+
+ public PhaseExecutionPoint executionPoint()
+ {
+ return executionPoint;
+ }
+
+ public String phase()
+ {
+ return phase;
+ }
+
+ public int priority()
+ {
+ return priority;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java
index 3316c50..c76f22f 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/PhaseRecorder.java
@@ -24,9 +24,10 @@ import org.apache.maven.project.MavenProject;
/**
* <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
- * @since 3.0
+ *
* @author Benjamin Bentmann
* @author Kristian Rosenvold
+ * @since 3.0
*/
public class PhaseRecorder
{
@@ -45,14 +46,15 @@ public class PhaseRecorder
if ( lifecyclePhase != null )
{
+ PhaseId phaseId = PhaseId.of( lifecyclePhase );
if ( lastLifecyclePhase == null )
{
- lastLifecyclePhase = lifecyclePhase;
+ lastLifecyclePhase = phaseId.phase();
}
- else if ( !lifecyclePhase.equals( lastLifecyclePhase ) )
+ else if ( !phaseId.phase().equals( lastLifecyclePhase ) )
{
project.addLifecyclePhase( lastLifecyclePhase );
- lastLifecyclePhase = lifecyclePhase;
+ lastLifecyclePhase = phaseId.phase();
}
}
@@ -69,7 +71,7 @@ public class PhaseRecorder
{
return lastLifecyclePhase != null;
}
- return !lifecyclePhase.equals( lastLifecyclePhase );
+ return !PhaseId.of( lifecyclePhase ).phase().equals( lastLifecyclePhase );
}
diff --git a/maven-plugin-api/src/main/mdo/lifecycle.mdo b/maven-plugin-api/src/main/mdo/lifecycle.mdo
index 7dfce74..b86fde8 100644
--- a/maven-plugin-api/src/main/mdo/lifecycle.mdo
+++ b/maven-plugin-api/src/main/mdo/lifecycle.mdo
@@ -17,8 +17,8 @@ specific language governing permissions and limitations
under the License.
-->
-<model xmlns="http://codehaus-plexus.github.io/MODELLO/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://codehaus-plexus.github.io/MODELLO/1.0.0 http://codehaus-plexus.github.io/modello/xsd/modello-1.0.0.xsd"
+<model xmlns="http://codehaus-plexus.github.io/MODELLO/1.8.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="https://codehaus-plexus.github.io/MODELLO/1.8.0 https://codehaus-plexus.github.io/modello/xsd/modello-1.8.0.xsd"
xml.namespace="http://maven.apache.org/LIFECYCLE/${version}"
xml.schemaLocation="http://maven.apache.org/xsd/lifecycle-${version}.xsd">
<id>lifecycle-mappings</id>
@@ -79,13 +79,29 @@ under the License.
<version>1.0.0</version>
<description>A phase mapping definition.</description>
<fields>
- <field>
+ <field java.getter="false">
<name>id</name>
<required>true</required>
<version>1.0.0</version>
<type>String</type>
<description>The ID of this phase, e.g., <code>generate-sources</code>.</description>
</field>
+ <field xml.attribute="true">
+ <name>executionPoint</name>
+ <required>false</required>
+ <version>1.0.0</version>
+ <type>String</type>
+ <defaultValue><![CDATA[]]></defaultValue>
+ <description><![CDATA[If specified, identifies this phase as a dynamic phase to decorate the specified phase id, e.g. <code>after</code> or <code>before</code>.]]></description>
+ </field>
+ <field xml.attribute="true">
+ <name>priority</name>
+ <required>false</required>
+ <version>1.0.0</version>
+ <type>int</type>
+ <defaultValue>0</defaultValue>
+ <description>If specified, identifies a within phase prioritization of executions.</description>
+ </field>
<field>
<name>executions</name>
<version>1.0.0</version>
@@ -102,6 +118,46 @@ under the License.
<description>Configuration to pass to all goals run in this phase.</description>
</field>
</fields>
+ <codeSegments>
+ <codeSegment>
+ <version>1.0.0</version>
+ <code><![CDATA[
+ /**
+ * Get the ID of this phase, e.g.,
+ * <code>generate-sources</code>.
+ *
+ * @return String
+ */
+ public String getRawId()
+ {
+ return id;
+ }
+
+ /**
+ * Get the effective ID of this phase, e.g.,
+ * <code>generate-sources</code> or <code>after:integration-test[1000]</code>.
+ *
+ * @return String
+ */
+ public String getId()
+ {
+ if ( executionPoint == null )
+ {
+ if ( priority == 0 )
+ {
+ return id;
+ }
+ return id + '[' + priority + ']';
+ }
+ if ( priority == 0 )
+ {
+ return executionPoint + ':' + id;
+ }
+ return executionPoint + ':' + id + '[' + priority + ']';
+ }
+]]></code>
+ </codeSegment>
+ </codeSegments>
</class>
<class>
<name>Execution</name>