1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.lifecycle.internal.concurrent;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.xml.stream.XMLStreamException;
24
25 import java.io.IOException;
26 import java.time.Duration;
27 import java.time.Instant;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Optional;
35 import java.util.Set;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ExecutionException;
38 import java.util.concurrent.Executors;
39 import java.util.concurrent.locks.ReadWriteLock;
40 import java.util.concurrent.locks.ReentrantReadWriteLock;
41 import java.util.stream.Collectors;
42 import java.util.stream.Stream;
43
44 import org.apache.maven.api.Lifecycle;
45 import org.apache.maven.api.MonotonicClock;
46 import org.apache.maven.api.services.LifecycleRegistry;
47 import org.apache.maven.api.services.MavenException;
48 import org.apache.maven.api.xml.XmlNode;
49 import org.apache.maven.api.xml.XmlService;
50 import org.apache.maven.execution.BuildFailure;
51 import org.apache.maven.execution.BuildSuccess;
52 import org.apache.maven.execution.ExecutionEvent;
53 import org.apache.maven.execution.MavenExecutionRequest;
54 import org.apache.maven.execution.MavenSession;
55 import org.apache.maven.execution.ProjectDependencyGraph;
56 import org.apache.maven.execution.ProjectExecutionEvent;
57 import org.apache.maven.execution.ProjectExecutionListener;
58 import org.apache.maven.impl.util.PhasingExecutor;
59 import org.apache.maven.internal.MultilineMessageHelper;
60 import org.apache.maven.internal.impl.DefaultLifecycleRegistry;
61 import org.apache.maven.internal.transformation.TransformerManager;
62 import org.apache.maven.lifecycle.LifecycleExecutionException;
63 import org.apache.maven.lifecycle.LifecycleNotFoundException;
64 import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException;
65 import org.apache.maven.lifecycle.MojoExecutionConfigurator;
66 import org.apache.maven.lifecycle.internal.BuildThreadFactory;
67 import org.apache.maven.lifecycle.internal.CompoundProjectExecutionListener;
68 import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
69 import org.apache.maven.lifecycle.internal.GoalTask;
70 import org.apache.maven.lifecycle.internal.LifecycleTask;
71 import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
72 import org.apache.maven.lifecycle.internal.MojoExecutor;
73 import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
74 import org.apache.maven.lifecycle.internal.ReactorContext;
75 import org.apache.maven.lifecycle.internal.Task;
76 import org.apache.maven.lifecycle.internal.TaskSegment;
77 import org.apache.maven.model.Plugin;
78 import org.apache.maven.model.PluginExecution;
79 import org.apache.maven.plugin.MavenPluginManager;
80 import org.apache.maven.plugin.MojoExecution;
81 import org.apache.maven.plugin.MojoNotFoundException;
82 import org.apache.maven.plugin.PluginDescriptorParsingException;
83 import org.apache.maven.plugin.descriptor.MojoDescriptor;
84 import org.apache.maven.plugin.descriptor.Parameter;
85 import org.apache.maven.plugin.descriptor.PluginDescriptor;
86 import org.apache.maven.project.MavenProject;
87 import org.codehaus.plexus.classworlds.realm.ClassRealm;
88 import org.eclipse.aether.repository.RemoteRepository;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
91
92 import static org.apache.maven.api.Lifecycle.AFTER;
93 import static org.apache.maven.api.Lifecycle.AT;
94 import static org.apache.maven.api.Lifecycle.BEFORE;
95 import static org.apache.maven.api.Lifecycle.Phase.PACKAGE;
96 import static org.apache.maven.api.Lifecycle.Phase.READY;
97 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.CREATED;
98 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.EXECUTED;
99 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.FAILED;
100 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.PLAN;
101 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.PLANNING;
102 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.SCHEDULED;
103 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.SETUP;
104 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.SKIPPED;
105 import static org.apache.maven.lifecycle.internal.concurrent.BuildStep.TEARDOWN;
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 @Named
150 public class BuildPlanExecutor {
151
152 private static final Object GLOBAL = new Object();
153
154 private final Logger logger = LoggerFactory.getLogger(getClass());
155
156 private final MojoExecutor mojoExecutor;
157 private final ExecutionEventCatapult eventCatapult;
158 private final ProjectExecutionListener projectExecutionListener;
159 private final TransformerManager transformerManager;
160 private final BuildPlanLogger buildPlanLogger;
161 private final Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators;
162 private final MavenPluginManager mavenPluginManager;
163 private final MojoDescriptorCreator mojoDescriptorCreator;
164 private final LifecycleRegistry lifecycles;
165
166 @Inject
167 @SuppressWarnings("checkstyle:ParameterNumber")
168 public BuildPlanExecutor(
169 @Named("concurrent") MojoExecutor mojoExecutor,
170 ExecutionEventCatapult eventCatapult,
171 List<ProjectExecutionListener> listeners,
172 TransformerManager transformerManager,
173 BuildPlanLogger buildPlanLogger,
174 Map<String, MojoExecutionConfigurator> mojoExecutionConfigurators,
175 MavenPluginManager mavenPluginManager,
176 MojoDescriptorCreator mojoDescriptorCreator,
177 LifecycleRegistry lifecycles) {
178 this.mojoExecutor = mojoExecutor;
179 this.eventCatapult = eventCatapult;
180 this.projectExecutionListener = new CompoundProjectExecutionListener(listeners);
181 this.transformerManager = transformerManager;
182 this.buildPlanLogger = buildPlanLogger;
183 this.mojoExecutionConfigurators = mojoExecutionConfigurators;
184 this.mavenPluginManager = mavenPluginManager;
185 this.mojoDescriptorCreator = mojoDescriptorCreator;
186 this.lifecycles = lifecycles;
187 }
188
189 public void execute(MavenSession session, ReactorContext reactorContext, List<TaskSegment> taskSegments)
190 throws ExecutionException, InterruptedException {
191 try (BuildContext ctx = new BuildContext(session, reactorContext, taskSegments)) {
192 ctx.execute();
193 }
194 }
195
196 class BuildContext implements AutoCloseable {
197 final MavenSession session;
198 final ReactorContext reactorContext;
199 final PhasingExecutor executor;
200 final Map<Object, Clock> clocks = new ConcurrentHashMap<>();
201 final ReadWriteLock lock = new ReentrantReadWriteLock();
202 final int threads;
203 BuildPlan plan;
204
205 BuildContext(MavenSession session, ReactorContext reactorContext, List<TaskSegment> taskSegments) {
206 this.session = session;
207 this.reactorContext = reactorContext;
208 this.threads = Math.min(
209 session.getRequest().getDegreeOfConcurrency(),
210 session.getProjects().size());
211
212 session.setParallel(threads > 1);
213 this.executor = new PhasingExecutor(Executors.newFixedThreadPool(threads, new BuildThreadFactory()));
214
215
216 this.plan = buildInitialPlan(taskSegments);
217 }
218
219 BuildContext() {
220 this.session = null;
221 this.reactorContext = null;
222 this.threads = 1;
223 this.executor = null;
224 this.plan = null;
225 }
226
227 public BuildPlan buildInitialPlan(List<TaskSegment> taskSegments) {
228 int nThreads = Math.min(
229 session.getRequest().getDegreeOfConcurrency(),
230 session.getProjects().size());
231 boolean parallel = nThreads > 1;
232
233 session.setParallel(parallel);
234
235 ProjectDependencyGraph dependencyGraph = session.getProjectDependencyGraph();
236 MavenProject rootProject = session.getTopLevelProject();
237
238 Map<MavenProject, List<MavenProject>> allProjects = new LinkedHashMap<>();
239 dependencyGraph
240 .getSortedProjects()
241 .forEach(p -> allProjects.put(p, dependencyGraph.getUpstreamProjects(p, false)));
242
243 BuildPlan plan = new BuildPlan(allProjects);
244 for (TaskSegment taskSegment : taskSegments) {
245 Map<MavenProject, List<MavenProject>> projects = taskSegment.isAggregating()
246 ? Collections.singletonMap(rootProject, allProjects.get(rootProject))
247 : allProjects;
248
249 BuildPlan segment = calculateMojoExecutions(projects, taskSegment.getTasks());
250 plan.then(segment);
251 }
252
253
254 for (MavenProject project : plan.getAllProjects().keySet()) {
255 BuildStep pplan = new BuildStep(PLAN, project, null);
256 pplan.status.set(PLANNING);
257 BuildStep setup = new BuildStep(SETUP, project, null);
258 BuildStep teardown = new BuildStep(TEARDOWN, project, null);
259 teardown.executeAfter(setup);
260 setup.executeAfter(pplan);
261 plan.steps(project).forEach(step -> {
262 if (step.predecessors.stream().noneMatch(s -> s.project == project)) {
263 step.executeAfter(setup);
264 } else if (step.successors.stream().noneMatch(s -> s.project == project)) {
265 teardown.executeAfter(step);
266 }
267 });
268 Stream.of(pplan, setup, teardown).forEach(step -> plan.addStep(project, step.name, step));
269 }
270
271 return plan;
272 }
273
274 private void checkUnboundVersions(BuildPlan buildPlan) {
275 String defaulModelId = DefaultLifecycleRegistry.DEFAULT_LIFECYCLE_MODELID;
276 List<String> unversionedPlugins = buildPlan
277 .allSteps()
278 .flatMap(step -> step.mojos.values().stream().flatMap(map -> map.values().stream()))
279 .map(MojoExecution::getPlugin)
280 .filter(p -> p.getLocation("version") != null
281 && p.getLocation("version").getSource() != null
282 && defaulModelId.equals(
283 p.getLocation("version").getSource().getModelId()))
284 .distinct()
285 .map(Plugin::getArtifactId)
286 .toList();
287 if (!unversionedPlugins.isEmpty()) {
288 logger.warn("Version not locked for default bindings plugins " + unversionedPlugins
289 + ", you should define versions in pluginManagement section of your " + "pom.xml or parent");
290 }
291 }
292
293 private void checkThreadSafety(BuildPlan buildPlan) {
294 if (threads > 1) {
295 Set<MojoExecution> unsafeExecutions = buildPlan
296 .allSteps()
297 .flatMap(step -> step.mojos.values().stream().flatMap(map -> map.values().stream()))
298 .filter(execution -> !execution.getMojoDescriptor().isV4Api())
299 .collect(Collectors.toSet());
300 if (!unsafeExecutions.isEmpty()) {
301 for (String s : MultilineMessageHelper.format(
302 """
303 Your build is requesting concurrent execution, but this project contains the \
304 following plugin(s) that have goals not built with Maven 4 to support concurrent \
305 execution. While this /may/ work fine, please look for plugin updates and/or \
306 request plugins be made thread-safe. If reporting an issue, report it against the \
307 plugin in question, not against Apache Maven.""")) {
308 logger.warn(s);
309 }
310 if (logger.isDebugEnabled()) {
311 Set<MojoDescriptor> unsafeGoals = unsafeExecutions.stream()
312 .map(MojoExecution::getMojoDescriptor)
313 .collect(Collectors.toSet());
314 logger.warn("The following goals are not Maven 4 goals:");
315 for (MojoDescriptor unsafeGoal : unsafeGoals) {
316 logger.warn(" " + unsafeGoal.getId());
317 }
318 } else {
319 Set<Plugin> unsafePlugins = unsafeExecutions.stream()
320 .map(MojoExecution::getPlugin)
321 .collect(Collectors.toSet());
322 logger.warn("The following plugins are not Maven 4 plugins:");
323 for (Plugin unsafePlugin : unsafePlugins) {
324 logger.warn(" " + unsafePlugin.getId());
325 }
326 logger.warn("");
327 logger.warn("Enable verbose output (-X) to see precisely which goals are not marked as"
328 + " thread-safe.");
329 }
330 logger.warn(MultilineMessageHelper.separatorLine());
331 }
332 }
333 }
334
335 void execute() {
336 try (var phase = executor.phase()) {
337 plan();
338 executePlan();
339 } catch (Exception e) {
340 session.getResult().addException(e);
341 }
342 }
343
344 @Override
345 public void close() {
346 this.executor.close();
347 }
348
349
350
351
352
353
354 private void processStep(BuildStep step) {
355
356 ReactorBuildStatus status = reactorContext.getReactorBuildStatus();
357 boolean isAfterStep = step.name.startsWith(AFTER);
358 boolean shouldExecute;
359
360
361 boolean allPredecessorsExecuted = step.predecessors.stream().allMatch(s -> s.status.get() == EXECUTED);
362
363
364 if (isAfterStep) {
365 String phaseName = step.name.substring(AFTER.length());
366
367 shouldExecute = plan.step(step.project, BEFORE + phaseName)
368 .map(s -> {
369 int stepStatus = s.status.get();
370 return stepStatus == EXECUTED;
371 })
372 .orElse(false);
373
374
375 boolean anyPredecessorFailed = step.predecessors.stream().anyMatch(s -> s.status.get() == FAILED);
376
377
378
379 if (shouldExecute && anyPredecessorFailed) {
380
381 if (step.status.compareAndSet(CREATED, SKIPPED)) {
382 logger.debug(
383 "Running after:* step {} for cleanup but marking it as SKIPPED because a predecessor failed",
384 step);
385 executor.execute(() -> {
386 try {
387 executeStep(step);
388 executePlan();
389 } catch (Exception e) {
390 step.status.compareAndSet(SKIPPED, FAILED);
391
392 step.exception = e;
393 logger.debug("Stored exception for step {} to be handled in TEARDOWN phase", step, e);
394
395 executePlan();
396 }
397 });
398 return;
399 }
400 }
401 } else if (TEARDOWN.equals(step.name)) {
402
403
404 shouldExecute = true;
405 } else {
406
407
408 shouldExecute = !status.isHalted() && !status.isBlackListed(step.project) && allPredecessorsExecuted;
409 }
410
411
412 if (shouldExecute && step.status.compareAndSet(CREATED, SCHEDULED)) {
413 boolean nextIsPlanning = step.successors.stream().anyMatch(st -> PLAN.equals(st.name));
414 executor.execute(() -> {
415 try {
416 executeStep(step);
417 if (nextIsPlanning) {
418 lock.writeLock().lock();
419 try {
420 plan();
421 } finally {
422 lock.writeLock().unlock();
423 }
424 }
425 executePlan();
426 } catch (Exception e) {
427 step.status.compareAndSet(SCHEDULED, FAILED);
428
429
430 step.exception = e;
431 logger.debug("Stored exception for step {} to be handled in TEARDOWN phase", step, e);
432
433
434 executePlan();
435 }
436 });
437 } else if (step.status.compareAndSet(CREATED, SKIPPED)) {
438
439 if (!shouldExecute) {
440 if (status.isHalted()) {
441 logger.debug("Skipping step {} because the build is halted", step);
442 } else if (status.isBlackListed(step.project)) {
443 logger.debug("Skipping step {} because the project is blacklisted", step);
444 } else if (TEARDOWN.equals(step.name)) {
445
446 logger.warn("Unexpected skipping of TEARDOWN step {}", step);
447 } else {
448 logger.debug("Skipping step {} because a dependency has failed", step);
449 }
450 } else {
451
452 logger.debug(
453 "Skipping step {} because one or more predecessors did not execute successfully", step);
454 }
455
456 executePlan();
457 }
458 }
459
460 private void executePlan() {
461
462
463 Clock global = getClock(GLOBAL);
464 global.start();
465 lock.readLock().lock();
466 try {
467
468
469
470
471 plan.sortedNodes().stream()
472
473 .filter(BuildStep::isCreated)
474
475 .filter(step -> step.predecessors.stream().allMatch(BuildStep::isDone))
476
477 .forEach(this::processStep);
478 } finally {
479 lock.readLock().unlock();
480 }
481 }
482
483
484
485
486
487
488
489
490
491
492
493
494 private void executeStep(BuildStep step) throws IOException, LifecycleExecutionException {
495 Clock clock = getClock(step.project);
496 switch (step.name) {
497 case PLAN:
498
499 throw new IllegalStateException();
500 case SETUP:
501 attachToThread(step);
502 transformerManager.injectTransformedArtifacts(session.getRepositorySession(), step.project);
503 projectExecutionListener.beforeProjectExecution(new ProjectExecutionEvent(session, step.project));
504 eventCatapult.fire(ExecutionEvent.Type.ProjectStarted, session, null);
505 break;
506 case TEARDOWN:
507 attachToThread(step);
508
509
510 List<Throwable> failures = null;
511 boolean allStepsExecuted = true;
512 for (BuildStep projectStep : plan.steps(step.project).toList()) {
513 Exception exception = projectStep.exception;
514 if (exception != null) {
515 if (failures == null) {
516 failures = new ArrayList<>();
517 }
518 failures.add(exception);
519 }
520 allStepsExecuted &= step == projectStep || projectStep.status.get() == EXECUTED;
521 }
522
523 if (failures != null) {
524
525 Throwable failure;
526 if (failures.size() == 1) {
527 failure = failures.get(
528 0);
529 } else {
530 failure = new LifecycleExecutionException("Error building project");
531 failures.forEach(failure::addSuppressed);
532 }
533 handleBuildError(reactorContext, session, step.project, failure);
534 } else if (allStepsExecuted) {
535
536 projectExecutionListener.afterProjectExecutionSuccess(
537 new ProjectExecutionEvent(session, step.project, Collections.emptyList()));
538 reactorContext
539 .getResult()
540 .addBuildSummary(new BuildSuccess(step.project, clock.wallTime(), clock.execTime()));
541 eventCatapult.fire(ExecutionEvent.Type.ProjectSucceeded, session, null);
542 } else {
543 eventCatapult.fire(ExecutionEvent.Type.ProjectSkipped, session, null);
544 }
545 break;
546 default:
547 List<MojoExecution> executions = step.executions().toList();
548 if (!executions.isEmpty()) {
549 attachToThread(step);
550 clock.start();
551 try {
552 executions.forEach(mojoExecution -> {
553 mojoExecutionConfigurator(mojoExecution).configure(step.project, mojoExecution, true);
554 finalizeMojoConfiguration(mojoExecution);
555 });
556 mojoExecutor.execute(session, executions);
557 } finally {
558 clock.stop();
559 }
560 }
561 break;
562 }
563 step.status.compareAndSet(SCHEDULED, EXECUTED);
564 }
565
566 private void attachToThread(BuildStep step) {
567 BuildPlanExecutor.attachToThread(step.project);
568 session.setCurrentProject(step.project);
569 }
570
571 private Clock getClock(Object key) {
572 return clocks.computeIfAbsent(key, p -> new Clock());
573 }
574
575 private void plan() {
576 lock.writeLock().lock();
577 try {
578 Set<BuildStep> planSteps = plan.allSteps()
579 .filter(step -> PLAN.equals(step.name))
580 .filter(step -> step.predecessors.stream().allMatch(s -> s.status.get() == EXECUTED))
581 .filter(step -> step.status.compareAndSet(PLANNING, SCHEDULED))
582 .collect(Collectors.toSet());
583 for (BuildStep step : planSteps) {
584 MavenProject project = step.project;
585 for (Plugin plugin : project.getBuild().getPlugins()) {
586 for (PluginExecution execution : plugin.getExecutions()) {
587 for (String goal : execution.getGoals()) {
588 MojoDescriptor mojoDescriptor = getMojoDescriptor(project, plugin, goal);
589 String phase =
590 execution.getPhase() != null ? execution.getPhase() : mojoDescriptor.getPhase();
591 if (phase == null) {
592 continue;
593 }
594 String tmpResolvedPhase = plan.aliases().getOrDefault(phase, phase);
595 String resolvedPhase = tmpResolvedPhase.startsWith(AT)
596 ? tmpResolvedPhase.substring(AT.length())
597 : tmpResolvedPhase;
598 plan.step(project, resolvedPhase).ifPresent(n -> {
599 MojoExecution mojoExecution = new MojoExecution(mojoDescriptor, execution.getId());
600 mojoExecution.setLifecyclePhase(phase);
601 n.addMojo(mojoExecution, execution.getPriority());
602 if (mojoDescriptor.getDependencyCollectionRequired() != null
603 || mojoDescriptor.getDependencyResolutionRequired() != null) {
604 for (MavenProject p :
605 plan.getAllProjects().get(project)) {
606 plan.step(p, AFTER + PACKAGE)
607 .ifPresent(a -> plan.requiredStep(project, resolvedPhase)
608 .executeAfter(a));
609 }
610 }
611 });
612 }
613 }
614 }
615 }
616
617 BuildPlan buildPlan = plan;
618 for (BuildStep step :
619 planSteps.stream().flatMap(p -> plan.steps(p.project)).toList()) {
620 for (MojoExecution execution : step.executions().toList()) {
621 buildPlan = computeForkPlan(step, execution, buildPlan);
622 }
623 }
624
625 for (BuildStep step : planSteps) {
626 MavenProject project = step.project;
627 buildPlanLogger.writePlan(plan, project);
628 step.status.compareAndSet(SCHEDULED, EXECUTED);
629 }
630
631 checkThreadSafety(plan);
632 checkUnboundVersions(plan);
633 } finally {
634 lock.writeLock().unlock();
635 }
636 }
637
638 protected BuildPlan computeForkPlan(BuildStep step, MojoExecution execution, BuildPlan buildPlan) {
639 MojoDescriptor mojoDescriptor = execution.getMojoDescriptor();
640 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
641 String forkedGoal = mojoDescriptor.getExecuteGoal();
642 String phase = mojoDescriptor.getExecutePhase();
643
644 if (forkedGoal != null && !forkedGoal.isEmpty()) {
645 MojoDescriptor forkedMojoDescriptor = pluginDescriptor.getMojo(forkedGoal);
646 if (forkedMojoDescriptor == null) {
647 throw new MavenException(new MojoNotFoundException(forkedGoal, pluginDescriptor));
648 }
649
650 List<MavenProject> toFork = new ArrayList<>();
651 toFork.add(step.project);
652 if (mojoDescriptor.isAggregator() && step.project.getCollectedProjects() != null) {
653 toFork.addAll(step.project.getCollectedProjects());
654 }
655
656 BuildPlan plan = new BuildPlan();
657 for (MavenProject project : toFork) {
658 BuildStep st = new BuildStep(forkedGoal, project, null);
659 MojoExecution mojoExecution = new MojoExecution(forkedMojoDescriptor, forkedGoal);
660 st.addMojo(mojoExecution, 0);
661 Map<String, BuildStep> n = new HashMap<>();
662 n.put(forkedGoal, st);
663 plan.addProject(project, n);
664 }
665
666 for (BuildStep astep : plan.allSteps().toList()) {
667 for (MojoExecution aexecution : astep.executions().toList()) {
668 plan = computeForkPlan(astep, aexecution, plan);
669 }
670 }
671
672 return plan;
673
674 } else if (phase != null && !phase.isEmpty()) {
675 String forkedLifecycle = mojoDescriptor.getExecuteLifecycle();
676 Lifecycle lifecycle;
677 if (forkedLifecycle != null && !forkedLifecycle.isEmpty()) {
678 org.apache.maven.api.plugin.descriptor.lifecycle.Lifecycle lifecycleOverlay;
679 try {
680 lifecycleOverlay = pluginDescriptor.getLifecycleMapping(forkedLifecycle);
681 } catch (IOException | XMLStreamException e) {
682 throw new MavenException(new PluginDescriptorParsingException(
683 pluginDescriptor.getPlugin(), pluginDescriptor.getSource(), e));
684 }
685 if (lifecycleOverlay == null) {
686 Optional<Lifecycle> lf = lifecycles.lookup(forkedLifecycle);
687 if (lf.isPresent()) {
688 lifecycle = lf.get();
689 } else {
690 throw new MavenException(new LifecycleNotFoundException(forkedLifecycle));
691 }
692 } else {
693 lifecycle = new PluginLifecycle(lifecycleOverlay, pluginDescriptor);
694 }
695 } else {
696 if (execution.getLifecyclePhase() != null) {
697 String n = execution.getLifecyclePhase();
698 String phaseName = n.startsWith(BEFORE)
699 ? n.substring(BEFORE.length())
700 : n.startsWith(AFTER) ? n.substring(AFTER.length()) : n;
701 lifecycle = lifecycles.stream()
702 .filter(l -> l.allPhases().anyMatch(p -> phaseName.equals(p.name())))
703 .findFirst()
704 .orElse(null);
705 if (lifecycle == null) {
706 throw new IllegalStateException();
707 }
708 } else {
709 lifecycle = lifecycles.require(Lifecycle.DEFAULT);
710 }
711 }
712
713 String resolvedPhase = getResolvedPhase(lifecycle, phase);
714
715 Map<MavenProject, List<MavenProject>> map = Collections.singletonMap(
716 step.project, plan.getAllProjects().get(step.project));
717 BuildPlan forkedPlan = calculateLifecycleMappings(map, lifecycle, resolvedPhase);
718 forkedPlan.then(buildPlan);
719 return forkedPlan;
720 } else {
721 return buildPlan;
722 }
723 }
724
725 private String getResolvedPhase(Lifecycle lifecycle, String phase) {
726 return lifecycle.aliases().stream()
727 .filter(a -> phase.equals(a.v3Phase()))
728 .findFirst()
729 .map(Lifecycle.Alias::v4Phase)
730 .orElse(phase);
731 }
732
733 private String getResolvedPhase(String phase) {
734 return lifecycles.stream()
735 .flatMap(l -> l.aliases().stream())
736 .filter(a -> phase.equals(a.v3Phase()))
737 .findFirst()
738 .map(Lifecycle.Alias::v4Phase)
739 .orElse(phase);
740 }
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759 protected void handleBuildError(
760 final ReactorContext buildContext,
761 final MavenSession session,
762 final MavenProject mavenProject,
763 Throwable t) {
764
765 Clock clock = getClock(mavenProject);
766 buildContext.getResult().addException(t);
767 buildContext
768 .getResult()
769 .addBuildSummary(new BuildFailure(mavenProject, clock.execTime(), clock.wallTime(), t));
770
771
772 if (t instanceof Exception exception && !(t instanceof RuntimeException)) {
773 eventCatapult.fire(ExecutionEvent.Type.ProjectFailed, session, null, exception);
774 }
775
776
777 if (t instanceof RuntimeException || !(t instanceof Exception)) {
778
779
780 buildContext.getReactorBuildStatus().halt();
781 } else if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(session.getReactorFailureBehavior())) {
782
783 } else if (MavenExecutionRequest.REACTOR_FAIL_AT_END.equals(session.getReactorFailureBehavior())) {
784
785 buildContext.getReactorBuildStatus().blackList(mavenProject);
786 } else if (MavenExecutionRequest.REACTOR_FAIL_FAST.equals(session.getReactorFailureBehavior())) {
787 buildContext.getReactorBuildStatus().halt();
788 } else {
789 logger.error("invalid reactor failure behavior " + session.getReactorFailureBehavior());
790 buildContext.getReactorBuildStatus().halt();
791 }
792 }
793
794 public BuildPlan calculateMojoExecutions(Map<MavenProject, List<MavenProject>> projects, List<Task> tasks) {
795 BuildPlan buildPlan = new BuildPlan(projects);
796
797 for (Task task : tasks) {
798 BuildPlan step;
799
800 if (task instanceof GoalTask) {
801 String pluginGoal = task.getValue();
802
803 String executionId = "default-cli";
804 int executionIdx = pluginGoal.indexOf('@');
805 if (executionIdx > 0) {
806 executionId = pluginGoal.substring(executionIdx + 1);
807 }
808
809 step = new BuildPlan();
810 for (MavenProject project : projects.keySet()) {
811 BuildStep st = new BuildStep(pluginGoal, project, null);
812 MojoDescriptor mojoDescriptor = getMojoDescriptor(project, pluginGoal);
813 MojoExecution mojoExecution =
814 new MojoExecution(mojoDescriptor, executionId, MojoExecution.Source.CLI);
815 st.addMojo(mojoExecution, 0);
816 Map<String, BuildStep> n = new HashMap<>();
817 n.put(pluginGoal, st);
818 step.addProject(project, n);
819 }
820 } else if (task instanceof LifecycleTask) {
821 String lifecyclePhase = task.getValue();
822
823 step = calculateLifecycleMappings(projects, lifecyclePhase);
824
825 } else {
826 throw new IllegalStateException("unexpected task " + task);
827 }
828
829 buildPlan.then(step);
830 }
831
832 return buildPlan;
833 }
834
835 private MojoDescriptor getMojoDescriptor(MavenProject project, Plugin plugin, String goal) {
836 try {
837 return mavenPluginManager.getMojoDescriptor(
838 plugin, goal, project.getRemotePluginRepositories(), session.getRepositorySession());
839 } catch (MavenException e) {
840 throw e;
841 } catch (Exception e) {
842 throw new MavenException(e);
843 }
844 }
845
846 private MojoDescriptor getMojoDescriptor(MavenProject project, String task) {
847 try {
848 return mojoDescriptorCreator.getMojoDescriptor(task, session, project);
849 } catch (MavenException e) {
850 throw e;
851 } catch (Exception e) {
852 throw new MavenException(e);
853 }
854 }
855
856 public BuildPlan calculateLifecycleMappings(
857 Map<MavenProject, List<MavenProject>> projects, String lifecyclePhase) {
858
859 String resolvedPhase = getResolvedPhase(lifecyclePhase);
860 String mainPhase = resolvedPhase.startsWith(BEFORE)
861 ? resolvedPhase.substring(BEFORE.length())
862 : resolvedPhase.startsWith(AFTER)
863 ? resolvedPhase.substring(AFTER.length())
864 : resolvedPhase.startsWith(AT) ? resolvedPhase.substring(AT.length()) : resolvedPhase;
865
866
867
868
869 Lifecycle lifecycle = lifecycles.stream()
870 .filter(l -> l.allPhases().anyMatch(p -> mainPhase.equals(p.name())))
871 .findFirst()
872 .orElse(null);
873
874 if (lifecycle == null) {
875 throw new MavenException(new LifecyclePhaseNotFoundException(
876 "Unknown lifecycle phase \"" + lifecyclePhase
877 + "\". You must specify a valid lifecycle phase"
878 + " or a goal in the format <plugin-prefix>:<goal> or"
879 + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: "
880 + lifecycles.stream()
881 .flatMap(l -> l.allPhases().map(Lifecycle.Phase::name))
882 .collect(Collectors.joining(", "))
883 + ".",
884 lifecyclePhase));
885 }
886
887 return calculateLifecycleMappings(projects, lifecycle, resolvedPhase);
888 }
889
890 public BuildPlan calculateLifecycleMappings(
891 Map<MavenProject, List<MavenProject>> projects, Lifecycle lifecycle, String lifecyclePhase) {
892 BuildPlan plan = new BuildPlan(projects);
893
894 for (MavenProject project : projects.keySet()) {
895
896 Map<String, BuildStep> steps = lifecycle
897 .allPhases()
898 .flatMap(phase -> {
899 BuildStep a = new BuildStep(BEFORE + phase.name(), project, phase);
900 BuildStep b = new BuildStep(phase.name(), project, phase);
901 BuildStep c = new BuildStep(AFTER + phase.name(), project, phase);
902 b.executeAfter(a);
903 c.executeAfter(b);
904 return Stream.of(a, b, c);
905 })
906 .collect(Collectors.toMap(n -> n.name, n -> n));
907
908 lifecycle.allPhases().forEach(phase -> phase.phases().forEach(child -> {
909 steps.get(BEFORE + child.name()).executeAfter(steps.get(BEFORE + phase.name()));
910 steps.get(AFTER + phase.name()).executeAfter(steps.get(AFTER + child.name()));
911 }));
912
913 lifecycle.allPhases().forEach(phase -> {
914 phase.links().stream()
915 .filter(l -> l.pointer().type() == Lifecycle.Pointer.Type.PROJECT)
916 .forEach(link -> {
917 String n1 = phase.name();
918 String n2 = link.pointer().phase();
919 if (link.kind() == Lifecycle.Link.Kind.AFTER) {
920 steps.get(BEFORE + n1).executeAfter(steps.get(AFTER + n2));
921 } else {
922 steps.get(BEFORE + n2).executeAfter(steps.get(AFTER + n1));
923 }
924 });
925 });
926
927
928 String endPhase = lifecyclePhase.startsWith(BEFORE) || lifecyclePhase.startsWith(AFTER)
929 ? lifecyclePhase
930 : lifecyclePhase.startsWith(AT)
931 ? lifecyclePhase.substring(AT.length())
932 : AFTER + lifecyclePhase;
933 Set<BuildStep> toKeep = steps.get(endPhase).allPredecessors().collect(Collectors.toSet());
934 toKeep.addAll(toKeep.stream()
935 .filter(s -> s.name.startsWith(BEFORE))
936 .map(s -> steps.get(AFTER + s.name.substring(BEFORE.length())))
937 .toList());
938 steps.values().stream().filter(n -> !toKeep.contains(n)).forEach(BuildStep::skip);
939
940 plan.addProject(project, steps);
941 }
942
943
944 plan.allSteps().filter(step -> step.phase != null).forEach(step -> {
945 Lifecycle.Phase phase = step.phase;
946 MavenProject project = step.project;
947 phase.links().stream().forEach(link -> {
948 BuildStep before = plan.requiredStep(project, BEFORE + phase.name());
949 BuildStep after = plan.requiredStep(project, AFTER + phase.name());
950 Lifecycle.Pointer pointer = link.pointer();
951 String n2 = pointer.phase();
952 if (pointer instanceof Lifecycle.DependenciesPointer) {
953
954
955
956 projects.get(project)
957 .forEach(p -> plan.step(p, AFTER + n2).ifPresent(before::executeAfter));
958 } else if (pointer instanceof Lifecycle.ChildrenPointer) {
959
960 project.getCollectedProjects().forEach(p -> {
961
962 plan.step(p, BEFORE + n2).ifPresent(before::executeBefore);
963
964 plan.step(p, AFTER + n2).ifPresent(after::executeAfter);
965 });
966 }
967 });
968 });
969
970
971 Map<String, MavenProject> reactorGavs =
972 projects.keySet().stream().collect(Collectors.toMap(BuildPlanExecutor::gav, p -> p));
973
974
975 List<Runnable> toResolve = new ArrayList<>();
976 projects.keySet().forEach(project -> project.getBuild().getPlugins().forEach(plugin -> {
977 MavenProject pluginProject = reactorGavs.get(gav(plugin));
978 if (pluginProject != null) {
979
980 plan.requiredStep(project, PLAN).executeAfter(plan.requiredStep(pluginProject, READY));
981 } else {
982 toResolve.add(() -> resolvePlugin(session, project.getRemotePluginRepositories(), plugin));
983 }
984 }));
985
986
987 toResolve.parallelStream().forEach(Runnable::run);
988
989
990 lifecycle.aliases().forEach(alias -> plan.aliases().put(alias.v3Phase(), alias.v4Phase()));
991
992 return plan;
993 }
994 }
995
996 private void resolvePlugin(MavenSession session, List<RemoteRepository> repositories, Plugin plugin) {
997 try {
998 mavenPluginManager.getPluginDescriptor(plugin, repositories, session.getRepositorySession());
999 } catch (Exception e) {
1000 throw new MavenException(e);
1001 }
1002 }
1003
1004 private static String gav(MavenProject p) {
1005 return p.getGroupId() + ":" + p.getArtifactId() + ":" + p.getVersion();
1006 }
1007
1008 private static String gav(Plugin p) {
1009 return p.getGroupId() + ":" + p.getArtifactId() + ":" + p.getVersion();
1010 }
1011
1012
1013
1014
1015
1016
1017
1018
1019 private void finalizeMojoConfiguration(MojoExecution mojoExecution) {
1020 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
1021
1022 XmlNode executionConfiguration = mojoExecution.getConfiguration() != null
1023 ? mojoExecution.getConfiguration().getDom()
1024 : null;
1025 if (executionConfiguration == null) {
1026 executionConfiguration = XmlNode.newInstance("configuration");
1027 }
1028
1029 XmlNode defaultConfiguration = getMojoConfiguration(mojoDescriptor);
1030
1031 List<XmlNode> children = new ArrayList<>();
1032 if (mojoDescriptor.getParameters() != null) {
1033 for (Parameter parameter : mojoDescriptor.getParameters()) {
1034 XmlNode parameterConfiguration = executionConfiguration.child(parameter.getName());
1035
1036 if (parameterConfiguration == null) {
1037 parameterConfiguration = executionConfiguration.child(parameter.getAlias());
1038 }
1039
1040 XmlNode parameterDefaults = defaultConfiguration.child(parameter.getName());
1041
1042 if (parameterConfiguration != null) {
1043 parameterConfiguration = XmlService.merge(parameterConfiguration, parameterDefaults, Boolean.TRUE);
1044 } else {
1045 parameterConfiguration = parameterDefaults;
1046 }
1047
1048 if (parameterConfiguration != null) {
1049 Map<String, String> attributes = new HashMap<>(parameterConfiguration.attributes());
1050
1051 String attributeForImplementation = parameterConfiguration.attribute("implementation");
1052 String parameterForImplementation = parameter.getImplementation();
1053 if ((attributeForImplementation == null || attributeForImplementation.isEmpty())
1054 && ((parameterForImplementation != null) && !parameterForImplementation.isEmpty())) {
1055 attributes.put("implementation", parameter.getImplementation());
1056 }
1057
1058 parameterConfiguration = XmlNode.newInstance(
1059 parameter.getName(),
1060 parameterConfiguration.value(),
1061 attributes,
1062 parameterConfiguration.children(),
1063 parameterConfiguration.inputLocation());
1064
1065 children.add(parameterConfiguration);
1066 }
1067 }
1068 }
1069 XmlNode finalConfiguration = XmlNode.newInstance("configuration", children);
1070
1071 mojoExecution.setConfiguration(finalConfiguration);
1072 }
1073
1074 private XmlNode getMojoConfiguration(MojoDescriptor mojoDescriptor) {
1075 if (mojoDescriptor.isV4Api()) {
1076 return MojoDescriptorCreator.convert(mojoDescriptor.getMojoDescriptorV4());
1077 } else {
1078 return MojoDescriptorCreator.convert(mojoDescriptor).getDom();
1079 }
1080 }
1081
1082 private MojoExecutionConfigurator mojoExecutionConfigurator(MojoExecution mojoExecution) {
1083 String configuratorId = mojoExecution.getMojoDescriptor().getComponentConfigurator();
1084 if (configuratorId == null) {
1085 configuratorId = "default";
1086 }
1087
1088 MojoExecutionConfigurator mojoExecutionConfigurator = mojoExecutionConfigurators.get(configuratorId);
1089
1090 if (mojoExecutionConfigurator == null) {
1091
1092
1093
1094
1095 mojoExecutionConfigurator = mojoExecutionConfigurators.get("default");
1096 }
1097 return mojoExecutionConfigurator;
1098 }
1099
1100 public static void attachToThread(MavenProject currentProject) {
1101 ClassRealm projectRealm = currentProject.getClassRealm();
1102 if (projectRealm != null) {
1103 Thread.currentThread().setContextClassLoader(projectRealm);
1104 }
1105 }
1106
1107 protected static class Clock {
1108 Instant start;
1109 Instant end;
1110 Instant resumed;
1111 Duration exec = Duration.ZERO;
1112
1113 protected void start() {
1114 if (start == null) {
1115 start = MonotonicClock.now();
1116 resumed = start;
1117 } else {
1118 resumed = MonotonicClock.now();
1119 }
1120 }
1121
1122 protected void stop() {
1123 end = MonotonicClock.now();
1124 exec = exec.plus(Duration.between(resumed, end));
1125 }
1126
1127 protected Duration wallTime() {
1128 return start != null && end != null ? Duration.between(start, end) : Duration.ZERO;
1129 }
1130
1131 protected Duration execTime() {
1132 return exec;
1133 }
1134 }
1135 }