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