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