Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ interface Phase {
// Maven defined phases
// ======================
String ALL = "all";
String EACH = "each";
String BUILD = "build";
String INITIALIZE = "initialize";
String VALIDATE = "validate";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,18 @@ private void processEvent(ExecutionEvent event) {
if (!Objects.equals(phase, phases.peekLast())) {
phases.addLast(phase);
if ("clean".equals(phase)) {
cleanProjectLocalRepository(project);
synchronized (project) {
cleanProjectLocalRepository(project);
}
}
}
}
break;
case ProjectSucceeded:
case ForkedProjectSucceeded:
installIntoProjectLocalRepository(project);
synchronized (project) {
installIntoProjectLocalRepository(project);
}
break;
default:
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.api.DependencyScope;
import org.apache.maven.api.Lifecycle;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.InputSource;
Expand All @@ -55,6 +56,7 @@
import static org.apache.maven.api.Lifecycle.Phase.BUILD;
import static org.apache.maven.api.Lifecycle.Phase.COMPILE;
import static org.apache.maven.api.Lifecycle.Phase.DEPLOY;
import static org.apache.maven.api.Lifecycle.Phase.EACH;
import static org.apache.maven.api.Lifecycle.Phase.INITIALIZE;
import static org.apache.maven.api.Lifecycle.Phase.INSTALL;
import static org.apache.maven.api.Lifecycle.Phase.INTEGRATION_TEST;
Expand All @@ -71,6 +73,7 @@
import static org.apache.maven.api.Lifecycle.Phase.VERIFY;
import static org.apache.maven.internal.impl.Lifecycles.after;
import static org.apache.maven.internal.impl.Lifecycles.alias;
import static org.apache.maven.internal.impl.Lifecycles.children;
import static org.apache.maven.internal.impl.Lifecycles.dependencies;
import static org.apache.maven.internal.impl.Lifecycles.phase;
import static org.apache.maven.internal.impl.Lifecycles.plugin;
Expand All @@ -91,6 +94,11 @@ public class DefaultLifecycleRegistry implements LifecycleRegistry {
public static final InputLocation DEFAULT_LIFECYCLE_INPUT_LOCATION =
new InputLocation(new InputSource(DEFAULT_LIFECYCLE_MODELID, null));

public static final String SCOPE_COMPILE = DependencyScope.COMPILE.id();
public static final String SCOPE_RUNTIME = DependencyScope.RUNTIME.id();
public static final String SCOPE_TEST_ONLY = DependencyScope.TEST_ONLY.id();
public static final String SCOPE_TEST = DependencyScope.TEST.id();

private final List<LifecycleProvider> providers;

public DefaultLifecycleRegistry() {
Expand Down Expand Up @@ -384,35 +392,38 @@ public Collection<Phase> phases() {
// START SNIPPET: default
return List.of(phase(
ALL,
phase(VALIDATE, phase(INITIALIZE)),
children(ALL),
phase(
BUILD,
after(VALIDATE),
phase(SOURCES),
phase(RESOURCES),
phase(COMPILE, after(SOURCES), dependencies(COMPILE, READY)),
phase(READY, after(COMPILE), after(RESOURCES)),
phase(PACKAGE, after(READY), dependencies("runtime", PACKAGE))),
phase(
VERIFY,
after(VALIDATE),
EACH,
phase(VALIDATE, phase(INITIALIZE)),
phase(
UNIT_TEST,
phase(TEST_SOURCES),
phase(TEST_RESOURCES),
phase(
TEST_COMPILE,
after(TEST_SOURCES),
after(READY),
dependencies("test-only", READY)),
BUILD,
after(VALIDATE),
phase(SOURCES),
phase(RESOURCES),
phase(COMPILE, after(SOURCES), dependencies(SCOPE_COMPILE, READY)),
phase(READY, after(COMPILE), after(RESOURCES)),
phase(PACKAGE, after(READY), dependencies(SCOPE_RUNTIME, PACKAGE))),
phase(
VERIFY,
after(VALIDATE),
phase(
TEST,
after(TEST_COMPILE),
after(TEST_RESOURCES),
dependencies("test", READY))),
phase(INTEGRATION_TEST)),
phase(INSTALL, after(PACKAGE)),
phase(DEPLOY, after(PACKAGE))));
UNIT_TEST,
phase(TEST_SOURCES),
phase(TEST_RESOURCES),
phase(
TEST_COMPILE,
after(TEST_SOURCES),
after(READY),
dependencies(SCOPE_TEST_ONLY, READY)),
phase(
TEST,
after(TEST_COMPILE),
after(TEST_RESOURCES),
dependencies(SCOPE_TEST, READY))),
phase(INTEGRATION_TEST)),
phase(INSTALL, after(PACKAGE)),
phase(DEPLOY, after(PACKAGE)))));
// END SNIPPET: default
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ static Plugin plugin(String coords, String phase) {
}

/** Indicates the phase is after the phases given in arguments */
static Lifecycle.Link after(String b) {
static Lifecycle.Link after(String phase) {
return new Lifecycle.Link() {
@Override
public Kind kind() {
Expand All @@ -101,10 +101,20 @@ public Lifecycle.Pointer pointer() {
return new Lifecycle.PhasePointer() {
@Override
public String phase() {
return b;
return phase;
}

@Override
public String toString() {
return "phase(" + phase + ")";
}
};
}

@Override
public String toString() {
return "after(" + pointer() + ")";
}
};
}

Expand All @@ -128,8 +138,47 @@ public String phase() {
public String scope() {
return scope;
}

@Override
public String toString() {
return "dependencies(" + scope + ", " + phase + ")";
}
};
}

@Override
public String toString() {
return "after(" + pointer() + ")";
}
};
}

static Lifecycle.Link children(String phase) {
return new Lifecycle.Link() {
@Override
public Kind kind() {
return Kind.AFTER;
}

@Override
public Lifecycle.Pointer pointer() {
return new Lifecycle.ChildrenPointer() {
@Override
public String phase() {
return phase;
}

@Override
public String toString() {
return "children(" + phase + ")";
}
};
}

@Override
public String toString() {
return "after(" + pointer() + ")";
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,46 @@
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.
* Executes the Maven build plan in a concurrent manner, handling the lifecycle phases and plugin executions.
* This executor implements a weave-mode build strategy, where builds are executed phase-by-phase rather than
* project-by-project.
*
* <h2>Key Features:</h2>
* <ul>
* <li>Concurrent execution of compatible build steps across projects</li>
* <li>Thread-safety validation for plugins</li>
* <li>Support for forked executions and lifecycle phases</li>
* <li>Dynamic build plan adjustment during execution</li>
* </ul>
*
* <h2>Execution Strategy:</h2>
* <p>The executor follows these main steps:</p>
* <ol>
* <li>Initial plan creation based on project dependencies and task segments</li>
* <li>Concurrent execution of build steps while maintaining dependency order</li>
* <li>Dynamic replanning when necessary (e.g., for forked executions)</li>
* <li>Project setup, execution, and teardown phases management</li>
* </ol>
*
* <h2>Thread Management:</h2>
* <p>The number of threads used is determined by:</p>
* <pre>
* min(degreeOfConcurrency, numberOfProjects)
* </pre>
* where degreeOfConcurrency is set via the -T command-line option.
*
* <h2>Build Step States:</h2>
* <ul>
* <li>CREATED: Initial state of a build step</li>
* <li>PLANNING: Step is being planned</li>
* <li>SCHEDULED: Step is queued for execution</li>
* <li>EXECUTED: Step has completed successfully</li>
* <li>FAILED: Step execution failed</li>
* </ul>
*
* <p><strong>NOTE:</strong> This class is not part of any public API and can be changed or deleted without prior notice.</p>
*
* @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 {
Expand Down Expand Up @@ -225,6 +254,7 @@ public BuildPlan buildInitialPlan(List<TaskSegment> taskSegments) {
pplan.status.set(PLANNING); // the plan step always need planning
BuildStep setup = new BuildStep(SETUP, project, null);
BuildStep teardown = new BuildStep(TEARDOWN, project, null);
teardown.executeAfter(setup);
setup.executeAfter(pplan);
plan.steps(project).forEach(step -> {
if (step.predecessors.isEmpty()) {
Expand Down Expand Up @@ -322,6 +352,10 @@ private void executePlan() {
global.start();
lock.readLock().lock();
try {
// Get all build steps that are:
// 1. Not yet started (CREATED status)
// 2. Have all their prerequisites completed (predecessors EXECUTED)
// 3. Successfully transition from CREATED to SCHEDULED state
plan.sortedNodes().stream()
.filter(step -> step.status.get() == CREATED)
.filter(step -> step.predecessors.stream().allMatch(s -> s.status.get() == EXECUTED))
Expand Down Expand Up @@ -356,6 +390,17 @@ private void executePlan() {
}
}

/**
* Executes all pending after:* phases for a failed project.
* This ensures proper cleanup is performed even when a build fails.
* Only executes after:xxx phases if their corresponding before:xxx phase
* has been either executed or failed.
*
* For example, if a project fails during 'compile', this will execute
* any configured 'after:compile' phases to ensure proper cleanup.
*
* @param failedStep The build step that failed, containing the project that needs cleanup
*/
private void executeAfterPhases(BuildStep failedStep) {
if (failedStep == null || failedStep.project == null) {
return;
Expand Down Expand Up @@ -393,6 +438,17 @@ private void executeAfterPhases(BuildStep failedStep) {
}
}

/**
* Executes a single build step, which can be one of:
* - PLAN: Project build planning
* - SETUP: Project initialization
* - TEARDOWN: Project cleanup
* - Default: Actual mojo/plugin executions
*
* @param step The build step to execute
* @throws IOException If there's an IO error during execution
* @throws LifecycleExecutionException If there's a lifecycle execution error
*/
private void executeStep(BuildStep step) throws IOException, LifecycleExecutionException {
Clock clock = getClock(step.project);
switch (step.name) {
Expand Down Expand Up @@ -796,16 +852,27 @@ public BuildPlan calculateLifecycleMappings(
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)));
phase.links().stream().forEach(link -> {
BuildStep before = plan.requiredStep(project, BEFORE + phase.name());
BuildStep after = plan.requiredStep(project, AFTER + phase.name());
Lifecycle.Pointer pointer = link.pointer();
String n2 = pointer.phase();
if (pointer instanceof Lifecycle.DependenciesPointer) {
// For dependencies: ensure current project's phase starts after dependency's phase completes
// Example: project's compile starts after dependency's package completes
// TODO: String scope = ((Lifecycle.DependenciesPointer) pointer).scope();
projects.get(project)
.forEach(p -> plan.step(p, AFTER + n2).ifPresent(before::executeAfter));
} else if (pointer instanceof Lifecycle.ChildrenPointer) {
// For children: ensure bidirectional phase coordination
project.getCollectedProjects().forEach(p -> {
// 1. Child's phase start waits for parent's phase start
plan.step(p, BEFORE + n2).ifPresent(before::executeBefore);
// 2. Parent's phase completion waits for child's phase completion
plan.step(p, AFTER + n2).ifPresent(after::executeAfter);
});
}
});
});

// Keep projects in reactors by GAV
Expand All @@ -832,19 +899,6 @@ public BuildPlan calculateLifecycleMappings(

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ public void executeAfter(BuildStep stepToExecuteBefore) {
}
}

public void executeBefore(BuildStep stepToExecuteAfter) {
stepToExecuteAfter.executeAfter(this);
}

public Stream<MojoExecution> executions() {
return mojos.values().stream().flatMap(m -> m.values().stream());
}
Expand Down
Loading