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         sessionScope.enter();
213         MavenChainedWorkspaceReader chainedWorkspaceReader =
214                 new MavenChainedWorkspaceReader(request.getWorkspaceReader(), ideWorkspaceReader);
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(Session.class, session.getSession());
221             sessionScope.seed(InternalMavenSession.class, InternalMavenSession.from(session.getSession()));
222 
223             legacySupport.setSession(session);
224 
225             return doExecute(request, session, result, chainedWorkspaceReader);
226         } finally {
227             sessionScope.exit();
228         }
229     }
230 
231     private MavenExecutionResult doExecute(
232             MavenExecutionRequest request,
233             MavenSession session,
234             MavenExecutionResult result,
235             MavenChainedWorkspaceReader chainedWorkspaceReader) {
236         try {
237             afterSessionStart(session);
238         } catch (MavenExecutionException e) {
239             return addExceptionToResult(result, e);
240         }
241 
242         try {
243             chainedWorkspaceReader.addReader(lookup.lookup(WorkspaceReader.class, ReactorReader.HINT));
244         } catch (LookupException e) {
245             return addExceptionToResult(result, e);
246         }
247 
248         eventCatapult.fire(ExecutionEvent.Type.ProjectDiscoveryStarted, session, null);
249 
250         Result<? extends ProjectDependencyGraph> graphResult = buildGraph(session);
251 
252         if (graphResult.hasErrors()) {
253             return addExceptionToResult(
254                     result, graphResult.getProblems().iterator().next().getException());
255         }
256 
257         try {
258             session.setProjectMap(getProjectMap(session.getProjects()));
259         } catch (DuplicateProjectException e) {
260             return addExceptionToResult(result, e);
261         }
262 
263         try {
264             setupWorkspaceReader(session, chainedWorkspaceReader);
265         } catch (LookupException e) {
266             return addExceptionToResult(result, e);
267         }
268         try {
269             afterProjectsRead(session);
270         } catch (MavenExecutionException e) {
271             return addExceptionToResult(result, e);
272         }
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         return newCloseableSession(request, new MavenChainedWorkspaceReader());
413     }
414 
415     private CloseableSession newCloseableSession(MavenExecutionRequest request, WorkspaceReader workspaceReader) {
416         return repositorySessionFactory
417                 .newRepositorySessionBuilder(request)
418                 .setWorkspaceReader(workspaceReader)
419                 .build();
420     }
421 
422     private void validateLocalRepository(MavenExecutionRequest request) throws IOException {
423         File localRepoDir = request.getLocalRepositoryPath();
424 
425         logger.debug("Using local repository at {}", localRepoDir);
426 
427         localRepoDir.mkdirs();
428 
429         if (!localRepoDir.isDirectory()) {
430             throw new IOException("Could not create local repository at " + localRepoDir);
431         }
432     }
433 
434     private <T> Collection<T> getExtensionComponents(Collection<MavenProject> projects, Class<T> role) {
435         Collection<T> foundComponents = new LinkedHashSet<>();
436         foundComponents.addAll(lookup.lookupList(role));
437         foundComponents.addAll(getProjectScopedExtensionComponents(projects, role));
438         return foundComponents;
439     }
440 
441     protected <T> Collection<T> getProjectScopedExtensionComponents(Collection<MavenProject> projects, Class<T> role) {
442         if (projects == null) {
443             return Collections.emptyList();
444         }
445 
446         Collection<T> foundComponents = new LinkedHashSet<>();
447         Collection<ClassLoader> scannedRealms = new HashSet<>();
448 
449         Thread currentThread = Thread.currentThread();
450         ClassLoader originalContextClassLoader = currentThread.getContextClassLoader();
451         try {
452             for (MavenProject project : projects) {
453                 ClassLoader projectRealm = project.getClassRealm();
454 
455                 if (projectRealm != null && scannedRealms.add(projectRealm)) {
456                     currentThread.setContextClassLoader(projectRealm);
457                     foundComponents.addAll(lookup.lookupList(role));
458                 }
459             }
460             return foundComponents;
461         } finally {
462             currentThread.setContextClassLoader(originalContextClassLoader);
463         }
464     }
465 
466     private MavenExecutionResult addExceptionToResult(MavenExecutionResult result, Throwable e) {
467         if (!result.getExceptions().contains(e)) {
468             result.addException(e);
469         }
470 
471         return result;
472     }
473 
474     private void validatePrerequisitesForNonMavenPluginProjects(List<MavenProject> projects) {
475         for (MavenProject mavenProject : projects) {
476             if (!"maven-plugin".equals(mavenProject.getPackaging())) {
477                 Prerequisites prerequisites =
478                         mavenProject.getModel().getDelegate().getPrerequisites();
479                 if (prerequisites != null && prerequisites.getMaven() != null) {
480                     logger.warn(
481                             "The project {} uses prerequisites"
482                                     + " which is only intended for maven-plugin projects "
483                                     + "but not for non maven-plugin projects. "
484                                     + "For such purposes you should use the maven-enforcer-plugin. "
485                                     + "See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html",
486                             mavenProject.getId());
487                 }
488             }
489         }
490     }
491 
492     
493 
494 
495 
496 
497     private Set<String> getAllProfiles(MavenSession session) {
498         final Map<String, Model> superPomModels = new HashMap<>();
499         final Set<MavenProject> projectsIncludingParents = new HashSet<>();
500         for (MavenProject project : session.getProjects()) {
501             superPomModels.computeIfAbsent(
502                     project.getModelVersion(),
503                     v -> superPomProvider.getSuperModel(v).getDelegate());
504             boolean isAdded = projectsIncludingParents.add(project);
505             MavenProject parent = project.getParent();
506             while (isAdded && parent != null) {
507                 isAdded = projectsIncludingParents.add(parent);
508                 parent = parent.getParent();
509             }
510         }
511 
512         final Stream<String> projectProfiles = projectsIncludingParents.stream()
513                 .flatMap(p -> p.getModel().getDelegate().getProfiles().stream())
514                 .map(Profile::getId);
515         final Stream<String> settingsProfiles =
516                 session.getSettings().getProfiles().stream().map(org.apache.maven.settings.Profile::getId);
517         final Stream<String> superPomProfiles = superPomModels.values().stream()
518                 .flatMap(p -> p.getProfiles().stream())
519                 .map(Profile::getId);
520 
521         return Stream.of(projectProfiles, settingsProfiles, superPomProfiles)
522                 .flatMap(Function.identity())
523                 .collect(toSet());
524     }
525 
526     
527 
528 
529 
530 
531     private void validateRequiredProfiles(MavenSession session, ProfileActivation profileActivation) {
532         final Set<String> allAvailableProfiles = getAllProfiles(session);
533 
534         final Set<String> requiredProfiles = new HashSet<>();
535         requiredProfiles.addAll(profileActivation.getRequiredActiveProfileIds());
536         requiredProfiles.addAll(profileActivation.getRequiredInactiveProfileIds());
537 
538         
539         final Set<String> notFoundRequiredProfiles = requiredProfiles.stream()
540                 .filter(rap -> !allAvailableProfiles.contains(rap))
541                 .collect(toSet());
542 
543         if (!notFoundRequiredProfiles.isEmpty()) {
544             
545             final String message = MessageFormatter.format(
546                             "The requested profiles {} could not be activated or deactivated because they do not"
547                                     + " exist.",
548                             notFoundRequiredProfiles)
549                     .getMessage();
550             addExceptionToResult(session.getResult(), new MissingProfilesException(message));
551         }
552     }
553 
554     
555 
556 
557 
558 
559     private void validateOptionalProjects(MavenExecutionRequest request, MavenSession session) {
560         final ProjectActivation projectActivation = request.getProjectActivation();
561         final Set<String> allOptionalSelectors = new HashSet<>();
562         allOptionalSelectors.addAll(projectActivation.getOptionalActiveProjectSelectors());
563         allOptionalSelectors.addAll(projectActivation.getRequiredActiveProjectSelectors());
564         
565         
566         projectSelector.getOptionalProjectsBySelectors(request, session.getAllProjects(), allOptionalSelectors);
567     }
568 
569     
570 
571 
572 
573 
574     private void validateOptionalProfiles(MavenSession session, ProfileActivation profileActivation) {
575         final Set<String> allAvailableProfiles = getAllProfiles(session);
576 
577         final Set<String> optionalProfiles = new HashSet<>();
578         optionalProfiles.addAll(profileActivation.getOptionalActiveProfileIds());
579         optionalProfiles.addAll(profileActivation.getOptionalInactiveProfileIds());
580 
581         final Set<String> notFoundOptionalProfiles = optionalProfiles.stream()
582                 .filter(rap -> !allAvailableProfiles.contains(rap))
583                 .collect(toSet());
584 
585         if (!notFoundOptionalProfiles.isEmpty()) {
586             logger.info(
587                     "The requested optional profiles {} could not be activated or deactivated because they do not"
588                             + " exist.",
589                     notFoundOptionalProfiles);
590         }
591     }
592 
593     private Map<String, MavenProject> getProjectMap(Collection<MavenProject> projects)
594             throws DuplicateProjectException {
595         Map<String, MavenProject> index = new LinkedHashMap<>();
596         Map<String, List<File>> collisions = new LinkedHashMap<>();
597 
598         for (MavenProject project : projects) {
599             String projectId = ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
600 
601             MavenProject collision = index.get(projectId);
602 
603             if (collision == null) {
604                 index.put(projectId, project);
605             } else {
606                 List<File> pomFiles = collisions.get(projectId);
607 
608                 if (pomFiles == null) {
609                     pomFiles = new ArrayList<>(Arrays.asList(collision.getFile(), project.getFile()));
610                     collisions.put(projectId, pomFiles);
611                 } else {
612                     pomFiles.add(project.getFile());
613                 }
614             }
615         }
616 
617         if (!collisions.isEmpty()) {
618             throw new DuplicateProjectException(
619                     "Two or more projects in the reactor"
620                             + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>"
621                             + " is unique for each project: " + collisions,
622                     collisions);
623         }
624 
625         return index;
626     }
627 
628     private Result<? extends ProjectDependencyGraph> buildGraph(MavenSession session) {
629         Result<? extends ProjectDependencyGraph> graphResult = graphBuilder.build(session);
630         for (ModelProblem problem : graphResult.getProblems()) {
631             if (problem.getSeverity() == ModelProblem.Severity.WARNING) {
632                 logger.warn(problem.getMessage());
633             } else {
634                 logger.error(problem.getMessage());
635             }
636         }
637 
638         if (!graphResult.hasErrors()) {
639             ProjectDependencyGraph projectDependencyGraph = graphResult.get();
640             session.setProjects(projectDependencyGraph.getSortedProjects());
641             session.setAllProjects(projectDependencyGraph.getAllProjects());
642             session.setProjectDependencyGraph(projectDependencyGraph);
643         }
644 
645         return graphResult;
646     }
647 
648     @Deprecated
649     
650     protected Logger getLogger() {
651         return logger;
652     }
653 }