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