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