1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.LinkedHashMap;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.function.Function;
40 import java.util.stream.Stream;
41
42 import org.apache.maven.api.Session;
43 import org.apache.maven.api.model.Model;
44 import org.apache.maven.api.model.Prerequisites;
45 import org.apache.maven.api.model.Profile;
46 import org.apache.maven.artifact.ArtifactUtils;
47 import org.apache.maven.execution.BuildResumptionAnalyzer;
48 import org.apache.maven.execution.BuildResumptionDataRepository;
49 import org.apache.maven.execution.BuildResumptionPersistenceException;
50 import org.apache.maven.execution.DefaultMavenExecutionResult;
51 import org.apache.maven.execution.ExecutionEvent;
52 import org.apache.maven.execution.MavenExecutionRequest;
53 import org.apache.maven.execution.MavenExecutionResult;
54 import org.apache.maven.execution.MavenSession;
55 import org.apache.maven.execution.ProfileActivation;
56 import org.apache.maven.execution.ProjectActivation;
57 import org.apache.maven.execution.ProjectDependencyGraph;
58 import org.apache.maven.graph.GraphBuilder;
59 import org.apache.maven.graph.ProjectSelector;
60 import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
61 import org.apache.maven.internal.aether.MavenChainedWorkspaceReader;
62 import org.apache.maven.internal.impl.DefaultSessionFactory;
63 import org.apache.maven.internal.impl.InternalSession;
64 import org.apache.maven.lifecycle.LifecycleExecutionException;
65 import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
66 import org.apache.maven.lifecycle.internal.LifecycleStarter;
67 import org.apache.maven.model.building.ModelProblem;
68 import org.apache.maven.model.building.Result;
69 import org.apache.maven.model.superpom.SuperPomProvider;
70 import org.apache.maven.plugin.LegacySupport;
71 import org.apache.maven.project.MavenProject;
72 import org.apache.maven.project.ProjectBuilder;
73 import org.apache.maven.session.scope.internal.SessionScope;
74 import org.codehaus.plexus.PlexusContainer;
75 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
76 import org.eclipse.aether.RepositorySystemSession;
77 import org.eclipse.aether.RepositorySystemSession.CloseableSession;
78 import org.eclipse.aether.repository.WorkspaceReader;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81 import org.slf4j.helpers.MessageFormatter;
82
83 import static java.util.stream.Collectors.toSet;
84
85
86
87 @Named
88 @Singleton
89 public class DefaultMaven implements Maven {
90 private final Logger logger = LoggerFactory.getLogger(getClass());
91
92 protected ProjectBuilder projectBuilder;
93
94 private LifecycleStarter lifecycleStarter;
95
96 protected PlexusContainer container;
97
98 private ExecutionEventCatapult eventCatapult;
99
100 private LegacySupport legacySupport;
101
102 private SessionScope sessionScope;
103
104 private DefaultRepositorySystemSessionFactory repositorySessionFactory;
105
106 private final GraphBuilder graphBuilder;
107
108 private final BuildResumptionAnalyzer buildResumptionAnalyzer;
109
110 private final BuildResumptionDataRepository buildResumptionDataRepository;
111
112 private final SuperPomProvider superPomProvider;
113
114 private final DefaultSessionFactory defaultSessionFactory;
115
116 private final ProjectSelector projectSelector;
117
118 @Inject
119 @SuppressWarnings("checkstyle:ParameterNumber")
120 public DefaultMaven(
121 ProjectBuilder projectBuilder,
122 LifecycleStarter lifecycleStarter,
123 PlexusContainer container,
124 ExecutionEventCatapult eventCatapult,
125 LegacySupport legacySupport,
126 SessionScope sessionScope,
127 DefaultRepositorySystemSessionFactory repositorySessionFactory,
128 @Named(GraphBuilder.HINT) GraphBuilder graphBuilder,
129 BuildResumptionAnalyzer buildResumptionAnalyzer,
130 BuildResumptionDataRepository buildResumptionDataRepository,
131 SuperPomProvider superPomProvider,
132 DefaultSessionFactory defaultSessionFactory) {
133 this.projectBuilder = projectBuilder;
134 this.lifecycleStarter = lifecycleStarter;
135 this.container = container;
136 this.eventCatapult = eventCatapult;
137 this.legacySupport = legacySupport;
138 this.sessionScope = sessionScope;
139 this.repositorySessionFactory = repositorySessionFactory;
140 this.graphBuilder = graphBuilder;
141 this.buildResumptionAnalyzer = buildResumptionAnalyzer;
142 this.buildResumptionDataRepository = buildResumptionDataRepository;
143 this.superPomProvider = superPomProvider;
144 this.defaultSessionFactory = defaultSessionFactory;
145 this.projectSelector = new ProjectSelector();
146 }
147
148 @Override
149 public MavenExecutionResult execute(MavenExecutionRequest request) {
150 MavenExecutionResult result;
151
152 try {
153 result = doExecute(request);
154 } catch (OutOfMemoryError e) {
155 result = addExceptionToResult(new DefaultMavenExecutionResult(), e);
156 } catch (RuntimeException e) {
157
158 if (e.getCause() instanceof ProjectCycleException) {
159 result = addExceptionToResult(new DefaultMavenExecutionResult(), e.getCause());
160 } else {
161 result = addExceptionToResult(
162 new DefaultMavenExecutionResult(), new InternalErrorException("Internal error: " + e, e));
163 }
164 } finally {
165 legacySupport.setSession(null);
166 }
167
168 return result;
169 }
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 @SuppressWarnings("checkstyle:methodlength")
200 private MavenExecutionResult doExecute(MavenExecutionRequest request) {
201 request.setStartTime(new Date());
202
203 MavenExecutionResult result = new DefaultMavenExecutionResult();
204
205 try {
206 validateLocalRepository(request);
207 } catch (IOException e) {
208 return addExceptionToResult(result, e);
209 }
210
211
212
213
214
215
216 sessionScope.enter();
217 MavenChainedWorkspaceReader chainedWorkspaceReader = new MavenChainedWorkspaceReader();
218 try (CloseableSession closeableSession = newCloseableSession(request, chainedWorkspaceReader)) {
219 MavenSession session = new MavenSession(closeableSession, request, result);
220 session.setSession(defaultSessionFactory.getSession(session));
221
222 sessionScope.seed(MavenSession.class, session);
223 sessionScope.seed(Session.class, session.getSession());
224 sessionScope.seed(InternalSession.class, InternalSession.from(session.getSession()));
225
226 legacySupport.setSession(session);
227
228 return doExecute(request, session, result, chainedWorkspaceReader);
229 } finally {
230 sessionScope.exit();
231 }
232 }
233
234 private MavenExecutionResult doExecute(
235 MavenExecutionRequest request,
236 MavenSession session,
237 MavenExecutionResult result,
238 MavenChainedWorkspaceReader chainedWorkspaceReader) {
239 try {
240 afterSessionStart(session);
241 } catch (MavenExecutionException e) {
242 return addExceptionToResult(result, e);
243 }
244
245 try {
246 WorkspaceReader reactorReader = container.lookup(WorkspaceReader.class, ReactorReader.HINT);
247 chainedWorkspaceReader.setReaders(Collections.singletonList(reactorReader));
248 } catch (ComponentLookupException e) {
249 return addExceptionToResult(result, e);
250 }
251
252 eventCatapult.fire(ExecutionEvent.Type.ProjectDiscoveryStarted, session, null);
253
254 Result<? extends ProjectDependencyGraph> graphResult = buildGraph(session);
255
256 if (graphResult.hasErrors()) {
257 return addExceptionToResult(
258 result, graphResult.getProblems().iterator().next().getException());
259 }
260
261 try {
262 session.setProjectMap(getProjectMap(session.getProjects()));
263 } catch (DuplicateProjectException e) {
264 return addExceptionToResult(result, e);
265 }
266
267 try {
268 setupWorkspaceReader(session, chainedWorkspaceReader);
269 } catch (ComponentLookupException e) {
270 return addExceptionToResult(result, e);
271 }
272 try {
273 afterProjectsRead(session);
274 } catch (MavenExecutionException e) {
275 return addExceptionToResult(result, e);
276 }
277
278
279
280
281
282
283
284
285
286
287 graphResult = buildGraph(session);
288
289 if (graphResult.hasErrors()) {
290 return addExceptionToResult(
291 result, graphResult.getProblems().iterator().next().getException());
292 }
293
294 try {
295 if (result.hasExceptions()) {
296 return result;
297 }
298
299 result.setTopologicallySortedProjects(session.getProjects());
300
301 result.setProject(session.getTopLevelProject());
302
303 validatePrerequisitesForNonMavenPluginProjects(session.getProjects());
304
305 validateRequiredProfiles(session, request.getProfileActivation());
306 if (session.getResult().hasExceptions()) {
307 return result;
308 }
309
310 validateOptionalProfiles(session, request.getProfileActivation());
311
312 lifecycleStarter.execute(session);
313
314 validateOptionalProjects(request, session);
315 validateOptionalProfiles(session, request.getProfileActivation());
316
317 if (session.getResult().hasExceptions()) {
318 addExceptionToResult(result, session.getResult().getExceptions().get(0));
319 persistResumptionData(result, session);
320 return result;
321 } else {
322 session.getAllProjects().stream()
323 .filter(MavenProject::isExecutionRoot)
324 .findFirst()
325 .ifPresent(buildResumptionDataRepository::removeResumptionData);
326 }
327 } finally {
328 try {
329 afterSessionEnd(session);
330 } catch (MavenExecutionException e) {
331 return addExceptionToResult(result, e);
332 }
333 }
334
335 return result;
336 }
337
338 private void setupWorkspaceReader(MavenSession session, MavenChainedWorkspaceReader chainedWorkspaceReader)
339 throws ComponentLookupException {
340
341 Set<WorkspaceReader> workspaceReaders = new LinkedHashSet<>();
342
343 WorkspaceReader reactorReader = container.lookup(WorkspaceReader.class, ReactorReader.HINT);
344 workspaceReaders.add(reactorReader);
345
346 for (WorkspaceReader repoWorkspaceReader : chainedWorkspaceReader.getReaders()) {
347 if (repoWorkspaceReader != null && repoWorkspaceReader != reactorReader) {
348 workspaceReaders.add(repoWorkspaceReader);
349 }
350 }
351
352 workspaceReaders.addAll(getProjectScopedExtensionComponents(session.getProjects(), WorkspaceReader.class));
353 chainedWorkspaceReader.setReaders(workspaceReaders);
354 }
355
356 private void afterSessionStart(MavenSession session) throws MavenExecutionException {
357 callListeners(session, AbstractMavenLifecycleParticipant::afterSessionStart);
358 }
359
360 private void afterProjectsRead(MavenSession session) throws MavenExecutionException {
361 callListeners(session, AbstractMavenLifecycleParticipant::afterProjectsRead);
362 }
363
364 private void afterSessionEnd(MavenSession session) throws MavenExecutionException {
365 callListeners(session, AbstractMavenLifecycleParticipant::afterSessionEnd);
366 }
367
368 @FunctionalInterface
369 interface ListenerMethod {
370 void run(AbstractMavenLifecycleParticipant listener, MavenSession session) throws MavenExecutionException;
371 }
372
373 private void callListeners(MavenSession session, ListenerMethod method) throws MavenExecutionException {
374 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
375 try {
376 for (AbstractMavenLifecycleParticipant listener :
377 getExtensionComponents(session.getProjects(), AbstractMavenLifecycleParticipant.class)) {
378 Thread.currentThread().setContextClassLoader(listener.getClass().getClassLoader());
379 method.run(listener, session);
380 }
381 } finally {
382 Thread.currentThread().setContextClassLoader(originalClassLoader);
383 }
384 }
385
386 private void persistResumptionData(MavenExecutionResult result, MavenSession session) {
387 boolean hasLifecycleExecutionExceptions =
388 result.getExceptions().stream().anyMatch(LifecycleExecutionException.class::isInstance);
389
390 if (hasLifecycleExecutionExceptions) {
391 MavenProject rootProject = session.getAllProjects().stream()
392 .filter(MavenProject::isExecutionRoot)
393 .findFirst()
394 .orElseThrow(() -> new IllegalStateException("No project in the session is execution root"));
395
396 buildResumptionAnalyzer.determineBuildResumptionData(result).ifPresent(resumption -> {
397 try {
398 buildResumptionDataRepository.persistResumptionData(rootProject, resumption);
399 result.setCanResume(true);
400 } catch (BuildResumptionPersistenceException e) {
401 logger.warn("Could not persist build resumption data", e);
402 }
403 });
404 }
405 }
406
407
408
409
410
411
412 @Deprecated
413 public RepositorySystemSession newRepositorySession(MavenExecutionRequest request) {
414 return newCloseableSession(request, new MavenChainedWorkspaceReader());
415 }
416
417 private CloseableSession newCloseableSession(MavenExecutionRequest request, WorkspaceReader workspaceReader) {
418 return repositorySessionFactory
419 .newRepositorySessionBuilder(request)
420 .setWorkspaceReader(workspaceReader)
421 .build();
422 }
423
424 private void validateLocalRepository(MavenExecutionRequest request) throws IOException {
425 File localRepoDir = request.getLocalRepositoryPath();
426
427 logger.debug("Using local repository at {}", localRepoDir);
428
429 localRepoDir.mkdirs();
430
431 if (!localRepoDir.isDirectory()) {
432 throw new IOException("Could not create local repository at " + localRepoDir);
433 }
434 }
435
436 private <T> Collection<T> getExtensionComponents(Collection<MavenProject> projects, Class<T> role) {
437 Collection<T> foundComponents = new LinkedHashSet<>();
438
439 try {
440 foundComponents.addAll(container.lookupList(role));
441 } catch (ComponentLookupException e) {
442
443 logger.warn("Failed to lookup {}: {}", role, e.getMessage());
444 }
445
446 foundComponents.addAll(getProjectScopedExtensionComponents(projects, role));
447
448 return foundComponents;
449 }
450
451 protected <T> Collection<T> getProjectScopedExtensionComponents(Collection<MavenProject> projects, Class<T> role) {
452 if (projects == null) {
453 return Collections.emptyList();
454 }
455
456 Collection<T> foundComponents = new LinkedHashSet<>();
457 Collection<ClassLoader> scannedRealms = new HashSet<>();
458
459 Thread currentThread = Thread.currentThread();
460 ClassLoader originalContextClassLoader = currentThread.getContextClassLoader();
461 try {
462 for (MavenProject project : projects) {
463 ClassLoader projectRealm = project.getClassRealm();
464
465 if (projectRealm != null && scannedRealms.add(projectRealm)) {
466 currentThread.setContextClassLoader(projectRealm);
467
468 try {
469 foundComponents.addAll(container.lookupList(role));
470 } catch (ComponentLookupException e) {
471
472 logger.warn("Failed to lookup {}: {}", role, e.getMessage());
473 }
474 }
475 }
476 return foundComponents;
477 } finally {
478 currentThread.setContextClassLoader(originalContextClassLoader);
479 }
480 }
481
482 private MavenExecutionResult addExceptionToResult(MavenExecutionResult result, Throwable e) {
483 if (!result.getExceptions().contains(e)) {
484 result.addException(e);
485 }
486
487 return result;
488 }
489
490 private void validatePrerequisitesForNonMavenPluginProjects(List<MavenProject> projects) {
491 for (MavenProject mavenProject : projects) {
492 if (!"maven-plugin".equals(mavenProject.getPackaging())) {
493 Prerequisites prerequisites =
494 mavenProject.getModel().getDelegate().getPrerequisites();
495 if (prerequisites != null && prerequisites.getMaven() != null) {
496 logger.warn(
497 "The project {} uses prerequisites"
498 + " which is only intended for maven-plugin projects "
499 + "but not for non maven-plugin projects. "
500 + "For such purposes you should use the maven-enforcer-plugin. "
501 + "See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html",
502 mavenProject.getId());
503 }
504 }
505 }
506 }
507
508
509
510
511
512
513 private Set<String> getAllProfiles(MavenSession session) {
514 final Map<String, Model> superPomModels = new HashMap<>();
515 final Set<MavenProject> projectsIncludingParents = new HashSet<>();
516 for (MavenProject project : session.getProjects()) {
517 superPomModels.computeIfAbsent(
518 project.getModelVersion(),
519 v -> superPomProvider.getSuperModel(v).getDelegate());
520 boolean isAdded = projectsIncludingParents.add(project);
521 MavenProject parent = project.getParent();
522 while (isAdded && parent != null) {
523 isAdded = projectsIncludingParents.add(parent);
524 parent = parent.getParent();
525 }
526 }
527
528 final Stream<String> projectProfiles = projectsIncludingParents.stream()
529 .flatMap(p -> p.getModel().getDelegate().getProfiles().stream())
530 .map(Profile::getId);
531 final Stream<String> settingsProfiles =
532 session.getSettings().getProfiles().stream().map(org.apache.maven.settings.Profile::getId);
533 final Stream<String> superPomProfiles = superPomModels.values().stream()
534 .flatMap(p -> p.getProfiles().stream())
535 .map(Profile::getId);
536
537 return Stream.of(projectProfiles, settingsProfiles, superPomProfiles)
538 .flatMap(Function.identity())
539 .collect(toSet());
540 }
541
542
543
544
545
546
547 private void validateRequiredProfiles(MavenSession session, ProfileActivation profileActivation) {
548 final Set<String> allAvailableProfiles = getAllProfiles(session);
549
550 final Set<String> requiredProfiles = new HashSet<>();
551 requiredProfiles.addAll(profileActivation.getRequiredActiveProfileIds());
552 requiredProfiles.addAll(profileActivation.getRequiredInactiveProfileIds());
553
554
555 final Set<String> notFoundRequiredProfiles = requiredProfiles.stream()
556 .filter(rap -> !allAvailableProfiles.contains(rap))
557 .collect(toSet());
558
559 if (!notFoundRequiredProfiles.isEmpty()) {
560
561 final String message = MessageFormatter.format(
562 "The requested profiles {} could not be activated or deactivated because they do not"
563 + " exist.",
564 notFoundRequiredProfiles)
565 .getMessage();
566 addExceptionToResult(session.getResult(), new MissingProfilesException(message));
567 }
568 }
569
570
571
572
573
574
575 private void validateOptionalProjects(MavenExecutionRequest request, MavenSession session) {
576 final ProjectActivation projectActivation = request.getProjectActivation();
577 final Set<String> allOptionalSelectors = new HashSet<>();
578 allOptionalSelectors.addAll(projectActivation.getOptionalActiveProjectSelectors());
579 allOptionalSelectors.addAll(projectActivation.getRequiredActiveProjectSelectors());
580
581
582 projectSelector.getOptionalProjectsBySelectors(request, session.getAllProjects(), allOptionalSelectors);
583 }
584
585
586
587
588
589
590 private void validateOptionalProfiles(MavenSession session, ProfileActivation profileActivation) {
591 final Set<String> allAvailableProfiles = getAllProfiles(session);
592
593 final Set<String> optionalProfiles = new HashSet<>();
594 optionalProfiles.addAll(profileActivation.getOptionalActiveProfileIds());
595 optionalProfiles.addAll(profileActivation.getOptionalInactiveProfileIds());
596
597 final Set<String> notFoundOptionalProfiles = optionalProfiles.stream()
598 .filter(rap -> !allAvailableProfiles.contains(rap))
599 .collect(toSet());
600
601 if (!notFoundOptionalProfiles.isEmpty()) {
602 logger.info(
603 "The requested optional profiles {} could not be activated or deactivated because they do not"
604 + " exist.",
605 notFoundOptionalProfiles);
606 }
607 }
608
609 private Map<String, MavenProject> getProjectMap(Collection<MavenProject> projects)
610 throws DuplicateProjectException {
611 Map<String, MavenProject> index = new LinkedHashMap<>();
612 Map<String, List<File>> collisions = new LinkedHashMap<>();
613
614 for (MavenProject project : projects) {
615 String projectId = ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
616
617 MavenProject collision = index.get(projectId);
618
619 if (collision == null) {
620 index.put(projectId, project);
621 } else {
622 List<File> pomFiles = collisions.get(projectId);
623
624 if (pomFiles == null) {
625 pomFiles = new ArrayList<>(Arrays.asList(collision.getFile(), project.getFile()));
626 collisions.put(projectId, pomFiles);
627 } else {
628 pomFiles.add(project.getFile());
629 }
630 }
631 }
632
633 if (!collisions.isEmpty()) {
634 throw new DuplicateProjectException(
635 "Two or more projects in the reactor"
636 + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>"
637 + " is unique for each project: " + collisions,
638 collisions);
639 }
640
641 return index;
642 }
643
644 private Result<? extends ProjectDependencyGraph> buildGraph(MavenSession session) {
645 Result<? extends ProjectDependencyGraph> graphResult = graphBuilder.build(session);
646 for (ModelProblem problem : graphResult.getProblems()) {
647 if (problem.getSeverity() == ModelProblem.Severity.WARNING) {
648 logger.warn(problem.getMessage());
649 } else {
650 logger.error(problem.getMessage());
651 }
652 }
653
654 if (!graphResult.hasErrors()) {
655 ProjectDependencyGraph projectDependencyGraph = graphResult.get();
656 session.setProjects(projectDependencyGraph.getSortedProjects());
657 session.setAllProjects(projectDependencyGraph.getAllProjects());
658 session.setProjectDependencyGraph(projectDependencyGraph);
659 }
660
661 return graphResult;
662 }
663
664 @Deprecated
665
666 protected Logger getLogger() {
667 return logger;
668 }
669 }