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