Skip to content

Commit

Permalink
[MNG-5668] Dynamic phases
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenc authored and gnodet committed Jun 12, 2024
1 parent 6a29a86 commit 70e4122
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 22 deletions.
2 changes: 1 addition & 1 deletion api/maven-api-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ under the License.
<phase>generate-sources</phase>
<configuration>
<velocityBasedir>${project.basedir}/../../src/mdo</velocityBasedir>
<version>1.0.0</version>
<version>2.0.0</version>
<models>
<model>src/main/mdo/lifecycle.mdo</model>
</models>
Expand Down
67 changes: 54 additions & 13 deletions api/maven-api-plugin/src/main/mdo/lifecycle.mdo
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ under the License.
<classes>
<class rootElement="true" xml.tagName="lifecycles" xsd.compositor="sequence">
<name>LifecycleConfiguration</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<description>Root element of the {@code lifecycle.xml} file.</description>
<fields>
<field>
<name>lifecycles</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<association xml.itemsStyle="flat">
<type>Lifecycle</type>
<multiplicity>*</multiplicity>
Expand All @@ -45,19 +45,19 @@ under the License.
</class>
<class>
<name>Lifecycle</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<description>A custom lifecycle mapping definition.</description>
<fields>
<field>
<name>id</name>
<required>true</required>
<version>1.0.0</version>
<version>1.0.0+</version>
<type>String</type>
<description>The ID of this lifecycle, for identification in the mojo descriptor.</description>
</field>
<field>
<name>phases</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<description>The phase mappings for this lifecycle.</description>
<association>
<type>Phase</type>
Expand All @@ -68,19 +68,35 @@ under the License.
</class>
<class>
<name>Phase</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<description>A phase mapping definition.</description>
<fields>
<field>
<name>id</name>
<required>true</required>
<version>1.0.0</version>
<version>1.0.0+</version>
<type>String</type>
<description>The ID of this phase, e.g., &lt;code&gt;generate-sources&lt;/code&gt;.</description>
<description>The ID of this phase, e.g., {@code generate-sources}.</description>
</field>
<field xml.attribute="true">
<name>executionPoint</name>
<required>false</required>
<version>2.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} or {@code before}.]]></description>
</field>
<field xml.attribute="true">
<name>priority</name>
<required>false</required>
<version>2.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>
<version>1.0.0+</version>
<description>The goals to execute within the phase.</description>
<association>
<type>Execution</type>
Expand All @@ -89,26 +105,51 @@ under the License.
</field>
<field>
<name>configuration</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<type>DOM</type>
<description>Configuration to pass to all goals run in this phase.</description>
</field>
</fields>
<codeSegments>
<codeSegment>
<version>2.0.0+</version>
<code><![CDATA[
/**
* Get the effective ID of this phase, e.g.,
* {@code generate-sources} or {@code after:integration-test[1000]}.
*
* @return String
*/
public String getEffectiveId() {
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>
<version>1.0.0</version>
<version>1.0.0+</version>
<description>A set of goals to execute.</description>
<fields>
<field>
<name>configuration</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<type>DOM</type>
<description>Configuration to pass to the goals.</description>
</field>
<field>
<name>goals</name>
<version>1.0.0</version>
<version>1.0.0+</version>
<description>The goals to execute.</description>
<association>
<type>String</type>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public Map<String, List<MojoExecution>> calculateLifecycleMappings(
* is interested in, i.e. all phases up to and including the specified phase.
*/

Map<String, Map<Integer, List<MojoExecution>>> mappings = new LinkedHashMap<>();
Map<String, Map<Integer, List<MojoExecution>>> mappings =
new TreeMap<>(new PhaseComparator(lifecycle.getPhases()));

for (String phase : lifecycle.getPhases()) {
Map<Integer, List<MojoExecution>> phaseBindings = new TreeMap<>();
Expand All @@ -94,7 +95,7 @@ public Map<String, List<MojoExecution>> calculateLifecycleMappings(
// if the phase is specified then I don't have to go fetch the plugin yet and pull it down
// 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()) {
MojoExecution mojoExecution = new MojoExecution(plugin, goal, execution.getId());
Expand All @@ -109,7 +110,8 @@ public Map<String, List<MojoExecution>> calculateLifecycleMappings(
MojoDescriptor mojoDescriptor = 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());
mojoExecution.setLifecyclePhase(mojoDescriptor.getPhase());
Expand All @@ -135,6 +137,15 @@ public Map<String, List<MojoExecution>> calculateLifecycleMappings(
return lifecycleMappings;
}

private Map<Integer, List<MojoExecution>> getPhaseBindings(
Map<String, Map<Integer, List<MojoExecution>>> mappings, String phase) {
if (phase != null) {
PhaseId id = PhaseId.of(phase);
return mappings.get(id.phase());
}
return null;
}

private void addMojoExecution(
Map<Integer, List<MojoExecution>> phaseBindings, MojoExecution mojoExecution, int priority) {
List<MojoExecution> mojoExecutions = phaseBindings.computeIfAbsent(priority, k -> new ArrayList<>());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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;

/**
* 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;
}
}
Loading

0 comments on commit 70e4122

Please sign in to comment.