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