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