View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.Date;
31  import java.util.HashSet;
32  import java.util.LinkedHashMap;
33  import java.util.LinkedHashSet;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  
38  import org.apache.maven.artifact.ArtifactUtils;
39  import org.apache.maven.execution.DefaultMavenExecutionResult;
40  import org.apache.maven.execution.ExecutionEvent;
41  import org.apache.maven.execution.MavenExecutionRequest;
42  import org.apache.maven.execution.MavenExecutionResult;
43  import org.apache.maven.execution.MavenSession;
44  import org.apache.maven.execution.ProjectDependencyGraph;
45  import org.apache.maven.graph.GraphBuilder;
46  import org.apache.maven.internal.RepositorySystemSessionFactory;
47  import org.apache.maven.internal.aether.MavenChainedWorkspaceReader;
48  import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
49  import org.apache.maven.lifecycle.internal.LifecycleStarter;
50  import org.apache.maven.model.Prerequisites;
51  import org.apache.maven.model.building.ModelProblem;
52  import org.apache.maven.model.building.Result;
53  import org.apache.maven.plugin.LegacySupport;
54  import org.apache.maven.project.MavenProject;
55  import org.apache.maven.project.ProjectBuilder;
56  import org.apache.maven.repository.LocalRepositoryNotAccessibleException;
57  import org.apache.maven.session.scope.internal.SessionScope;
58  import org.codehaus.plexus.PlexusContainer;
59  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
60  import org.eclipse.aether.DefaultRepositorySystemSession;
61  import org.eclipse.aether.RepositorySystemSession;
62  import org.eclipse.aether.repository.WorkspaceReader;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  /**
67   * @author Jason van Zyl
68   */
69  @Singleton
70  @Named
71  public class DefaultMaven implements Maven {
72  
73      private final Logger logger = LoggerFactory.getLogger(getClass());
74  
75      @Inject
76      protected ProjectBuilder projectBuilder;
77  
78      @Inject
79      private LifecycleStarter lifecycleStarter;
80  
81      @Inject
82      protected PlexusContainer container;
83  
84      @Inject
85      private ExecutionEventCatapult eventCatapult;
86  
87      @Inject
88      private LegacySupport legacySupport;
89  
90      @Inject
91      private SessionScope sessionScope;
92  
93      @Inject
94      private RepositorySystemSessionFactory repositorySessionFactory;
95  
96      @Inject
97      @Named(GraphBuilder.HINT)
98      private GraphBuilder graphBuilder;
99  
100     @Override
101     public MavenExecutionResult execute(MavenExecutionRequest request) {
102         MavenExecutionResult result;
103 
104         try {
105             result = doExecute(request);
106         } catch (OutOfMemoryError e) {
107             result = addExceptionToResult(new DefaultMavenExecutionResult(), e);
108         } catch (RuntimeException e) {
109             // TODO Hack to make the cycle detection the same for the new graph builder
110             if (e.getCause() instanceof ProjectCycleException) {
111                 result = addExceptionToResult(new DefaultMavenExecutionResult(), e.getCause());
112             } else {
113                 result = addExceptionToResult(
114                         new DefaultMavenExecutionResult(), new InternalErrorException("Internal error: " + e, e));
115             }
116         } finally {
117             legacySupport.setSession(null);
118         }
119 
120         return result;
121     }
122 
123     //
124     // 1) Setup initial properties.
125     //
126     // 2) Validate local repository directory is accessible.
127     //
128     // 3) Create RepositorySystemSession.
129     //
130     // 4) Create MavenSession.
131     //
132     // 5) Execute AbstractLifecycleParticipant.afterSessionStart(session)
133     //
134     // 6) Get reactor projects looking for general POM errors
135     //
136     // 7) Create ProjectDependencyGraph using trimming which takes into account --projects and reactor mode.
137     // This ensures that the projects passed into the ReactorReader are only those specified.
138     //
139     // 8) Create ReactorReader with the getProjectMap( projects ). NOTE that getProjectMap(projects) is the code that
140     // checks for duplicate projects definitions in the build. Ideally this type of duplicate checking should be
141     // part of getting the reactor projects in 6). The duplicate checking is conflated with getProjectMap(projects).
142     //
143     // 9) Execute AbstractLifecycleParticipant.afterProjectsRead(session)
144     //
145     // 10) Create ProjectDependencyGraph without trimming (as trimming was done in 7). A new topological sort is
146     // required after the execution of 9) as the AbstractLifecycleParticipants are free to mutate the MavenProject
147     // instances, which may change dependencies which can, in turn, affect the build order.
148     //
149     // 11) Execute LifecycleStarter.start()
150     //
151     @SuppressWarnings("checkstyle:methodlength")
152     private MavenExecutionResult doExecute(MavenExecutionRequest request) {
153         request.setStartTime(new Date());
154 
155         MavenExecutionResult result = new DefaultMavenExecutionResult();
156 
157         try {
158             validateLocalRepository(request);
159         } catch (LocalRepositoryNotAccessibleException e) {
160             return addExceptionToResult(result, e);
161         }
162 
163         //
164         // We enter the session scope right after the MavenSession creation and before any of the
165         // AbstractLifecycleParticipant lookups
166         // so that @SessionScoped components can be @Injected into AbstractLifecycleParticipants.
167         //
168         sessionScope.enter();
169         try (RepositorySystemSession.CloseableSession repoSession =
170                 newRepositorySessionBuilder(request).build()) {
171             DefaultRepositorySystemSession mutableSession = new DefaultRepositorySystemSession(repoSession);
172             MavenSession session = new MavenSession(container, mutableSession, request, result);
173 
174             sessionScope.seed(RepositorySystemSession.class, mutableSession);
175             sessionScope.seed(MavenSession.class, session);
176 
177             legacySupport.setSession(session);
178 
179             return doExecute(request, session, result, mutableSession);
180         } finally {
181             sessionScope.exit();
182         }
183     }
184 
185     private MavenExecutionResult doExecute(
186             MavenExecutionRequest request,
187             MavenSession session,
188             MavenExecutionResult result,
189             DefaultRepositorySystemSession repoSession) {
190         try {
191             // CHECKSTYLE_OFF: LineLength
192             for (AbstractMavenLifecycleParticipant listener :
193                     getLifecycleParticipants(Collections.<MavenProject>emptyList())) {
194                 listener.afterSessionStart(session);
195             }
196             // CHECKSTYLE_ON: LineLength
197         } catch (MavenExecutionException e) {
198             return addExceptionToResult(result, e);
199         }
200 
201         eventCatapult.fire(ExecutionEvent.Type.ProjectDiscoveryStarted, session, null);
202 
203         Result<? extends ProjectDependencyGraph> graphResult = buildGraph(session, result);
204 
205         if (graphResult.hasErrors()) {
206             return addExceptionToResult(
207                     result, graphResult.getProblems().iterator().next().getException());
208         }
209 
210         try {
211             session.setProjectMap(getProjectMap(session.getProjects()));
212         } catch (DuplicateProjectException e) {
213             return addExceptionToResult(result, e);
214         }
215 
216         try {
217             setupWorkspaceReader(session, repoSession);
218         } catch (ComponentLookupException e) {
219             return addExceptionToResult(result, e);
220         }
221 
222         repoSession.setReadOnly();
223 
224         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
225         try {
226             for (AbstractMavenLifecycleParticipant listener : getLifecycleParticipants(session.getProjects())) {
227                 Thread.currentThread().setContextClassLoader(listener.getClass().getClassLoader());
228 
229                 listener.afterProjectsRead(session);
230             }
231         } catch (MavenExecutionException e) {
232             return addExceptionToResult(result, e);
233         } finally {
234             Thread.currentThread().setContextClassLoader(originalClassLoader);
235         }
236 
237         //
238         // The projects need to be topologically after the participants have run their afterProjectsRead(session)
239         // because the participant is free to change the dependencies of a project which can potentially change the
240         // topological order of the projects, and therefore can potentially change the build order.
241         //
242         // Note that participants may affect the topological order of the projects but it is
243         // not expected that a participant will add or remove projects from the session.
244         //
245 
246         graphResult = buildGraph(session, result);
247 
248         if (graphResult.hasErrors()) {
249             return addExceptionToResult(
250                     result, graphResult.getProblems().iterator().next().getException());
251         }
252 
253         try {
254             if (result.hasExceptions()) {
255                 return result;
256             }
257 
258             result.setTopologicallySortedProjects(session.getProjects());
259 
260             result.setProject(session.getTopLevelProject());
261 
262             validatePrerequisitesForNonMavenPluginProjects(session.getProjects());
263 
264             validateActivatedProfiles(
265                     session.getProjects(), request.getActiveProfiles(), request.getInactiveProfiles());
266 
267             lifecycleStarter.execute(session);
268 
269             validateActivatedProfiles(
270                     session.getProjects(), request.getActiveProfiles(), request.getInactiveProfiles());
271 
272             if (session.getResult().hasExceptions()) {
273                 return addExceptionToResult(
274                         result, session.getResult().getExceptions().get(0));
275             }
276         } finally {
277             try {
278                 afterSessionEnd(session.getProjects(), session);
279             } catch (MavenExecutionException e) {
280                 return addExceptionToResult(result, e);
281             }
282         }
283 
284         return result;
285     }
286 
287     private void setupWorkspaceReader(MavenSession session, DefaultRepositorySystemSession repoSession)
288             throws ComponentLookupException {
289         // Desired order of precedence for workspace readers before querying the local artifact repositories
290         Set<WorkspaceReader> workspaceReaders = new LinkedHashSet<>();
291         // 1) Reactor workspace reader
292         workspaceReaders.add(container.lookup(WorkspaceReader.class, ReactorReader.HINT));
293         // 2) Repository system session-scoped workspace reader
294         WorkspaceReader repoWorkspaceReader = repoSession.getWorkspaceReader();
295         if (repoWorkspaceReader != null) {
296             workspaceReaders.add(repoWorkspaceReader);
297         }
298         // 3) .. n) Project-scoped workspace readers
299         workspaceReaders.addAll(getProjectScopedExtensionComponents(session.getProjects(), WorkspaceReader.class));
300         repoSession.setWorkspaceReader(MavenChainedWorkspaceReader.of(workspaceReaders));
301     }
302 
303     private void afterSessionEnd(Collection<MavenProject> projects, MavenSession session)
304             throws MavenExecutionException {
305         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
306         try {
307             for (AbstractMavenLifecycleParticipant listener : getLifecycleParticipants(projects)) {
308                 Thread.currentThread().setContextClassLoader(listener.getClass().getClassLoader());
309 
310                 listener.afterSessionEnd(session);
311             }
312         } finally {
313             Thread.currentThread().setContextClassLoader(originalClassLoader);
314         }
315     }
316 
317     @Deprecated
318     public RepositorySystemSession newRepositorySession(MavenExecutionRequest request) {
319         return newRepositorySessionBuilder(request).build();
320     }
321 
322     private RepositorySystemSession.SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) {
323         return repositorySessionFactory.newRepositorySessionBuilder(request);
324     }
325 
326     private void validateLocalRepository(MavenExecutionRequest request) throws LocalRepositoryNotAccessibleException {
327         File localRepoDir = request.getLocalRepositoryPath();
328 
329         logger.debug("Using local repository at " + localRepoDir);
330 
331         localRepoDir.mkdirs();
332 
333         if (!localRepoDir.isDirectory()) {
334             throw new LocalRepositoryNotAccessibleException("Could not create local repository at " + localRepoDir);
335         }
336     }
337 
338     private Collection<AbstractMavenLifecycleParticipant> getLifecycleParticipants(Collection<MavenProject> projects) {
339         Collection<AbstractMavenLifecycleParticipant> lifecycleListeners = new LinkedHashSet<>();
340 
341         try {
342             lifecycleListeners.addAll(container.lookupList(AbstractMavenLifecycleParticipant.class));
343         } catch (ComponentLookupException e) {
344             // this is just silly, lookupList should return an empty list!
345             logger.warn("Failed to lookup lifecycle participants: " + e.getMessage());
346         }
347 
348         lifecycleListeners.addAll(
349                 getProjectScopedExtensionComponents(projects, AbstractMavenLifecycleParticipant.class));
350 
351         return lifecycleListeners;
352     }
353 
354     protected <T> Collection<T> getProjectScopedExtensionComponents(Collection<MavenProject> projects, Class<T> role) {
355 
356         Collection<T> foundComponents = new LinkedHashSet<>();
357         Collection<ClassLoader> scannedRealms = new HashSet<>();
358 
359         Thread currentThread = Thread.currentThread();
360         ClassLoader originalContextClassLoader = currentThread.getContextClassLoader();
361         try {
362             for (MavenProject project : projects) {
363                 ClassLoader projectRealm = project.getClassRealm();
364 
365                 if (projectRealm != null && scannedRealms.add(projectRealm)) {
366                     currentThread.setContextClassLoader(projectRealm);
367 
368                     try {
369                         foundComponents.addAll(container.lookupList(role));
370                     } catch (ComponentLookupException e) {
371                         // this is just silly, lookupList should return an empty list!
372                         logger.warn("Failed to lookup " + role + ": " + e.getMessage());
373                     }
374                 }
375             }
376             return foundComponents;
377         } finally {
378             currentThread.setContextClassLoader(originalContextClassLoader);
379         }
380     }
381 
382     private MavenExecutionResult addExceptionToResult(MavenExecutionResult result, Throwable e) {
383         if (!result.getExceptions().contains(e)) {
384             result.addException(e);
385         }
386 
387         return result;
388     }
389 
390     private void validatePrerequisitesForNonMavenPluginProjects(List<MavenProject> projects) {
391         for (MavenProject mavenProject : projects) {
392             if (!"maven-plugin".equals(mavenProject.getPackaging())) {
393                 Prerequisites prerequisites = mavenProject.getPrerequisites();
394                 if (prerequisites != null && prerequisites.getMaven() != null) {
395                     logger.warn("The project " + mavenProject.getId() + " uses prerequisites"
396                             + " which is only intended for maven-plugin projects "
397                             + "but not for non maven-plugin projects. "
398                             + "For such purposes you should use the maven-enforcer-plugin. "
399                             + "See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html");
400                 }
401             }
402         }
403     }
404 
405     private void validateActivatedProfiles(
406             List<MavenProject> projects, List<String> activeProfileIds, List<String> inactiveProfileIds) {
407         Collection<String> notActivatedProfileIds = new LinkedHashSet<>(activeProfileIds);
408 
409         for (MavenProject project : projects) {
410             for (List<String> profileIds : project.getInjectedProfileIds().values()) {
411                 notActivatedProfileIds.removeAll(profileIds);
412             }
413         }
414 
415         notActivatedProfileIds.removeAll(inactiveProfileIds);
416 
417         for (String notActivatedProfileId : notActivatedProfileIds) {
418             logger.warn("The requested profile \"" + notActivatedProfileId
419                     + "\" could not be activated because it does not exist.");
420         }
421     }
422 
423     private Map<String, MavenProject> getProjectMap(Collection<MavenProject> projects)
424             throws DuplicateProjectException {
425         Map<String, MavenProject> index = new LinkedHashMap<>();
426         Map<String, List<File>> collisions = new LinkedHashMap<>();
427 
428         for (MavenProject project : projects) {
429             String projectId = ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
430 
431             MavenProject collision = index.get(projectId);
432 
433             if (collision == null) {
434                 index.put(projectId, project);
435             } else {
436                 List<File> pomFiles = collisions.get(projectId);
437 
438                 if (pomFiles == null) {
439                     pomFiles = new ArrayList<>(Arrays.asList(collision.getFile(), project.getFile()));
440                     collisions.put(projectId, pomFiles);
441                 } else {
442                     pomFiles.add(project.getFile());
443                 }
444             }
445         }
446 
447         if (!collisions.isEmpty()) {
448             throw new DuplicateProjectException(
449                     "Two or more projects in the reactor"
450                             + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>"
451                             + " is unique for each project: " + collisions,
452                     collisions);
453         }
454 
455         return index;
456     }
457 
458     private Result<? extends ProjectDependencyGraph> buildGraph(MavenSession session, MavenExecutionResult result) {
459         Result<? extends ProjectDependencyGraph> graphResult = graphBuilder.build(session);
460         for (ModelProblem problem : graphResult.getProblems()) {
461             if (problem.getSeverity() == ModelProblem.Severity.WARNING) {
462                 logger.warn(problem.toString());
463             } else {
464                 logger.error(problem.toString());
465             }
466         }
467 
468         if (!graphResult.hasErrors()) {
469             ProjectDependencyGraph projectDependencyGraph = graphResult.get();
470             session.setProjects(projectDependencyGraph.getSortedProjects());
471             session.setAllProjects(projectDependencyGraph.getAllProjects());
472             session.setProjectDependencyGraph(projectDependencyGraph);
473         }
474 
475         return graphResult;
476     }
477 
478     @Deprecated
479     // 5 January 2014
480     protected Logger getLogger() {
481         return logger;
482     }
483 }