103
103
import static org .apache .maven .lifecycle .internal .concurrent .BuildStep .TEARDOWN ;
104
104
105
105
/**
106
- * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project).
107
- * <p>
108
- * This builder uses a number of threads equal to the minimum of the degree of concurrency (which is the thread count
109
- * set with <code>-T</code> on the command-line) and the number of projects to build. As such, building a single project
110
- * will always result in a sequential build, regardless of the thread count.
111
- * </p>
112
- * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
106
+ * Executes the Maven build plan in a concurrent manner, handling the lifecycle phases and plugin executions.
107
+ * This executor implements a weave-mode build strategy, where builds are executed phase-by-phase rather than
108
+ * project-by-project.
109
+ *
110
+ * <h2>Key Features:</h2>
111
+ * <ul>
112
+ * <li>Concurrent execution of compatible build steps across projects</li>
113
+ * <li>Thread-safety validation for plugins</li>
114
+ * <li>Support for forked executions and lifecycle phases</li>
115
+ * <li>Dynamic build plan adjustment during execution</li>
116
+ * </ul>
117
+ *
118
+ * <h2>Execution Strategy:</h2>
119
+ * <p>The executor follows these main steps:</p>
120
+ * <ol>
121
+ * <li>Initial plan creation based on project dependencies and task segments</li>
122
+ * <li>Concurrent execution of build steps while maintaining dependency order</li>
123
+ * <li>Dynamic replanning when necessary (e.g., for forked executions)</li>
124
+ * <li>Project setup, execution, and teardown phases management</li>
125
+ * </ol>
126
+ *
127
+ * <h2>Thread Management:</h2>
128
+ * <p>The number of threads used is determined by:</p>
129
+ * <pre>
130
+ * min(degreeOfConcurrency, numberOfProjects)
131
+ * </pre>
132
+ * where degreeOfConcurrency is set via the -T command-line option.
133
+ *
134
+ * <h2>Build Step States:</h2>
135
+ * <ul>
136
+ * <li>CREATED: Initial state of a build step</li>
137
+ * <li>PLANNING: Step is being planned</li>
138
+ * <li>SCHEDULED: Step is queued for execution</li>
139
+ * <li>EXECUTED: Step has completed successfully</li>
140
+ * <li>FAILED: Step execution failed</li>
141
+ * </ul>
142
+ *
143
+ * <p><strong>NOTE:</strong> This class is not part of any public API and can be changed or deleted without prior notice.</p>
113
144
*
114
145
* @since 3.0
115
- * Builds one or more lifecycles for a full module
116
- * NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
117
146
*/
118
147
@ Named
119
148
public class BuildPlanExecutor {
@@ -225,6 +254,7 @@ public BuildPlan buildInitialPlan(List<TaskSegment> taskSegments) {
225
254
pplan .status .set (PLANNING ); // the plan step always need planning
226
255
BuildStep setup = new BuildStep (SETUP , project , null );
227
256
BuildStep teardown = new BuildStep (TEARDOWN , project , null );
257
+ teardown .executeAfter (setup );
228
258
setup .executeAfter (pplan );
229
259
plan .steps (project ).forEach (step -> {
230
260
if (step .predecessors .isEmpty ()) {
@@ -322,6 +352,10 @@ private void executePlan() {
322
352
global .start ();
323
353
lock .readLock ().lock ();
324
354
try {
355
+ // Get all build steps that are:
356
+ // 1. Not yet started (CREATED status)
357
+ // 2. Have all their prerequisites completed (predecessors EXECUTED)
358
+ // 3. Successfully transition from CREATED to SCHEDULED state
325
359
plan .sortedNodes ().stream ()
326
360
.filter (step -> step .status .get () == CREATED )
327
361
.filter (step -> step .predecessors .stream ().allMatch (s -> s .status .get () == EXECUTED ))
@@ -356,6 +390,17 @@ private void executePlan() {
356
390
}
357
391
}
358
392
393
+ /**
394
+ * Executes all pending after:* phases for a failed project.
395
+ * This ensures proper cleanup is performed even when a build fails.
396
+ * Only executes after:xxx phases if their corresponding before:xxx phase
397
+ * has been either executed or failed.
398
+ *
399
+ * For example, if a project fails during 'compile', this will execute
400
+ * any configured 'after:compile' phases to ensure proper cleanup.
401
+ *
402
+ * @param failedStep The build step that failed, containing the project that needs cleanup
403
+ */
359
404
private void executeAfterPhases (BuildStep failedStep ) {
360
405
if (failedStep == null || failedStep .project == null ) {
361
406
return ;
@@ -393,6 +438,17 @@ private void executeAfterPhases(BuildStep failedStep) {
393
438
}
394
439
}
395
440
441
+ /**
442
+ * Executes a single build step, which can be one of:
443
+ * - PLAN: Project build planning
444
+ * - SETUP: Project initialization
445
+ * - TEARDOWN: Project cleanup
446
+ * - Default: Actual mojo/plugin executions
447
+ *
448
+ * @param step The build step to execute
449
+ * @throws IOException If there's an IO error during execution
450
+ * @throws LifecycleExecutionException If there's a lifecycle execution error
451
+ */
396
452
private void executeStep (BuildStep step ) throws IOException , LifecycleExecutionException {
397
453
Clock clock = getClock (step .project );
398
454
switch (step .name ) {
@@ -796,16 +852,27 @@ public BuildPlan calculateLifecycleMappings(
796
852
plan .allSteps ().filter (step -> step .phase != null ).forEach (step -> {
797
853
Lifecycle .Phase phase = step .phase ;
798
854
MavenProject project = step .project ;
799
- phase .links ().stream ()
800
- .filter (l -> l .pointer ().type () != Lifecycle .Pointer .Type .PROJECT )
801
- .forEach (link -> {
802
- String n1 = phase .name ();
803
- String n2 = link .pointer ().phase ();
804
- // for each project, if the phase in the build, link after it
805
- getLinkedProjects (projects , project , link ).forEach (p -> plan .step (p , AFTER + n2 )
806
- .ifPresent (a -> plan .requiredStep (project , BEFORE + n1 )
807
- .executeAfter (a )));
855
+ phase .links ().stream ().forEach (link -> {
856
+ BuildStep before = plan .requiredStep (project , BEFORE + phase .name ());
857
+ BuildStep after = plan .requiredStep (project , AFTER + phase .name ());
858
+ Lifecycle .Pointer pointer = link .pointer ();
859
+ String n2 = pointer .phase ();
860
+ if (pointer instanceof Lifecycle .DependenciesPointer ) {
861
+ // For dependencies: ensure current project's phase starts after dependency's phase completes
862
+ // Example: project's compile starts after dependency's package completes
863
+ // TODO: String scope = ((Lifecycle.DependenciesPointer) pointer).scope();
864
+ projects .get (project )
865
+ .forEach (p -> plan .step (p , AFTER + n2 ).ifPresent (before ::executeAfter ));
866
+ } else if (pointer instanceof Lifecycle .ChildrenPointer ) {
867
+ // For children: ensure bidirectional phase coordination
868
+ project .getCollectedProjects ().forEach (p -> {
869
+ // 1. Child's phase start waits for parent's phase start
870
+ plan .step (p , BEFORE + n2 ).ifPresent (before ::executeBefore );
871
+ // 2. Parent's phase completion waits for child's phase completion
872
+ plan .step (p , AFTER + n2 ).ifPresent (after ::executeAfter );
808
873
});
874
+ }
875
+ });
809
876
});
810
877
811
878
// Keep projects in reactors by GAV
@@ -832,19 +899,6 @@ public BuildPlan calculateLifecycleMappings(
832
899
833
900
return plan ;
834
901
}
835
-
836
- private List <MavenProject > getLinkedProjects (
837
- Map <MavenProject , List <MavenProject >> projects , MavenProject project , Lifecycle .Link link ) {
838
- if (link .pointer ().type () == Lifecycle .Pointer .Type .DEPENDENCIES ) {
839
- // TODO: String scope = ((Lifecycle.DependenciesPointer) link.pointer()).scope();
840
- return projects .get (project );
841
- } else if (link .pointer ().type () == Lifecycle .Pointer .Type .CHILDREN ) {
842
- return project .getCollectedProjects ();
843
- } else {
844
- throw new IllegalArgumentException (
845
- "Unsupported pointer type: " + link .pointer ().type ());
846
- }
847
- }
848
902
}
849
903
850
904
private void resolvePlugin (MavenSession session , List <RemoteRepository > repositories , Plugin plugin ) {
0 commit comments