mirror of https://github.com/apache/maven.git
[MNG-8052] Concurrently lifecycle executor (#1429)
This commit is contained in:
parent
c948b26484
commit
e11b475e8b
|
@ -56,6 +56,7 @@ public interface Lifecycle extends ExtensibleEnum {
|
|||
// ======================
|
||||
String BEFORE = "before:";
|
||||
String AFTER = "after:";
|
||||
String AT = "at:";
|
||||
|
||||
/**
|
||||
* Name or identifier of this lifecycle.
|
||||
|
|
|
@ -91,6 +91,14 @@ under the License.
|
|||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-api-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-jline</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-slf4j-provider</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.resolver</groupId>
|
||||
<artifactId>maven-resolver-api</artifactId>
|
||||
|
|
|
@ -39,7 +39,19 @@ public class BuildFailure extends BuildSummary {
|
|||
* @param cause The cause of the build failure, may be {@code null}.
|
||||
*/
|
||||
public BuildFailure(MavenProject project, long time, Throwable cause) {
|
||||
super(project, time);
|
||||
this(project, time, time, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new build summary for the specified project.
|
||||
*
|
||||
* @param project The project being summarized, must not be {@code null}.
|
||||
* @param execTime The exec time of the project in milliseconds.
|
||||
* @param wallTime The wall time of the project in milliseconds.
|
||||
* @param cause The cause of the build failure, may be {@code null}.
|
||||
*/
|
||||
public BuildFailure(MavenProject project, long execTime, long wallTime, Throwable cause) {
|
||||
super(project, execTime, wallTime);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,17 @@ public class BuildSuccess extends BuildSummary {
|
|||
* @param time The build time of the project in milliseconds.
|
||||
*/
|
||||
public BuildSuccess(MavenProject project, long time) {
|
||||
super(project, time);
|
||||
super(project, time, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new build summary for the specified project.
|
||||
*
|
||||
* @param project The project being summarized, must not be {@code null}.
|
||||
* @param wallTime The wall time of the project in milliseconds.
|
||||
* @param execTime The exec time of the project in milliseconds.
|
||||
*/
|
||||
public BuildSuccess(MavenProject project, long wallTime, long execTime) {
|
||||
super(project, wallTime, execTime);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,12 @@ public abstract class BuildSummary {
|
|||
/**
|
||||
* The build time of the project in milliseconds.
|
||||
*/
|
||||
private final long time;
|
||||
private final long wallTime;
|
||||
|
||||
/**
|
||||
* The total amount of time spent for to run mojos in milliseconds.
|
||||
*/
|
||||
private final long execTime;
|
||||
|
||||
/**
|
||||
* Creates a new build summary for the specified project.
|
||||
|
@ -45,9 +50,21 @@ public abstract class BuildSummary {
|
|||
* @param time The build time of the project in milliseconds.
|
||||
*/
|
||||
protected BuildSummary(MavenProject project, long time) {
|
||||
this(project, time, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new build summary for the specified project.
|
||||
*
|
||||
* @param project The project being summarized, must not be {@code null}.
|
||||
* @param execTime The exec time of the project in milliseconds.
|
||||
* @param wallTime The wall time of the project in milliseconds.
|
||||
*/
|
||||
protected BuildSummary(MavenProject project, long execTime, long wallTime) {
|
||||
this.project = Objects.requireNonNull(project, "project cannot be null");
|
||||
// TODO Validate for < 0?
|
||||
this.time = time;
|
||||
this.execTime = execTime;
|
||||
this.wallTime = wallTime;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,11 +77,20 @@ public abstract class BuildSummary {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the build time of the project in milliseconds.
|
||||
* Gets the wall time of the project in milliseconds.
|
||||
*
|
||||
* @return The build time of the project in milliseconds.
|
||||
* @return The wall time of the project in milliseconds.
|
||||
*/
|
||||
public long getTime() {
|
||||
return time;
|
||||
return execTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exec time of the project in milliseconds.
|
||||
*
|
||||
* @return The exec time of the project in milliseconds.
|
||||
*/
|
||||
public long getWallTime() {
|
||||
return wallTime;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,10 @@ import org.apache.maven.execution.ProjectExecutionEvent;
|
|||
import org.apache.maven.execution.ProjectExecutionListener;
|
||||
import org.apache.maven.lifecycle.LifecycleExecutionException;
|
||||
|
||||
class CompoundProjectExecutionListener implements ProjectExecutionListener {
|
||||
public class CompoundProjectExecutionListener implements ProjectExecutionListener {
|
||||
private final Collection<ProjectExecutionListener> listeners;
|
||||
|
||||
CompoundProjectExecutionListener(Collection<ProjectExecutionListener> listeners) {
|
||||
public CompoundProjectExecutionListener(Collection<ProjectExecutionListener> listeners) {
|
||||
this.listeners = listeners; // NB this is live injected collection
|
||||
}
|
||||
|
||||
|
|
|
@ -214,6 +214,13 @@ public class MojoExecutor {
|
|||
doExecute(session, mojoExecution, dependencyContext);
|
||||
}
|
||||
|
||||
protected static class NoLock implements NoExceptionCloseable {
|
||||
public NoLock() {}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregating mojo executions (possibly) modify all MavenProjects, including those that are currently in use
|
||||
* by concurrently running mojo executions. To prevent race conditions, an aggregating execution will block
|
||||
|
@ -222,54 +229,45 @@ public class MojoExecutor {
|
|||
* TODO: ideally, the builder should take care of the ordering in a smarter way
|
||||
* TODO: and concurrency issues fixed with MNG-7157
|
||||
*/
|
||||
private class ProjectLock implements AutoCloseable {
|
||||
protected class ProjectLock implements NoExceptionCloseable {
|
||||
final Lock acquiredAggregatorLock;
|
||||
final OwnerReentrantLock acquiredProjectLock;
|
||||
|
||||
ProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
|
||||
mojos.put(Thread.currentThread(), mojoDescriptor);
|
||||
if (session.getRequest().getDegreeOfConcurrency() > 1) {
|
||||
boolean aggregator = mojoDescriptor.isAggregator();
|
||||
acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
|
||||
acquiredProjectLock = getProjectLock(session);
|
||||
if (!acquiredAggregatorLock.tryLock()) {
|
||||
Thread owner = aggregatorLock.getOwner();
|
||||
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
|
||||
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
|
||||
String msg = str + " aggregator mojo is already being executed "
|
||||
+ "in this parallel build, those kind of mojos require exclusive access to "
|
||||
+ "reactor to prevent race conditions. This mojo execution will be blocked "
|
||||
+ "until the aggregator mojo is done.";
|
||||
warn(msg);
|
||||
acquiredAggregatorLock.lock();
|
||||
}
|
||||
if (!acquiredProjectLock.tryLock()) {
|
||||
Thread owner = acquiredProjectLock.getOwner();
|
||||
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
|
||||
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
|
||||
String msg = str + " mojo is already being executed "
|
||||
+ "on the project " + session.getCurrentProject().getGroupId()
|
||||
+ ":" + session.getCurrentProject().getArtifactId() + ". "
|
||||
+ "This mojo execution will be blocked "
|
||||
+ "until the mojo is done.";
|
||||
warn(msg);
|
||||
acquiredProjectLock.lock();
|
||||
}
|
||||
} else {
|
||||
acquiredAggregatorLock = null;
|
||||
acquiredProjectLock = null;
|
||||
boolean aggregator = mojoDescriptor.isAggregator();
|
||||
acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
|
||||
acquiredProjectLock = getProjectLock(session);
|
||||
if (!acquiredAggregatorLock.tryLock()) {
|
||||
Thread owner = aggregatorLock.getOwner();
|
||||
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
|
||||
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
|
||||
String msg = str + " aggregator mojo is already being executed "
|
||||
+ "in this parallel build, those kind of mojos require exclusive access to "
|
||||
+ "reactor to prevent race conditions. This mojo execution will be blocked "
|
||||
+ "until the aggregator mojo is done.";
|
||||
warn(msg);
|
||||
acquiredAggregatorLock.lock();
|
||||
}
|
||||
if (!acquiredProjectLock.tryLock()) {
|
||||
Thread owner = acquiredProjectLock.getOwner();
|
||||
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
|
||||
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
|
||||
String msg = str + " mojo is already being executed "
|
||||
+ "on the project " + session.getCurrentProject().getGroupId()
|
||||
+ ":" + session.getCurrentProject().getArtifactId() + ". "
|
||||
+ "This mojo execution will be blocked "
|
||||
+ "until the mojo is done.";
|
||||
warn(msg);
|
||||
acquiredProjectLock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// release the lock in the reverse order of the acquisition
|
||||
if (acquiredProjectLock != null) {
|
||||
acquiredProjectLock.unlock();
|
||||
}
|
||||
if (acquiredAggregatorLock != null) {
|
||||
acquiredAggregatorLock.unlock();
|
||||
}
|
||||
acquiredProjectLock.unlock();
|
||||
acquiredAggregatorLock.unlock();
|
||||
mojos.remove(Thread.currentThread());
|
||||
}
|
||||
|
||||
|
@ -308,7 +306,7 @@ public class MojoExecutor {
|
|||
|
||||
ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext);
|
||||
|
||||
try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) {
|
||||
try (NoExceptionCloseable lock = getProjectLock(session, mojoDescriptor)) {
|
||||
doExecute2(session, mojoExecution);
|
||||
} finally {
|
||||
for (MavenProject forkedProject : forkedProjects) {
|
||||
|
@ -317,6 +315,23 @@ public class MojoExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
protected interface NoExceptionCloseable extends AutoCloseable {
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
|
||||
protected NoExceptionCloseable getProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
|
||||
if (useProjectLock(session)) {
|
||||
return new ProjectLock(session, mojoDescriptor);
|
||||
} else {
|
||||
return new NoLock();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean useProjectLock(MavenSession session) {
|
||||
return session.getRequest().getDegreeOfConcurrency() > 1;
|
||||
}
|
||||
|
||||
private void doExecute2(MavenSession session, MojoExecution mojoExecution) throws LifecycleExecutionException {
|
||||
eventCatapult.fire(ExecutionEvent.Type.MojoStarted, session, mojoExecution);
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecution;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
public class BuildPlan {
|
||||
|
||||
private final Map<MavenProject, Map<String, BuildStep>> plan = new LinkedHashMap<>();
|
||||
private final Map<MavenProject, List<MavenProject>> projects;
|
||||
private final Map<String, String> aliases = new HashMap<>();
|
||||
private volatile Set<String> duplicateIds;
|
||||
private volatile List<BuildStep> sortedNodes;
|
||||
|
||||
BuildPlan() {
|
||||
this.projects = null;
|
||||
}
|
||||
|
||||
public BuildPlan(Map<MavenProject, List<MavenProject>> projects) {
|
||||
this.projects = projects;
|
||||
}
|
||||
|
||||
public Map<MavenProject, List<MavenProject>> getAllProjects() {
|
||||
return projects;
|
||||
}
|
||||
|
||||
public Map<String, String> aliases() {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
public Stream<MavenProject> projects() {
|
||||
return plan.keySet().stream();
|
||||
}
|
||||
|
||||
public void addProject(MavenProject project, Map<String, BuildStep> steps) {
|
||||
plan.put(project, steps);
|
||||
}
|
||||
|
||||
public void addStep(MavenProject project, String name, BuildStep step) {
|
||||
plan.get(project).put(name, step);
|
||||
}
|
||||
|
||||
public Stream<BuildStep> allSteps() {
|
||||
return plan.values().stream().flatMap(m -> m.values().stream());
|
||||
}
|
||||
|
||||
public Stream<BuildStep> steps(MavenProject project) {
|
||||
return Optional.ofNullable(plan.get(project))
|
||||
.map(m -> m.values().stream())
|
||||
.orElse(Stream.empty());
|
||||
}
|
||||
|
||||
public Optional<BuildStep> step(MavenProject project, String name) {
|
||||
return Optional.ofNullable(plan.get(project)).map(m -> m.get(name));
|
||||
}
|
||||
|
||||
public BuildStep requiredStep(MavenProject project, String name) {
|
||||
return step(project, name).get();
|
||||
}
|
||||
|
||||
// add a follow-up plan to this one
|
||||
public void then(BuildPlan step) {
|
||||
step.plan.forEach((k, v) -> plan.merge(k, v, this::merge));
|
||||
aliases.putAll(step.aliases);
|
||||
}
|
||||
|
||||
private Map<String, BuildStep> merge(Map<String, BuildStep> org, Map<String, BuildStep> add) {
|
||||
// all new phases should be added after the existing ones
|
||||
List<BuildStep> lasts =
|
||||
org.values().stream().filter(b -> b.successors.isEmpty()).collect(Collectors.toList());
|
||||
List<BuildStep> firsts =
|
||||
add.values().stream().filter(b -> b.predecessors.isEmpty()).collect(Collectors.toList());
|
||||
firsts.stream()
|
||||
.filter(addNode -> !org.containsKey(addNode.name))
|
||||
.forEach(addNode -> lasts.forEach(orgNode -> addNode.executeAfter(orgNode)));
|
||||
add.forEach((name, node) -> org.merge(name, node, this::merge));
|
||||
return org;
|
||||
}
|
||||
|
||||
private BuildStep merge(BuildStep node1, BuildStep node2) {
|
||||
node1.predecessors.addAll(node2.predecessors);
|
||||
node1.successors.addAll(node2.successors);
|
||||
node2.mojos.forEach((k, v) -> node1.mojos.merge(k, v, this::mergeMojos));
|
||||
return node1;
|
||||
}
|
||||
|
||||
private Map<String, MojoExecution> mergeMojos(Map<String, MojoExecution> l1, Map<String, MojoExecution> l2) {
|
||||
l2.forEach(l1::putIfAbsent);
|
||||
return l1;
|
||||
}
|
||||
|
||||
// gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
|
||||
public Set<String> duplicateIds() {
|
||||
if (duplicateIds == null) {
|
||||
synchronized (this) {
|
||||
if (duplicateIds == null) {
|
||||
duplicateIds = projects()
|
||||
.map(MavenProject::getArtifactId)
|
||||
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(p -> p.getValue() > 1)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
return duplicateIds;
|
||||
}
|
||||
|
||||
public List<BuildStep> sortedNodes() {
|
||||
if (sortedNodes == null) {
|
||||
synchronized (this) {
|
||||
if (sortedNodes == null) {
|
||||
List<BuildStep> sortedNodes = new ArrayList<>();
|
||||
Set<BuildStep> visited = new HashSet<>();
|
||||
// Visit each unvisited node
|
||||
allSteps().forEach(node -> visitNode(node, visited, sortedNodes));
|
||||
// Reverse the sorted nodes to get the correct order
|
||||
Collections.reverse(sortedNodes);
|
||||
this.sortedNodes = sortedNodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sortedNodes;
|
||||
}
|
||||
|
||||
// Helper method to visit a node
|
||||
private static void visitNode(BuildStep node, Set<BuildStep> visited, List<BuildStep> sortedNodes) {
|
||||
if (visited.add(node)) {
|
||||
// For each successor of the current node, visit unvisited successors
|
||||
node.successors.forEach(successor -> visitNode(successor, visited, sortedNodes));
|
||||
sortedNodes.add(node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,939 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.maven.api.Lifecycle;
|
||||
import org.apache.maven.api.services.LifecycleRegistry;
|
||||
import org.apache.maven.api.services.MavenException;
|
||||
import org.apache.maven.api.xml.XmlNode;
|
||||
import org.apache.maven.execution.BuildFailure;
|
||||
import org.apache.maven.execution.BuildSuccess;
|
||||
import org.apache.maven.execution.ExecutionEvent;
|
||||
import org.apache.maven.execution.MavenExecutionRequest;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.execution.ProjectDependencyGraph;
|
||||
import org.apache.maven.execution.ProjectExecutionEvent;
|
||||
import org.apache.maven.execution.ProjectExecutionListener;
|
||||
import org.apache.maven.internal.MultilineMessageHelper;
|
||||
import org.apache.maven.internal.transformation.ConsumerPomArtifactTransformer;
|
||||
import org.apache.maven.internal.xml.XmlNodeImpl;
|
||||
import org.apache.maven.lifecycle.LifecycleExecutionException;
|
||||
import org.apache.maven.lifecycle.LifecycleNotFoundException;
|
||||
import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
|
||||
import org.apache.maven.lifecycle.MojoExecutionConfigurator;
|
||||
import org.apache.maven.lifecycle.internal.BuildThreadFactory;
|
||||
import org.apache.maven.lifecycle.internal.CompoundProjectExecutionListener;
|
||||
import org.apache.maven.lifecycle.internal.DefaultLifecyclePluginAnalyzer;
|
||||
import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
|
||||
import org.apache.maven.lifecycle.internal.GoalTask;
|
||||
import org.apache.maven.lifecycle.internal.LifecycleTask;
|
||||
import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
|
||||
import org.apache.maven.lifecycle.internal.MojoExecutor;
|
||||
import org.apache.maven.lifecycle.internal.ReactorContext;
|
||||
import org.apache.maven.lifecycle.internal.Task;
|
||||
import org.apache.maven.lifecycle.internal.TaskSegment;
|
||||
import org.apache.maven.model.Plugin;
|
||||
import org.apache.maven.model.PluginExecution;
|
||||
import org.apache.maven.plugin.MavenPluginManager;
|
||||
import org.apache.maven.plugin.MojoExecution;
|
||||
import org.apache.maven.plugin.MojoNotFoundException;
|
||||
import org.apache.maven.plugin.PluginDescriptorParsingException;
|
||||
import org.apache.maven.plugin.descriptor.MojoDescriptor;
|
||||
import org.apache.maven.plugin.descriptor.Parameter;
|
||||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.classworlds.realm.ClassRealm;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.apache.maven.api.Lifecycle.AFTER;
|
||||
import static org.apache.maven.api.Lifecycle.AT;
|
||||
import static org.apache.maven.api.Lifecycle.BEFORE;
|
||||
import static org.apache.maven.api.Lifecycle.Phase.PACKAGE;
|
||||
import static org.apache.maven.api.Lifecycle.Phase.READY;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.CREATED;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.EXECUTED;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.FAILED;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.PLAN;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.PLANNING;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.SCHEDULED;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.SETUP;
|
||||
import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.TEARDOWN;
|
||||
|
||||
/**
|
||||
* Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project).
|
||||
* <p>
|
||||
* This builder uses a number of threads equal to the minimum of the degree of concurrency (which is the thread count
|
||||
* set with <code>-T</code> on the command-line) and the number of projects to build. As such, building a single project
|
||||
* will always result in a sequential build, regardless of the thread count.
|
||||
* </p>
|
||||
* <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*
|
||||
* @since 3.0
|
||||
* Builds one or more lifecycles for a full module
|
||||
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*/
|
||||
@Named
|
||||
public class BuildPlanExecutor {
|
||||
|
||||
private static final Object GLOBAL = new Object();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final MojoExecutor mojoExecutor;
|
||||
private final ExecutionEventCatapult eventCatapult;
|
||||
private final ProjectExecutionListener projectExecutionListener;
|
||||
private final ConsumerPomArtifactTransformer consumerPomArtifactTransformer;
|
||||
private final BuildPlanLogger buildPlanLogger;
|
||||
private final Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators;
|
||||
private final MavenPluginManager mavenPluginManager;
|
||||
private final MojoDescriptorCreator mojoDescriptorCreator;
|
||||
private final LifecycleRegistry lifecycles;
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||
public BuildPlanExecutor(
|
||||
@Named("concurrent") MojoExecutor mojoExecutor,
|
||||
ExecutionEventCatapult eventCatapult,
|
||||
List<ProjectExecutionListener> listeners,
|
||||
ConsumerPomArtifactTransformer consumerPomArtifactTransformer,
|
||||
BuildPlanLogger buildPlanLogger,
|
||||
Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators,
|
||||
MavenPluginManager mavenPluginManager,
|
||||
MojoDescriptorCreator mojoDescriptorCreator,
|
||||
LifecycleRegistry lifecycles) {
|
||||
this.mojoExecutor = mojoExecutor;
|
||||
this.eventCatapult = eventCatapult;
|
||||
this.projectExecutionListener = new CompoundProjectExecutionListener(listeners);
|
||||
this.consumerPomArtifactTransformer = consumerPomArtifactTransformer;
|
||||
this.buildPlanLogger = buildPlanLogger;
|
||||
this.mojoExecutionConfigurators = mojoExecutionConfigurators;
|
||||
this.mavenPluginManager = mavenPluginManager;
|
||||
this.mojoDescriptorCreator = mojoDescriptorCreator;
|
||||
this.lifecycles = lifecycles;
|
||||
}
|
||||
|
||||
public void execute(MavenSession session, ReactorContext reactorContext, List<TaskSegment> taskSegments)
|
||||
throws ExecutionException, InterruptedException {
|
||||
try (BuildContext ctx = new BuildContext(session, reactorContext, taskSegments)) {
|
||||
ctx.execute();
|
||||
}
|
||||
}
|
||||
|
||||
class BuildContext implements AutoCloseable {
|
||||
final MavenSession session;
|
||||
final ReactorContext reactorContext;
|
||||
final PhasingExecutor executor;
|
||||
final ConcurrentLogOutput appender;
|
||||
final Map<Object, Clock> clocks = new ConcurrentHashMap<>();
|
||||
final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
final int threads;
|
||||
BuildPlan plan;
|
||||
|
||||
BuildContext(MavenSession session, ReactorContext reactorContext, List<TaskSegment> taskSegments) {
|
||||
this.session = session;
|
||||
this.reactorContext = reactorContext;
|
||||
this.threads = Math.min(
|
||||
session.getRequest().getDegreeOfConcurrency(),
|
||||
session.getProjects().size());
|
||||
// Propagate the parallel flag to the root session
|
||||
session.setParallel(threads > 1);
|
||||
this.executor = new PhasingExecutor(Executors.newFixedThreadPool(threads, new BuildThreadFactory()));
|
||||
this.appender = new ConcurrentLogOutput();
|
||||
|
||||
// build initial plan
|
||||
this.plan = buildInitialPlan(taskSegments);
|
||||
}
|
||||
|
||||
BuildContext() {
|
||||
this.session = null;
|
||||
this.reactorContext = null;
|
||||
this.threads = 1;
|
||||
this.executor = null;
|
||||
this.appender = null;
|
||||
this.plan = null;
|
||||
}
|
||||
|
||||
public BuildPlan buildInitialPlan(List<TaskSegment> taskSegments) {
|
||||
int nThreads = Math.min(
|
||||
session.getRequest().getDegreeOfConcurrency(),
|
||||
session.getProjects().size());
|
||||
boolean parallel = nThreads > 1;
|
||||
// Propagate the parallel flag to the root session
|
||||
session.setParallel(parallel);
|
||||
|
||||
ProjectDependencyGraph dependencyGraph = session.getProjectDependencyGraph();
|
||||
MavenProject rootProject = session.getTopLevelProject();
|
||||
|
||||
Map<MavenProject, List<MavenProject>> allProjects = new LinkedHashMap<>();
|
||||
dependencyGraph
|
||||
.getSortedProjects()
|
||||
.forEach(p -> allProjects.put(p, dependencyGraph.getUpstreamProjects(p, false)));
|
||||
|
||||
BuildPlan plan = new BuildPlan(allProjects);
|
||||
for (TaskSegment taskSegment : taskSegments) {
|
||||
Map<MavenProject, List<MavenProject>> projects = taskSegment.isAggregating()
|
||||
? Collections.singletonMap(rootProject, allProjects.get(rootProject))
|
||||
: allProjects;
|
||||
|
||||
BuildPlan segment = calculateMojoExecutions(projects, taskSegment.getTasks());
|
||||
plan.then(segment);
|
||||
}
|
||||
|
||||
// Create plan, setup and teardown
|
||||
for (MavenProject project : plan.getAllProjects().keySet()) {
|
||||
BuildStep pplan = new BuildStep(PLAN, project, null);
|
||||
pplan.status.set(PLANNING); // the plan step always need planning
|
||||
BuildStep setup = new BuildStep(SETUP, project, null);
|
||||
BuildStep teardown = new BuildStep(TEARDOWN, project, null);
|
||||
setup.executeAfter(pplan);
|
||||
plan.steps(project).forEach(step -> {
|
||||
if (step.predecessors.isEmpty()) {
|
||||
step.executeAfter(setup);
|
||||
} else if (step.successors.isEmpty()) {
|
||||
teardown.executeAfter(step);
|
||||
}
|
||||
});
|
||||
Stream.of(pplan, setup, teardown).forEach(step -> plan.addStep(project, step.name, step));
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
private void checkUnboundVersions(BuildPlan buildPlan) {
|
||||
String defaulModelId = DefaultLifecyclePluginAnalyzer.DEFAULTLIFECYCLEBINDINGS_MODELID;
|
||||
List<String> unversionedPlugins = buildPlan
|
||||
.allSteps()
|
||||
.flatMap(step -> step.mojos.values().stream().flatMap(map -> map.values().stream()))
|
||||
.map(MojoExecution::getPlugin)
|
||||
.filter(p -> p.getLocation("version") != null
|
||||
&& p.getLocation("version").getSource() != null
|
||||
&& defaulModelId.equals(
|
||||
p.getLocation("version").getSource().getModelId()))
|
||||
.distinct()
|
||||
.map(Plugin::getArtifactId) // managed by us, groupId is always o.a.m.plugins
|
||||
.toList();
|
||||
if (!unversionedPlugins.isEmpty()) {
|
||||
logger.warn("Version not locked for default bindings plugins " + unversionedPlugins
|
||||
+ ", you should define versions in pluginManagement section of your " + "pom.xml or parent");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkThreadSafety(BuildPlan buildPlan) {
|
||||
if (threads > 1) {
|
||||
Set<MojoExecution> unsafeExecutions = buildPlan
|
||||
.allSteps()
|
||||
.flatMap(step -> step.mojos.values().stream().flatMap(map -> map.values().stream()))
|
||||
.filter(execution -> !execution.getMojoDescriptor().isV4Api())
|
||||
.collect(Collectors.toSet());
|
||||
if (!unsafeExecutions.isEmpty()) {
|
||||
for (String s : MultilineMessageHelper.format(
|
||||
"""
|
||||
Your build is requesting concurrent execution, but this project contains the \
|
||||
following plugin(s) that have goals not built with Maven 4 to support concurrent \
|
||||
execution. While this /may/ work fine, please look for plugin updates and/or \
|
||||
request plugins be made thread-safe. If reporting an issue, report it against the \
|
||||
plugin in question, not against Apache Maven.""")) {
|
||||
logger.warn(s);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
Set<MojoDescriptor> unsafeGoals = unsafeExecutions.stream()
|
||||
.map(MojoExecution::getMojoDescriptor)
|
||||
.collect(Collectors.toSet());
|
||||
logger.warn("The following goals are not Maven 4 goals:");
|
||||
for (MojoDescriptor unsafeGoal : unsafeGoals) {
|
||||
logger.warn(" " + unsafeGoal.getId());
|
||||
}
|
||||
} else {
|
||||
Set<Plugin> unsafePlugins = unsafeExecutions.stream()
|
||||
.map(MojoExecution::getPlugin)
|
||||
.collect(Collectors.toSet());
|
||||
logger.warn("The following plugins are not Maven 4 plugins:");
|
||||
for (Plugin unsafePlugin : unsafePlugins) {
|
||||
logger.warn(" " + unsafePlugin.getId());
|
||||
}
|
||||
logger.warn("");
|
||||
logger.warn("Enable verbose output (-X) to see precisely which goals are not marked as"
|
||||
+ " thread-safe.");
|
||||
}
|
||||
logger.warn(MultilineMessageHelper.separatorLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void execute() {
|
||||
try {
|
||||
plan();
|
||||
executePlan();
|
||||
executor.await();
|
||||
} catch (Exception e) {
|
||||
session.getResult().addException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.appender.close();
|
||||
this.executor.close();
|
||||
}
|
||||
|
||||
private void executePlan() {
|
||||
if (reactorContext.getReactorBuildStatus().isHalted()) {
|
||||
return;
|
||||
}
|
||||
Clock global = clocks.computeIfAbsent(GLOBAL, p -> new Clock());
|
||||
global.start();
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
plan.sortedNodes().stream()
|
||||
.filter(step -> step.status.get() == CREATED)
|
||||
.filter(step -> step.predecessors.stream().allMatch(s -> s.status.get() == EXECUTED))
|
||||
.filter(step -> step.status.compareAndSet(CREATED, SCHEDULED))
|
||||
.forEach(step -> {
|
||||
boolean nextIsPlanning = step.successors.stream().anyMatch(st -> PLAN.equals(st.name));
|
||||
executor.execute(() -> {
|
||||
try (AutoCloseable ctx = appender.build(step.project)) {
|
||||
executeStep(step);
|
||||
if (nextIsPlanning) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
plan();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
executePlan();
|
||||
} catch (Exception e) {
|
||||
step.status.compareAndSet(SCHEDULED, FAILED);
|
||||
global.stop();
|
||||
handleBuildError(reactorContext, session, step.project, e, global);
|
||||
}
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void executeStep(BuildStep step) throws IOException, LifecycleExecutionException {
|
||||
Clock clock = getClock(step.project);
|
||||
switch (step.name) {
|
||||
case PLAN:
|
||||
// Planning steps should be executed out of normal execution
|
||||
throw new IllegalStateException();
|
||||
case SETUP:
|
||||
consumerPomArtifactTransformer.injectTransformedArtifacts(
|
||||
session.getRepositorySession(), step.project);
|
||||
projectExecutionListener.beforeProjectExecution(new ProjectExecutionEvent(session, step.project));
|
||||
eventCatapult.fire(ExecutionEvent.Type.ProjectStarted, session, null);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
projectExecutionListener.afterProjectExecutionSuccess(
|
||||
new ProjectExecutionEvent(session, step.project, Collections.emptyList()));
|
||||
reactorContext
|
||||
.getResult()
|
||||
.addBuildSummary(new BuildSuccess(step.project, clock.wallTime(), clock.execTime()));
|
||||
eventCatapult.fire(ExecutionEvent.Type.ProjectSucceeded, session, null);
|
||||
break;
|
||||
default:
|
||||
List<MojoExecution> executions = step.executions().collect(Collectors.toList());
|
||||
if (!executions.isEmpty()) {
|
||||
attachToThread(step.project);
|
||||
session.setCurrentProject(step.project);
|
||||
clock.start();
|
||||
executions.forEach(mojoExecution -> {
|
||||
mojoExecutionConfigurator(mojoExecution).configure(step.project, mojoExecution, true);
|
||||
finalizeMojoConfiguration(mojoExecution);
|
||||
});
|
||||
mojoExecutor.execute(session, executions);
|
||||
clock.stop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
step.status.compareAndSet(SCHEDULED, EXECUTED);
|
||||
}
|
||||
|
||||
private Clock getClock(Object key) {
|
||||
return clocks.computeIfAbsent(key, p -> new Clock());
|
||||
}
|
||||
|
||||
private void plan() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
Set<BuildStep> planSteps = plan.allSteps()
|
||||
.filter(st -> PLAN.equals(st.name))
|
||||
.filter(step -> step.predecessors.stream().allMatch(s -> s.status.get() == EXECUTED))
|
||||
.filter(step -> step.status.compareAndSet(PLANNING, SCHEDULED))
|
||||
.collect(Collectors.toSet());
|
||||
for (BuildStep step : planSteps) {
|
||||
MavenProject project = step.project;
|
||||
for (Plugin plugin : project.getBuild().getPlugins()) {
|
||||
for (PluginExecution execution : plugin.getExecutions()) {
|
||||
for (String goal : execution.getGoals()) {
|
||||
MojoDescriptor mojoDescriptor = getMojoDescriptor(project, plugin, goal);
|
||||
String phase =
|
||||
execution.getPhase() != null ? execution.getPhase() : mojoDescriptor.getPhase();
|
||||
String tmpResolvedPhase = plan.aliases().getOrDefault(phase, phase);
|
||||
String resolvedPhase = tmpResolvedPhase.startsWith(AT)
|
||||
? tmpResolvedPhase.substring(AT.length())
|
||||
: tmpResolvedPhase;
|
||||
plan.step(project, resolvedPhase).ifPresent(n -> {
|
||||
MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, execution.getId());
|
||||
mojoExecution.setLifecyclePhase(phase);
|
||||
n.addMojo(mojoExecution, execution.getPriority());
|
||||
if (mojoDescriptor.getDependencyCollectionRequired() != null
|
||||
|| mojoDescriptor.getDependencyResolutionRequired() != null) {
|
||||
for (MavenProject p :
|
||||
plan.getAllProjects().get(project)) {
|
||||
plan.step(p, AFTER + PACKAGE)
|
||||
.ifPresent(a -> plan.requiredStep(project, resolvedPhase)
|
||||
.executeAfter(a));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BuildPlan buildPlan = plan;
|
||||
for (BuildStep step :
|
||||
planSteps.stream().flatMap(p -> plan.steps(p.project)).toList()) {
|
||||
for (MojoExecution execution : step.executions().toList()) {
|
||||
buildPlan = computeForkPlan(step, execution, buildPlan);
|
||||
}
|
||||
}
|
||||
|
||||
for (BuildStep step : planSteps) {
|
||||
MavenProject project = step.project;
|
||||
buildPlanLogger.writePlan(plan, project);
|
||||
step.status.compareAndSet(SCHEDULED, EXECUTED);
|
||||
}
|
||||
|
||||
checkThreadSafety(plan);
|
||||
checkUnboundVersions(plan);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected BuildPlan computeForkPlan(BuildStep step, MojoExecution execution, BuildPlan buildPlan) {
|
||||
MojoDescriptor mojoDescriptor = execution.getMojoDescriptor();
|
||||
PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
|
||||
String forkedGoal = mojoDescriptor.getExecuteGoal();
|
||||
String phase = mojoDescriptor.getExecutePhase();
|
||||
// We have a fork goal
|
||||
if (forkedGoal != null && !forkedGoal.isEmpty()) {
|
||||
MojoDescriptor forkedMojoDescriptor = pluginDescriptor.getMojo(forkedGoal);
|
||||
if (forkedMojoDescriptor == null) {
|
||||
throw new MavenException(new MojoNotFoundException(forkedGoal, pluginDescriptor));
|
||||
}
|
||||
|
||||
List<MavenProject> toFork = new ArrayList<>();
|
||||
toFork.add(step.project);
|
||||
if (mojoDescriptor.isAggregator() && step.project.getCollectedProjects() != null) {
|
||||
toFork.addAll(step.project.getCollectedProjects());
|
||||
}
|
||||
|
||||
BuildPlan plan = new BuildPlan();
|
||||
for (MavenProject project : toFork) {
|
||||
BuildStep st = new BuildStep(forkedGoal, project, null);
|
||||
MojoExecution mojoExecution = new MojoExecution(forkedMojoDescriptor, forkedGoal);
|
||||
st.addMojo(mojoExecution, 0);
|
||||
Map<String, BuildStep> n = new HashMap<>();
|
||||
n.put(forkedGoal, st);
|
||||
plan.addProject(project, n);
|
||||
}
|
||||
|
||||
for (BuildStep astep : plan.allSteps().toList()) {
|
||||
for (MojoExecution aexecution : astep.executions().toList()) {
|
||||
plan = computeForkPlan(astep, aexecution, plan);
|
||||
}
|
||||
}
|
||||
|
||||
return plan;
|
||||
|
||||
} else if (phase != null && !phase.isEmpty()) {
|
||||
String forkedLifecycle = mojoDescriptor.getExecuteLifecycle();
|
||||
Lifecycle lifecycle;
|
||||
if (forkedLifecycle != null && !forkedLifecycle.isEmpty()) {
|
||||
org.apache.maven.api.plugin.descriptor.lifecycle.Lifecycle lifecycleOverlay;
|
||||
try {
|
||||
lifecycleOverlay = pluginDescriptor.getLifecycleMapping(forkedLifecycle);
|
||||
} catch (IOException | XMLStreamException e) {
|
||||
throw new MavenException(new PluginDescriptorParsingException(
|
||||
pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e));
|
||||
}
|
||||
if (lifecycleOverlay == null) {
|
||||
Optional<Lifecycle> lf = lifecycles.lookup(forkedLifecycle);
|
||||
if (lf.isPresent()) {
|
||||
lifecycle = lf.get();
|
||||
} else {
|
||||
throw new MavenException(new LifecycleNotFoundException(forkedLifecycle));
|
||||
}
|
||||
} else {
|
||||
lifecycle = new PluginLifecycle(lifecycleOverlay, pluginDescriptor);
|
||||
}
|
||||
} else {
|
||||
if (execution.getLifecyclePhase() != null) {
|
||||
String n = execution.getLifecyclePhase();
|
||||
String phaseName = n.startsWith(BEFORE)
|
||||
? n.substring(BEFORE.length())
|
||||
: n.startsWith(AFTER) ? n.substring(AFTER.length()) : n;
|
||||
lifecycle = lifecycles.stream()
|
||||
.filter(l -> l.allPhases().anyMatch(p -> phaseName.equals(p.name())))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (lifecycle == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
} else {
|
||||
lifecycle = lifecycles.require(Lifecycle.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
String resolvedPhase = getResolvedPhase(lifecycle, phase);
|
||||
|
||||
Map<MavenProject, List<MavenProject>> map = Collections.singletonMap(
|
||||
step.project, plan.getAllProjects().get(step.project));
|
||||
BuildPlan forkedPlan = calculateLifecycleMappings(map, lifecycle, resolvedPhase);
|
||||
forkedPlan.then(buildPlan);
|
||||
return forkedPlan;
|
||||
} else {
|
||||
return buildPlan;
|
||||
}
|
||||
}
|
||||
|
||||
private String getResolvedPhase(Lifecycle lifecycle, String phase) {
|
||||
return lifecycle.aliases().stream()
|
||||
.filter(a -> phase.equals(a.v3Phase()))
|
||||
.findFirst()
|
||||
.map(Lifecycle.Alias::v4Phase)
|
||||
.orElse(phase);
|
||||
}
|
||||
|
||||
private String getResolvedPhase(String phase) {
|
||||
return lifecycles.stream()
|
||||
.flatMap(l -> l.aliases().stream())
|
||||
.filter(a -> phase.equals(a.v3Phase()))
|
||||
.findFirst()
|
||||
.map(Lifecycle.Alias::v4Phase)
|
||||
.orElse(phase);
|
||||
}
|
||||
|
||||
protected void handleBuildError(
|
||||
final ReactorContext buildContext,
|
||||
final MavenSession session,
|
||||
final MavenProject mavenProject,
|
||||
Throwable t,
|
||||
final Clock clock) {
|
||||
// record the error and mark the project as failed
|
||||
buildContext.getResult().addException(t);
|
||||
buildContext
|
||||
.getResult()
|
||||
.addBuildSummary(new BuildFailure(mavenProject, clock.execTime(), clock.wallTime(), t));
|
||||
|
||||
// notify listeners about "soft" project build failures only
|
||||
if (t instanceof Exception && !(t instanceof RuntimeException)) {
|
||||
eventCatapult.fire(ExecutionEvent.Type.ProjectFailed, session, null, (Exception) t);
|
||||
}
|
||||
|
||||
// reactor failure modes
|
||||
if (t instanceof RuntimeException || !(t instanceof Exception)) {
|
||||
// fail fast on RuntimeExceptions, Errors and "other" Throwables
|
||||
// assume these are system errors and further build is meaningless
|
||||
buildContext.getReactorBuildStatus().halt();
|
||||
} else if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(session.getReactorFailureBehavior())) {
|
||||
// continue the build
|
||||
} else if (MavenExecutionRequest.REACTOR_FAIL_AT_END.equals(session.getReactorFailureBehavior())) {
|
||||
// continue the build but ban all projects that depend on the failed one
|
||||
buildContext.getReactorBuildStatus().blackList(mavenProject);
|
||||
} else if (MavenExecutionRequest.REACTOR_FAIL_FAST.equals(session.getReactorFailureBehavior())) {
|
||||
buildContext.getReactorBuildStatus().halt();
|
||||
} else {
|
||||
logger.error("invalid reactor failure behavior " + session.getReactorFailureBehavior());
|
||||
buildContext.getReactorBuildStatus().halt();
|
||||
}
|
||||
}
|
||||
|
||||
public BuildPlan calculateMojoExecutions(Map<MavenProject, List<MavenProject>> projects, List<Task> tasks) {
|
||||
BuildPlan buildPlan = new BuildPlan(projects);
|
||||
|
||||
for (Task task : tasks) {
|
||||
BuildPlan step;
|
||||
|
||||
if (task instanceof GoalTask) {
|
||||
String pluginGoal = task.getValue();
|
||||
|
||||
String executionId = "default-cli";
|
||||
int executionIdx = pluginGoal.indexOf('@');
|
||||
if (executionIdx > 0) {
|
||||
executionId = pluginGoal.substring(executionIdx + 1);
|
||||
}
|
||||
|
||||
step = new BuildPlan();
|
||||
for (MavenProject project : projects.keySet()) {
|
||||
BuildStep st = new BuildStep(pluginGoal, project, null);
|
||||
MojoDescriptor mojoDescriptor = getMojoDescriptor(project, pluginGoal);
|
||||
MojoExecution mojoExecution =
|
||||
new MojoExecution(mojoDescriptor, executionId, MojoExecution.Source.CLI);
|
||||
st.addMojo(mojoExecution, 0);
|
||||
Map<String, BuildStep> n = new HashMap<>();
|
||||
n.put(pluginGoal, st);
|
||||
step.addProject(project, n);
|
||||
}
|
||||
} else if (task instanceof LifecycleTask) {
|
||||
String lifecyclePhase = task.getValue();
|
||||
|
||||
step = calculateLifecycleMappings(projects, lifecyclePhase);
|
||||
|
||||
} else {
|
||||
throw new IllegalStateException("unexpected task " + task);
|
||||
}
|
||||
|
||||
buildPlan.then(step);
|
||||
}
|
||||
|
||||
return buildPlan;
|
||||
}
|
||||
|
||||
private MojoDescriptor getMojoDescriptor(MavenProject project, Plugin plugin, String goal) {
|
||||
try {
|
||||
return mavenPluginManager.getMojoDescriptor(
|
||||
plugin, goal, project.getRemotePluginRepositories(), session.getRepositorySession());
|
||||
} catch (MavenException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new MavenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private MojoDescriptor getMojoDescriptor(MavenProject project, String task) {
|
||||
try {
|
||||
return mojoDescriptorCreator.getMojoDescriptor(task, session, project);
|
||||
} catch (MavenException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new MavenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public BuildPlan calculateLifecycleMappings(
|
||||
Map<MavenProject, List<MavenProject>> projects, String lifecyclePhase) {
|
||||
|
||||
String resolvedPhase = getResolvedPhase(lifecyclePhase);
|
||||
String mainPhase = resolvedPhase.startsWith(BEFORE)
|
||||
? resolvedPhase.substring(BEFORE.length())
|
||||
: resolvedPhase.startsWith(AFTER)
|
||||
? resolvedPhase.substring(AFTER.length())
|
||||
: resolvedPhase.startsWith(AT) ? resolvedPhase.substring(AT.length()) : resolvedPhase;
|
||||
|
||||
/*
|
||||
* Determine the lifecycle that corresponds to the given phase.
|
||||
*/
|
||||
Lifecycle lifecycle = lifecycles.stream()
|
||||
.filter(l -> l.allPhases().anyMatch(p -> mainPhase.equals(p.name())))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (lifecycle == null) {
|
||||
throw new MavenException(new LifecyclePhaseNotFoundException(
|
||||
"Unknown lifecycle phase \"" + lifecyclePhase
|
||||
+ "\". You must specify a valid lifecycle phase"
|
||||
+ " or a goal in the format <plugin-prefix>:<goal> or"
|
||||
+ " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: "
|
||||
+ lifecycles.stream()
|
||||
.flatMap(l -> l.orderedPhases()
|
||||
.map(List::stream)
|
||||
.orElseGet(() -> l.allPhases().map(Lifecycle.Phase::name)))
|
||||
.collect(Collectors.joining(", "))
|
||||
+ ".",
|
||||
lifecyclePhase));
|
||||
}
|
||||
|
||||
return calculateLifecycleMappings(projects, lifecycle, resolvedPhase);
|
||||
}
|
||||
|
||||
public BuildPlan calculateLifecycleMappings(
|
||||
Map<MavenProject, List<MavenProject>> projects, Lifecycle lifecycle, String lifecyclePhase) {
|
||||
BuildPlan plan = new BuildPlan(projects);
|
||||
|
||||
for (MavenProject project : projects.keySet()) {
|
||||
// For each phase, create and sequence the pre, run and post steps
|
||||
Map<String, BuildStep> steps = lifecycle
|
||||
.allPhases()
|
||||
.flatMap(phase -> {
|
||||
BuildStep a = new BuildStep(BEFORE + phase.name(), project, phase);
|
||||
BuildStep b = new BuildStep(phase.name(), project, phase);
|
||||
BuildStep c = new BuildStep(AFTER + phase.name(), project, phase);
|
||||
b.executeAfter(a);
|
||||
c.executeAfter(b);
|
||||
return Stream.of(a, b, c);
|
||||
})
|
||||
.collect(Collectors.toMap(n -> n.name, n -> n));
|
||||
// for each phase, make sure children phases are execute between pre and post steps
|
||||
lifecycle.allPhases().forEach(phase -> phase.phases().forEach(child -> {
|
||||
steps.get(BEFORE + child.name()).executeAfter(steps.get(BEFORE + phase.name()));
|
||||
steps.get(AFTER + phase.name()).executeAfter(steps.get(AFTER + child.name()));
|
||||
}));
|
||||
// for each phase, create links between this project phases
|
||||
lifecycle.allPhases().forEach(phase -> {
|
||||
phase.links().stream()
|
||||
.filter(l -> l.pointer().type() == Lifecycle.Pointer.Type.PROJECT)
|
||||
.forEach(link -> {
|
||||
String n1 = phase.name();
|
||||
String n2 = link.pointer().phase();
|
||||
if (link.kind() == Lifecycle.Link.Kind.AFTER) {
|
||||
steps.get(BEFORE + n1).executeAfter(steps.get(AFTER + n2));
|
||||
} else {
|
||||
steps.get(BEFORE + n2).executeAfter(steps.get(AFTER + n1));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Only keep mojo executions before the end phase
|
||||
String endPhase = lifecyclePhase.startsWith(BEFORE) || lifecyclePhase.startsWith(AFTER)
|
||||
? lifecyclePhase
|
||||
: lifecyclePhase.startsWith(AT)
|
||||
? lifecyclePhase.substring(AT.length())
|
||||
: AFTER + lifecyclePhase;
|
||||
Set<BuildStep> toKeep = steps.get(endPhase).allPredecessors().collect(Collectors.toSet());
|
||||
steps.values().stream().filter(n -> !toKeep.contains(n)).forEach(BuildStep::skip);
|
||||
|
||||
plan.addProject(project, steps);
|
||||
}
|
||||
|
||||
// Create inter project dependencies
|
||||
plan.allSteps().filter(step -> step.phase != null).forEach(step -> {
|
||||
Lifecycle.Phase phase = step.phase;
|
||||
MavenProject project = step.project;
|
||||
phase.links().stream()
|
||||
.filter(l -> l.pointer().type() != Lifecycle.Pointer.Type.PROJECT)
|
||||
.forEach(link -> {
|
||||
String n1 = phase.name();
|
||||
String n2 = link.pointer().phase();
|
||||
// for each project, if the phase in the build, link after it
|
||||
getLinkedProjects(projects, project, link).forEach(p -> plan.step(p, AFTER + n2)
|
||||
.ifPresent(a -> plan.requiredStep(project, BEFORE + n1)
|
||||
.executeAfter(a)));
|
||||
});
|
||||
});
|
||||
|
||||
// Keep projects in reactors by GAV
|
||||
Map<String, MavenProject> reactorGavs =
|
||||
projects.keySet().stream().collect(Collectors.toMap(BuildPlanExecutor::gav, p -> p));
|
||||
|
||||
// Go through all plugins
|
||||
List<Runnable> toResolve = new ArrayList<>();
|
||||
projects.keySet().forEach(project -> project.getBuild().getPlugins().forEach(plugin -> {
|
||||
MavenProject pluginProject = reactorGavs.get(gav(plugin));
|
||||
if (pluginProject != null) {
|
||||
// In order to plan the project, we need all its plugins...
|
||||
plan.requiredStep(project, PLAN).executeAfter(plan.requiredStep(pluginProject, READY));
|
||||
} else {
|
||||
toResolve.add(() -> resolvePlugin(session, project.getRemotePluginRepositories(), plugin));
|
||||
}
|
||||
}));
|
||||
|
||||
// Eagerly resolve all plugins in parallel
|
||||
toResolve.parallelStream().forEach(Runnable::run);
|
||||
|
||||
// Keep track of phase aliases
|
||||
lifecycle.aliases().forEach(alias -> plan.aliases().put(alias.v3Phase(), alias.v4Phase()));
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
private List<MavenProject> getLinkedProjects(
|
||||
Map<MavenProject, List<MavenProject>> projects, MavenProject project, Lifecycle.Link link) {
|
||||
if (link.pointer().type() == Lifecycle.Pointer.Type.DEPENDENCIES) {
|
||||
// TODO: String scope = ((Lifecycle.DependenciesPointer) link.pointer()).scope();
|
||||
return projects.get(project);
|
||||
} else if (link.pointer().type() == Lifecycle.Pointer.Type.CHILDREN) {
|
||||
return project.getCollectedProjects();
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported pointer type: " + link.pointer().type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resolvePlugin(MavenSession session, List<RemoteRepository> repositories, Plugin plugin) {
|
||||
try {
|
||||
mavenPluginManager.getPluginDescriptor(plugin, repositories, session.getRepositorySession());
|
||||
} catch (Exception e) {
|
||||
throw new MavenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String gav(MavenProject p) {
|
||||
return p.getGroupId() + ":" + p.getArtifactId() + ":" + p.getVersion();
|
||||
}
|
||||
|
||||
private static String gav(Plugin p) {
|
||||
return p.getGroupId() + ":" + p.getArtifactId() + ":" + p.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processes the effective configuration for the specified mojo execution. This step discards all parameters
|
||||
* from the configuration that are not applicable to the mojo and injects the default values for any missing
|
||||
* parameters.
|
||||
*
|
||||
* @param mojoExecution The mojo execution whose configuration should be finalized, must not be {@code null}.
|
||||
*/
|
||||
private void finalizeMojoConfiguration(MojoExecution mojoExecution) {
|
||||
MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
|
||||
|
||||
XmlNode executionConfiguration = mojoExecution.getConfiguration() != null
|
||||
? mojoExecution.getConfiguration().getDom()
|
||||
: null;
|
||||
if (executionConfiguration == null) {
|
||||
executionConfiguration = new XmlNodeImpl("configuration");
|
||||
}
|
||||
|
||||
XmlNode defaultConfiguration = getMojoConfiguration(mojoDescriptor);
|
||||
|
||||
List<XmlNode> children = new ArrayList<>();
|
||||
if (mojoDescriptor.getParameters() != null) {
|
||||
for (Parameter parameter : mojoDescriptor.getParameters()) {
|
||||
XmlNode parameterConfiguration = executionConfiguration.getChild(parameter.getName());
|
||||
|
||||
if (parameterConfiguration == null) {
|
||||
parameterConfiguration = executionConfiguration.getChild(parameter.getAlias());
|
||||
}
|
||||
|
||||
XmlNode parameterDefaults = defaultConfiguration.getChild(parameter.getName());
|
||||
|
||||
if (parameterConfiguration != null) {
|
||||
parameterConfiguration = parameterConfiguration.merge(parameterDefaults, Boolean.TRUE);
|
||||
} else {
|
||||
parameterConfiguration = parameterDefaults;
|
||||
}
|
||||
|
||||
if (parameterConfiguration != null) {
|
||||
Map<String, String> attributes = new HashMap<>(parameterConfiguration.getAttributes());
|
||||
|
||||
String attributeForImplementation = parameterConfiguration.getAttribute("implementation");
|
||||
String parameterForImplementation = parameter.getImplementation();
|
||||
if ((attributeForImplementation == null || attributeForImplementation.isEmpty())
|
||||
&& ((parameterForImplementation != null) && !parameterForImplementation.isEmpty())) {
|
||||
attributes.put("implementation", parameter.getImplementation());
|
||||
}
|
||||
|
||||
parameterConfiguration = new XmlNodeImpl(
|
||||
parameter.getName(),
|
||||
parameterConfiguration.getValue(),
|
||||
attributes,
|
||||
parameterConfiguration.getChildren(),
|
||||
parameterConfiguration.getInputLocation());
|
||||
|
||||
children.add(parameterConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
XmlNode finalConfiguration = new XmlNodeImpl("configuration", null, null, children, null);
|
||||
|
||||
mojoExecution.setConfiguration(finalConfiguration);
|
||||
}
|
||||
|
||||
private XmlNode getMojoConfiguration(MojoDescriptor mojoDescriptor) {
|
||||
if (mojoDescriptor.isV4Api()) {
|
||||
return MojoDescriptorCreator.convert(mojoDescriptor.getMojoDescriptorV4());
|
||||
} else {
|
||||
return MojoDescriptorCreator.convert(mojoDescriptor).getDom();
|
||||
}
|
||||
}
|
||||
|
||||
private MojoExecutionConfigurator mojoExecutionConfigurator(MojoExecution mojoExecution) {
|
||||
String configuratorId = mojoExecution.getMojoDescriptor().getComponentConfigurator();
|
||||
if (configuratorId == null) {
|
||||
configuratorId = "default";
|
||||
}
|
||||
|
||||
MojoExecutionConfigurator mojoExecutionConfigurator = mojoExecutionConfigurators.get(configuratorId);
|
||||
|
||||
if (mojoExecutionConfigurator == null) {
|
||||
//
|
||||
// The plugin has a custom component configurator but does not have a custom mojo execution configurator
|
||||
// so fall back to the default mojo execution configurator.
|
||||
//
|
||||
mojoExecutionConfigurator = mojoExecutionConfigurators.get("default");
|
||||
}
|
||||
return mojoExecutionConfigurator;
|
||||
}
|
||||
|
||||
public static void attachToThread(MavenProject currentProject) {
|
||||
ClassRealm projectRealm = currentProject.getClassRealm();
|
||||
if (projectRealm != null) {
|
||||
Thread.currentThread().setContextClassLoader(projectRealm);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class Clock {
|
||||
long start;
|
||||
long end;
|
||||
long resumed;
|
||||
long exec;
|
||||
|
||||
protected void start() {
|
||||
if (start == 0) {
|
||||
start = System.nanoTime();
|
||||
resumed = start;
|
||||
} else {
|
||||
resumed = System.nanoTime();
|
||||
}
|
||||
}
|
||||
|
||||
protected void stop() {
|
||||
end = System.nanoTime();
|
||||
exec += end - resumed;
|
||||
}
|
||||
|
||||
protected long wallTime() {
|
||||
return TimeUnit.NANOSECONDS.toMillis(end - start);
|
||||
}
|
||||
|
||||
protected long execTime() {
|
||||
return TimeUnit.NANOSECONDS.toMillis(exec);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecution;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Logs debug output from the various lifecycle phases.
|
||||
* </p>
|
||||
* <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@Named
|
||||
public class BuildPlanLogger {
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
public void writePlan(BuildPlan plan) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
writePlan(logger::debug, plan);
|
||||
}
|
||||
}
|
||||
|
||||
public void writePlan(BuildPlan plan, MavenProject project) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
writePlan(logger::debug, plan, project);
|
||||
}
|
||||
}
|
||||
|
||||
public void writePlan(Consumer<String> writer, BuildPlan plan) {
|
||||
plan.projects().forEach(project -> writePlan(writer, plan, project));
|
||||
}
|
||||
|
||||
public void writePlan(Consumer<String> writer, BuildPlan plan, MavenProject project) {
|
||||
writer.accept("=== PROJECT BUILD PLAN ================================================");
|
||||
writer.accept("Project: " + getKey(project));
|
||||
writer.accept("Repositories (dependencies): " + project.getRemoteProjectRepositories());
|
||||
writer.accept("Repositories (plugins): " + project.getRemotePluginRepositories());
|
||||
|
||||
Optional<BuildStep> planStep = plan.step(project, BuildStep.PLAN);
|
||||
if (planStep.isPresent() && planStep.get().status.get() == BuildStep.PLANNING) {
|
||||
writer.accept("Build plan will be lazily computed");
|
||||
} else {
|
||||
plan.steps(project)
|
||||
.filter(step ->
|
||||
step.phase != null && step.executions().findAny().isPresent())
|
||||
.sorted(Comparator.comparingInt(plan.sortedNodes()::indexOf))
|
||||
.forEach(step -> {
|
||||
writer.accept("\t-----------------------------------------------------------------------");
|
||||
writer.accept("\tPhase: " + step.name);
|
||||
if (!step.predecessors.isEmpty()) {
|
||||
writer.accept("\tPredecessors: "
|
||||
+ nonEmptyPredecessors(step)
|
||||
.map(n -> phase(project, n, plan.duplicateIds()))
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
/*
|
||||
if (!node.successors.isEmpty()) {
|
||||
writer.accept("\tSuccessors: "
|
||||
+ node.successors.stream()
|
||||
.map(n -> phase(currentProject, n, duplicateIds))
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
*/
|
||||
step.mojos.values().stream()
|
||||
.flatMap(m -> m.values().stream())
|
||||
.forEach(mojo -> mojo(writer, mojo));
|
||||
});
|
||||
}
|
||||
|
||||
writer.accept("=======================================================================");
|
||||
}
|
||||
|
||||
protected Stream<BuildStep> nonEmptyPredecessors(BuildStep step) {
|
||||
HashSet<BuildStep> preds = new HashSet<>();
|
||||
nonEmptyPredecessors(step, preds, new HashSet<>());
|
||||
return preds.stream();
|
||||
}
|
||||
|
||||
private void nonEmptyPredecessors(BuildStep step, Set<BuildStep> preds, Set<BuildStep> visited) {
|
||||
if (visited.add(step)) {
|
||||
step.predecessors.forEach(ch -> {
|
||||
if (ch.executions().findAny().isPresent()) {
|
||||
preds.add(ch);
|
||||
} else {
|
||||
nonEmptyPredecessors(ch, preds, visited);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected String phase(MavenProject currentProject, BuildStep step, Set<String> duplicateIds) {
|
||||
if (step.project == currentProject) {
|
||||
return step.name;
|
||||
} else {
|
||||
String artifactId = step.project.getArtifactId();
|
||||
if (duplicateIds.contains(artifactId)) {
|
||||
return step.name + "(" + step.project.getGroupId() + ":" + artifactId + ")";
|
||||
} else {
|
||||
return step.name + "(:" + artifactId + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void mojo(Consumer<String> writer, MojoExecution mojoExecution) {
|
||||
String mojoExecId =
|
||||
mojoExecution.getGroupId() + ':' + mojoExecution.getArtifactId() + ':' + mojoExecution.getVersion()
|
||||
+ ':' + mojoExecution.getGoal() + " (" + mojoExecution.getExecutionId() + ')';
|
||||
|
||||
Map<String, List<MojoExecution>> forkedExecutions = mojoExecution.getForkedExecutions();
|
||||
if (!forkedExecutions.isEmpty()) {
|
||||
for (Map.Entry<String, List<MojoExecution>> fork : forkedExecutions.entrySet()) {
|
||||
writer.accept("\t--- init fork of " + fork.getKey() + " for " + mojoExecId + " ---");
|
||||
|
||||
for (MojoExecution forkedExecution : fork.getValue()) {
|
||||
mojo(writer, forkedExecution);
|
||||
}
|
||||
|
||||
writer.accept("\t--- exit fork of " + fork.getKey() + " for " + mojoExecId + " ---");
|
||||
}
|
||||
}
|
||||
|
||||
writer.accept("\t\t-----------------------------------------------------------------------");
|
||||
if (mojoExecution.getMojoDescriptor().isAggregator()) {
|
||||
writer.accept("\t\tAggregator goal: " + mojoExecId);
|
||||
} else {
|
||||
writer.accept("\t\tGoal: " + mojoExecId);
|
||||
}
|
||||
if (mojoExecution.getConfiguration() != null) {
|
||||
writer.accept("\t\tConfiguration: " + mojoExecution.getConfiguration());
|
||||
}
|
||||
if (mojoExecution.getMojoDescriptor().getDependencyCollectionRequired() != null) {
|
||||
writer.accept("\t\tDependencies (collect): "
|
||||
+ mojoExecution.getMojoDescriptor().getDependencyCollectionRequired());
|
||||
}
|
||||
if (mojoExecution.getMojoDescriptor().getDependencyResolutionRequired() != null) {
|
||||
writer.accept("\t\tDependencies (resolve): "
|
||||
+ mojoExecution.getMojoDescriptor().getDependencyResolutionRequired());
|
||||
}
|
||||
}
|
||||
|
||||
protected String getKey(MavenProject project) {
|
||||
return project.getGroupId() + ':' + project.getArtifactId() + ':' + project.getVersion();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.maven.api.Lifecycle;
|
||||
import org.apache.maven.plugin.MojoExecution;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
|
||||
public class BuildStep {
|
||||
public static final int CREATED = 0;
|
||||
public static final int PLANNING = 1;
|
||||
public static final int SCHEDULED = 2;
|
||||
public static final int EXECUTED = 3;
|
||||
public static final int FAILED = 4;
|
||||
|
||||
public static final String PLAN = "$plan$";
|
||||
public static final String SETUP = "$setup$";
|
||||
public static final String TEARDOWN = "$teardown$";
|
||||
|
||||
final MavenProject project;
|
||||
final String name;
|
||||
final Lifecycle.Phase phase;
|
||||
final Map<Integer, Map<String, MojoExecution>> mojos = new TreeMap<>();
|
||||
final Collection<BuildStep> predecessors = new HashSet<>();
|
||||
final Collection<BuildStep> successors = new HashSet<>();
|
||||
final AtomicInteger status = new AtomicInteger();
|
||||
final AtomicBoolean skip = new AtomicBoolean();
|
||||
|
||||
public BuildStep(String name, MavenProject project, Lifecycle.Phase phase) {
|
||||
this.name = name;
|
||||
this.project = project;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public Stream<BuildStep> allPredecessors() {
|
||||
return preds(new HashSet<>()).stream();
|
||||
}
|
||||
|
||||
private Set<BuildStep> preds(Set<BuildStep> preds) {
|
||||
if (preds.add(this)) {
|
||||
this.predecessors.forEach(n -> n.preds(preds));
|
||||
}
|
||||
return preds;
|
||||
}
|
||||
|
||||
public boolean isSuccessorOf(BuildStep step) {
|
||||
return isSuccessorOf(new HashSet<>(), step);
|
||||
}
|
||||
|
||||
private boolean isSuccessorOf(Set<BuildStep> visited, BuildStep step) {
|
||||
if (this == step) {
|
||||
return true;
|
||||
} else if (visited.add(this)) {
|
||||
return this.predecessors.stream().anyMatch(n -> n.isSuccessorOf(visited, step));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void skip() {
|
||||
skip.set(true);
|
||||
mojos.clear();
|
||||
}
|
||||
|
||||
public void addMojo(MojoExecution mojo, int priority) {
|
||||
if (!skip.get()) {
|
||||
mojos.computeIfAbsent(priority, k -> new LinkedHashMap<>())
|
||||
.put(mojo.getGoal() + ":" + mojo.getExecutionId(), mojo);
|
||||
}
|
||||
}
|
||||
|
||||
public void executeAfter(BuildStep stepToExecuteBefore) {
|
||||
if (!isSuccessorOf(stepToExecuteBefore)) {
|
||||
predecessors.add(stepToExecuteBefore);
|
||||
stepToExecuteBefore.successors.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<MojoExecution> executions() {
|
||||
return mojos.values().stream().flatMap(m -> m.values().stream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
BuildStep that = (BuildStep) o;
|
||||
return Objects.equals(project, that.project) && Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(project, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BuildStep[" + "project="
|
||||
+ project.getGroupId() + ":" + project.getArtifactId() + ", phase="
|
||||
+ name + ']';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.maven.execution.ExecutionEvent;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.lifecycle.DefaultLifecycles;
|
||||
import org.apache.maven.lifecycle.MissingProjectException;
|
||||
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;
|
||||
import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
|
||||
import org.apache.maven.lifecycle.internal.ReactorContext;
|
||||
import org.apache.maven.lifecycle.internal.TaskSegment;
|
||||
import org.apache.maven.plugin.descriptor.MojoDescriptor;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Starts the build life cycle
|
||||
*/
|
||||
@Named("concurrent")
|
||||
@Singleton
|
||||
public class ConcurrentLifecycleStarter implements LifecycleStarter {
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final ExecutionEventCatapult eventCatapult;
|
||||
private final DefaultLifecycles defaultLifeCycles;
|
||||
private final BuildPlanExecutor executor;
|
||||
private final LifecyclePluginResolver lifecyclePluginResolver;
|
||||
private final MojoDescriptorCreator mojoDescriptorCreator;
|
||||
|
||||
@Inject
|
||||
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;
|
||||
}
|
||||
|
||||
public void execute(MavenSession session) {
|
||||
eventCatapult.fire(ExecutionEvent.Type.SessionStarted, session, null);
|
||||
|
||||
try {
|
||||
if (requiresProject(session) && projectIsNotPresent(session)) {
|
||||
throw new MissingProjectException("The goal you specified requires a project to execute"
|
||||
+ " but there is no POM in this directory (" + session.getTopDirectory() + ")."
|
||||
+ " Please verify you invoked Maven from the correct directory.");
|
||||
}
|
||||
|
||||
List<TaskSegment> taskSegments = calculateTaskSegments(session);
|
||||
if (taskSegments.isEmpty()) {
|
||||
throw new NoGoalSpecifiedException("No goals have been specified for this build."
|
||||
+ " You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or"
|
||||
+ " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>."
|
||||
+ " Available lifecycle phases are: " + defaultLifeCycles.getLifecyclePhaseList() + ".");
|
||||
}
|
||||
|
||||
int degreeOfConcurrency = session.getRequest().getDegreeOfConcurrency();
|
||||
if (degreeOfConcurrency > 1) {
|
||||
logger.info("");
|
||||
logger.info(String.format(
|
||||
"Using the %s implementation with a thread count of %d",
|
||||
executor.getClass().getSimpleName(), degreeOfConcurrency));
|
||||
}
|
||||
|
||||
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus(session.getProjectDependencyGraph());
|
||||
ReactorContext reactorContext =
|
||||
new ReactorContext(session.getResult(), oldContextClassLoader, reactorBuildStatus);
|
||||
executor.execute(session, reactorContext, taskSegments);
|
||||
|
||||
} catch (Exception e) {
|
||||
session.getResult().addException(e);
|
||||
} finally {
|
||||
eventCatapult.fire(ExecutionEvent.Type.SessionEnded, session, null);
|
||||
}
|
||||
}
|
||||
|
||||
public List<TaskSegment> calculateTaskSegments(MavenSession session) throws Exception {
|
||||
|
||||
MavenProject rootProject = session.getTopLevelProject();
|
||||
|
||||
List<String> tasks = requireNonNull(session.getGoals()); // session never returns null, but empty list
|
||||
|
||||
if (tasks.isEmpty()
|
||||
&& (rootProject.getDefaultGoal() != null
|
||||
&& !rootProject.getDefaultGoal().isEmpty())) {
|
||||
tasks = Stream.of(rootProject.getDefaultGoal().split("\\s+"))
|
||||
.filter(g -> !g.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return calculateTaskSegments(session, tasks);
|
||||
}
|
||||
|
||||
public List<TaskSegment> calculateTaskSegments(MavenSession session, List<String> tasks) throws Exception {
|
||||
List<TaskSegment> taskSegments = new ArrayList<>(tasks.size());
|
||||
|
||||
TaskSegment currentSegment = null;
|
||||
|
||||
for (String task : tasks) {
|
||||
if (isGoalSpecification(task)) {
|
||||
// "pluginPrefix[:version]:goal" or "groupId:artifactId[:version]:goal"
|
||||
|
||||
lifecyclePluginResolver.resolveMissingPluginVersions(session.getTopLevelProject(), session);
|
||||
|
||||
MojoDescriptor mojoDescriptor =
|
||||
mojoDescriptorCreator.getMojoDescriptor(task, session, session.getTopLevelProject());
|
||||
|
||||
boolean aggregating = mojoDescriptor.isAggregator() || !mojoDescriptor.isProjectRequired();
|
||||
|
||||
if (currentSegment == null || currentSegment.isAggregating() != aggregating) {
|
||||
currentSegment = new TaskSegment(aggregating);
|
||||
taskSegments.add(currentSegment);
|
||||
}
|
||||
|
||||
currentSegment.getTasks().add(new GoalTask(task));
|
||||
} else {
|
||||
// lifecycle phase
|
||||
|
||||
if (currentSegment == null || currentSegment.isAggregating()) {
|
||||
currentSegment = new TaskSegment(false);
|
||||
taskSegments.add(currentSegment);
|
||||
}
|
||||
|
||||
currentSegment.getTasks().add(new LifecycleTask(task));
|
||||
}
|
||||
}
|
||||
|
||||
return taskSegments;
|
||||
}
|
||||
|
||||
private boolean projectIsNotPresent(MavenSession session) {
|
||||
return !session.getRequest().isProjectPresent();
|
||||
}
|
||||
|
||||
private boolean requiresProject(MavenSession session) {
|
||||
List<String> goals = session.getGoals();
|
||||
if (goals != null) {
|
||||
for (String goal : goals) {
|
||||
if (!isGoalSpecification(goal)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isGoalSpecification(String task) {
|
||||
return task.indexOf(':') >= 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.apache.maven.slf4j.MavenSimpleLogger;
|
||||
|
||||
/**
|
||||
* Forwards log messages to the client.
|
||||
*/
|
||||
public class ConcurrentLogOutput implements AutoCloseable {
|
||||
|
||||
private static final ThreadLocal<ProjectExecutionContext> CONTEXT = new InheritableThreadLocal<>();
|
||||
|
||||
public ConcurrentLogOutput() {
|
||||
MavenSimpleLogger.setLogSink(this::accept);
|
||||
}
|
||||
|
||||
protected void accept(String message) {
|
||||
ProjectExecutionContext context = CONTEXT.get();
|
||||
if (context != null) {
|
||||
context.accept(message);
|
||||
} else {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
MavenSimpleLogger.setLogSink(null);
|
||||
}
|
||||
|
||||
public AutoCloseable build(MavenProject project) {
|
||||
return new ProjectExecutionContext(project);
|
||||
}
|
||||
|
||||
private static class ProjectExecutionContext implements AutoCloseable {
|
||||
final MavenProject project;
|
||||
final List<String> messages = new CopyOnWriteArrayList<>();
|
||||
boolean closed;
|
||||
|
||||
ProjectExecutionContext(MavenProject project) {
|
||||
this.project = project;
|
||||
CONTEXT.set(this);
|
||||
}
|
||||
|
||||
void accept(String message) {
|
||||
if (!closed) {
|
||||
this.messages.add(message);
|
||||
} else {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed = true;
|
||||
CONTEXT.set(null);
|
||||
this.messages.forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.maven.api.services.MessageBuilderFactory;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
|
||||
import org.apache.maven.lifecycle.internal.LifecycleDependencyResolver;
|
||||
import org.apache.maven.plugin.BuildPluginManager;
|
||||
import org.apache.maven.plugin.MavenPluginManager;
|
||||
import org.apache.maven.plugin.MojosExecutionStrategy;
|
||||
|
||||
@Named("concurrent")
|
||||
@Singleton
|
||||
public class MojoExecutor extends org.apache.maven.lifecycle.internal.MojoExecutor {
|
||||
|
||||
@Inject
|
||||
public MojoExecutor(
|
||||
BuildPluginManager pluginManager,
|
||||
MavenPluginManager mavenPluginManager,
|
||||
LifecycleDependencyResolver lifeCycleDependencyResolver,
|
||||
ExecutionEventCatapult eventCatapult,
|
||||
Provider<MojosExecutionStrategy> mojosExecutionStrategy,
|
||||
MessageBuilderFactory messageBuilderFactory) {
|
||||
super(
|
||||
pluginManager,
|
||||
mavenPluginManager,
|
||||
lifeCycleDependencyResolver,
|
||||
eventCatapult,
|
||||
mojosExecutionStrategy,
|
||||
messageBuilderFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useProjectLock(MavenSession session) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Phaser;
|
||||
|
||||
public class PhasingExecutor implements Executor, AutoCloseable {
|
||||
private final ExecutorService executor;
|
||||
private final Phaser phaser = new Phaser();
|
||||
|
||||
public PhasingExecutor(ExecutorService executor) {
|
||||
this.executor = executor;
|
||||
this.phaser.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
phaser.register();
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
command.run();
|
||||
} finally {
|
||||
phaser.arriveAndDeregister();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void await() {
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.maven.api.Lifecycle;
|
||||
import org.apache.maven.api.model.Plugin;
|
||||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
|
||||
class PluginLifecycle implements Lifecycle {
|
||||
private final org.apache.maven.api.plugin.descriptor.lifecycle.Lifecycle lifecycleOverlay;
|
||||
private final PluginDescriptor pluginDescriptor;
|
||||
|
||||
PluginLifecycle(
|
||||
org.apache.maven.api.plugin.descriptor.lifecycle.Lifecycle lifecycleOverlay,
|
||||
PluginDescriptor pluginDescriptor) {
|
||||
this.lifecycleOverlay = lifecycleOverlay;
|
||||
this.pluginDescriptor = pluginDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return lifecycleOverlay.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Phase> phases() {
|
||||
return lifecycleOverlay.getPhases().stream()
|
||||
.map(phase -> new Phase() {
|
||||
@Override
|
||||
public String name() {
|
||||
return phase.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Plugin> plugins() {
|
||||
return Collections.singletonList(Plugin.newBuilder()
|
||||
.groupId(pluginDescriptor.getGroupId())
|
||||
.artifactId(pluginDescriptor.getArtifactId())
|
||||
.version(pluginDescriptor.getVersion())
|
||||
.configuration(phase.getConfiguration())
|
||||
.executions(phase.getExecutions().stream()
|
||||
.map(exec -> org.apache.maven.api.model.PluginExecution.newBuilder()
|
||||
.goals(exec.getGoals())
|
||||
.configuration(exec.getConfiguration())
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Link> links() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Phase> phases() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Phase> allPhases() {
|
||||
return Stream.concat(Stream.of(this), phases().stream().flatMap(Phase::allPhases));
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Alias> aliases() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.maven.internal.impl.DefaultLifecycleRegistry;
|
||||
import org.apache.maven.plugin.MojoExecution;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class BuildPlanCreatorTest {
|
||||
|
||||
@Test
|
||||
void testMulti() {
|
||||
MavenProject project = new MavenProject();
|
||||
Map<MavenProject, List<MavenProject>> projects = Collections.singletonMap(project, Collections.emptyList());
|
||||
|
||||
BuildPlan plan = calculateLifecycleMappings(projects, "package");
|
||||
|
||||
new BuildPlanLogger().writePlan(System.out::println, plan);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCondense() {
|
||||
MavenProject p1 = new MavenProject();
|
||||
p1.setArtifactId("p1");
|
||||
MavenProject p2 = new MavenProject();
|
||||
p2.setArtifactId("p2");
|
||||
Map<MavenProject, List<MavenProject>> projects = new HashMap<>();
|
||||
projects.put(p1, Collections.emptyList());
|
||||
projects.put(p2, Collections.singletonList(p1));
|
||||
|
||||
BuildPlan plan = calculateLifecycleMappings(projects, "verify");
|
||||
plan.then(calculateLifecycleMappings(projects, "install"));
|
||||
|
||||
Stream.of(p1, p2).forEach(project -> {
|
||||
plan.requiredStep(project, "after:resources").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "after:test-resources").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "compile").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "test-compile").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "test").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "package").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "install").addMojo(new MojoExecution(null), 0);
|
||||
});
|
||||
|
||||
new BuildPlanLogger() {
|
||||
@Override
|
||||
protected void mojo(Consumer<String> writer, MojoExecution mojoExecution) {}
|
||||
}.writePlan(System.out::println, plan);
|
||||
|
||||
plan.allSteps().forEach(phase -> {
|
||||
phase.predecessors.forEach(
|
||||
pred -> assertTrue(plan.step(pred.project, pred.name).isPresent(), "Phase not present: " + pred));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAlias() {
|
||||
MavenProject p1 = new MavenProject();
|
||||
p1.setArtifactId("p1");
|
||||
Map<MavenProject, List<MavenProject>> projects = Collections.singletonMap(p1, Collections.emptyList());
|
||||
|
||||
BuildPlan plan = calculateLifecycleMappings(projects, "generate-resources");
|
||||
}
|
||||
|
||||
private BuildPlan calculateLifecycleMappings(Map<MavenProject, List<MavenProject>> projects, String phase) {
|
||||
DefaultLifecycleRegistry lifecycles = new DefaultLifecycleRegistry(Collections.emptyList());
|
||||
BuildPlanExecutor builder = new BuildPlanExecutor(null, null, null, null, null, null, null, null, lifecycles);
|
||||
BuildPlanExecutor.BuildContext context = builder.new BuildContext();
|
||||
return context.calculateLifecycleMappings(projects, phase);
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
void testPlugins() {
|
||||
DefaultLifecycleRegistry lifecycles =
|
||||
new DefaultLifecycleRegistry(Collections.emptyList(), Collections.emptyMap());
|
||||
BuildPlanCreator builder = new BuildPlanCreator(null, null, null, null, null, lifecycles);
|
||||
MavenProject p1 = new MavenProject();
|
||||
p1.setGroupId("g");
|
||||
p1.setArtifactId("p1");
|
||||
p1.getBuild().getPlugins().add(new Plugin(org.apache.maven.api.model.Plugin.newBuilder()
|
||||
.groupId("g").artifactId("p2")
|
||||
.
|
||||
.build()))
|
||||
MavenProject p2 = new MavenProject();
|
||||
p2.setGroupId("g");
|
||||
p2.setArtifactId("p2");
|
||||
|
||||
Map<MavenProject, List<MavenProject>> projects = new HashMap<>();
|
||||
projects.put(p1, Collections.emptyList());
|
||||
projects.put(p2, Collections.singletonList(p1));
|
||||
Lifecycle lifecycle = lifecycles.require("default");
|
||||
BuildPlan plan = builder.calculateLifecycleMappings(null, projects, lifecycle, "verify");
|
||||
plan.then(builder.calculateLifecycleMappings(null, projects, lifecycle, "install"));
|
||||
|
||||
Stream.of(p1, p2).forEach(project -> {
|
||||
plan.requiredStep(project, "post:resources").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "post:test-resources").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "compile").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "test-compile").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "test").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "package").addMojo(new MojoExecution(null), 0);
|
||||
plan.requiredStep(project, "install").addMojo(new MojoExecution(null), 0);
|
||||
});
|
||||
|
||||
plan.condense();
|
||||
|
||||
new BuildPlanLogger() {
|
||||
@Override
|
||||
protected void mojo(Consumer<String> writer, MojoExecution mojoExecution) {}
|
||||
}.writePlan(System.out::println, plan);
|
||||
|
||||
plan.allSteps().forEach(phase -> {
|
||||
phase.predecessors.forEach(
|
||||
pred -> assertTrue(plan.step(pred.project, pred.name).isPresent(), "Phase not present: " + pred));
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.concurrent;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class PhasingExecutorTest {
|
||||
|
||||
@Test
|
||||
void testPhaser() {
|
||||
PhasingExecutor p = new PhasingExecutor(Executors.newFixedThreadPool(4));
|
||||
p.execute(() -> waitSomeTime(p, 2));
|
||||
p.await();
|
||||
}
|
||||
|
||||
private void waitSomeTime(Executor executor, int nb) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
if (nb > 0) {
|
||||
executor.execute(() -> waitSomeTime(executor, nb - 1));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue