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