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