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  import java.util.Set;
34  
35  import org.apache.maven.artifact.ArtifactUtils;
36  import org.apache.maven.execution.DefaultMavenExecutionResult;
37  import org.apache.maven.execution.ExecutionEvent;
38  import org.apache.maven.execution.MavenExecutionRequest;
39  import org.apache.maven.execution.MavenExecutionResult;
40  import org.apache.maven.execution.MavenSession;
41  import org.apache.maven.execution.ProjectDependencyGraph;
42  import org.apache.maven.graph.GraphBuilder;
43  import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
44  import org.apache.maven.internal.aether.MavenChainedWorkspaceReader;
45  import org.apache.maven.lifecycle.internal.ExecutionEventCatapult;
46  import org.apache.maven.lifecycle.internal.LifecycleStarter;
47  import org.apache.maven.model.Prerequisites;
48  import org.apache.maven.model.building.ModelProblem;
49  import org.apache.maven.model.building.Result;
50  import org.apache.maven.plugin.LegacySupport;
51  import org.apache.maven.project.MavenProject;
52  import org.apache.maven.project.ProjectBuilder;
53  import org.apache.maven.repository.LocalRepositoryNotAccessibleException;
54  import org.apache.maven.session.scope.internal.SessionScope;
55  import org.codehaus.plexus.PlexusContainer;
56  import org.codehaus.plexus.component.annotations.Component;
57  import org.codehaus.plexus.component.annotations.Requirement;
58  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
59  import org.codehaus.plexus.logging.Logger;
60  import org.eclipse.aether.DefaultRepositorySystemSession;
61  import org.eclipse.aether.RepositorySystemSession;
62  import org.eclipse.aether.repository.WorkspaceReader;
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             // CHECKSTYLE_OFF: LineLength
207             for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( Collections.<MavenProject>emptyList() ) )
208             {
209                 listener.afterSessionStart( session );
210             }
211             // CHECKSTYLE_ON: LineLength
212         }
213         catch ( MavenExecutionException e )
214         {
215             return addExceptionToResult( result, e );
216         }
217 
218         eventCatapult.fire( ExecutionEvent.Type.ProjectDiscoveryStarted, session, null );
219 
220         Result<? extends ProjectDependencyGraph> graphResult = buildGraph( session, result );
221 
222         if ( graphResult.hasErrors() )
223         {
224             return addExceptionToResult( result, graphResult.getProblems().iterator().next().getException() );
225         }
226 
227         try
228         {
229             session.setProjectMap( getProjectMap( session.getProjects() ) );
230         }
231         catch ( DuplicateProjectException e )
232         {
233             return addExceptionToResult( result, e );
234         }
235 
236         try
237         {
238             setupWorkspaceReader( session, repoSession );
239         }
240         catch ( ComponentLookupException e )
241         {
242             return addExceptionToResult( result, e );
243         }
244 
245         repoSession.setReadOnly();
246 
247         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
248         try
249         {
250             for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( session.getProjects() ) )
251             {
252                 Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() );
253 
254                 listener.afterProjectsRead( session );
255             }
256         }
257         catch ( MavenExecutionException e )
258         {
259             return addExceptionToResult( result, e );
260         }
261         finally
262         {
263             Thread.currentThread().setContextClassLoader( originalClassLoader );
264         }
265 
266         //
267         // The projects need to be topologically after the participants have run their afterProjectsRead(session)
268         // because the participant is free to change the dependencies of a project which can potentially change the
269         // topological order of the projects, and therefore can potentially change the build order.
270         //
271         // Note that participants may affect the topological order of the projects but it is
272         // not expected that a participant will add or remove projects from the session.
273         //
274 
275         graphResult = buildGraph( session, result );
276 
277         if ( graphResult.hasErrors() )
278         {
279             return addExceptionToResult( result, graphResult.getProblems().iterator().next().getException() );
280         }
281 
282         try
283         {
284             if ( result.hasExceptions() )
285             {
286                 return result;
287             }
288 
289             result.setTopologicallySortedProjects( session.getProjects() );
290 
291             result.setProject( session.getTopLevelProject() );
292 
293             validatePrerequisitesForNonMavenPluginProjects( session.getProjects() );
294 
295             validateActivatedProfiles( session.getProjects(),
296                     request.getActiveProfiles(),
297                     request.getInactiveProfiles() );
298 
299             lifecycleStarter.execute( session );
300 
301             validateActivatedProfiles( session.getProjects(),
302                     request.getActiveProfiles(),
303                     request.getInactiveProfiles() );
304 
305             if ( session.getResult().hasExceptions() )
306             {
307                 return addExceptionToResult( result, session.getResult().getExceptions().get( 0 ) );
308             }
309         }
310         finally
311         {
312             try
313             {
314                 afterSessionEnd( session.getProjects(), session );
315             }
316             catch ( MavenExecutionException e )
317             {
318                 return addExceptionToResult( result, e );
319             }
320         }
321 
322         return result;
323     }
324 
325     private void setupWorkspaceReader( MavenSession session, DefaultRepositorySystemSession repoSession )
326         throws ComponentLookupException
327     {
328         // Desired order of precedence for workspace readers before querying the local artifact repositories
329         Set<WorkspaceReader> workspaceReaders = new LinkedHashSet<>();
330         // 1) Reactor workspace reader
331         workspaceReaders.add( container.lookup( WorkspaceReader.class, ReactorReader.HINT ) );
332         // 2) Repository system session-scoped workspace reader
333         WorkspaceReader repoWorkspaceReader = repoSession.getWorkspaceReader();
334         if ( repoWorkspaceReader != null )
335         {
336             workspaceReaders.add( repoWorkspaceReader );
337         }
338         // 3) .. n) Project-scoped workspace readers
339         workspaceReaders.addAll( getProjectScopedExtensionComponents( session.getProjects(), WorkspaceReader.class ) );
340         repoSession.setWorkspaceReader( MavenChainedWorkspaceReader.of( workspaceReaders ) );
341     }
342 
343     private void afterSessionEnd( Collection<MavenProject> projects, MavenSession session )
344         throws MavenExecutionException
345     {
346         ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
347         try
348         {
349             for ( AbstractMavenLifecycleParticipant listener : getLifecycleParticipants( projects ) )
350             {
351                 Thread.currentThread().setContextClassLoader( listener.getClass().getClassLoader() );
352 
353                 listener.afterSessionEnd( session );
354             }
355         }
356         finally
357         {
358             Thread.currentThread().setContextClassLoader( originalClassLoader );
359         }
360     }
361 
362     public RepositorySystemSession newRepositorySession( MavenExecutionRequest request )
363     {
364         return repositorySessionFactory.newRepositorySession( request );
365     }
366 
367     private void validateLocalRepository( MavenExecutionRequest request )
368         throws LocalRepositoryNotAccessibleException
369     {
370         File localRepoDir = request.getLocalRepositoryPath();
371 
372         logger.debug( "Using local repository at " + localRepoDir );
373 
374         localRepoDir.mkdirs();
375 
376         if ( !localRepoDir.isDirectory() )
377         {
378             throw new LocalRepositoryNotAccessibleException( "Could not create local repository at " + localRepoDir );
379         }
380     }
381 
382     private Collection<AbstractMavenLifecycleParticipant> getLifecycleParticipants( Collection<MavenProject> projects )
383     {
384         Collection<AbstractMavenLifecycleParticipant> lifecycleListeners = new LinkedHashSet<>();
385 
386         try
387         {
388             lifecycleListeners.addAll( container.lookupList( AbstractMavenLifecycleParticipant.class ) );
389         }
390         catch ( ComponentLookupException e )
391         {
392             // this is just silly, lookupList should return an empty list!
393             logger.warn( "Failed to lookup lifecycle participants: " + e.getMessage() );
394         }
395 
396         lifecycleListeners.addAll( getProjectScopedExtensionComponents( projects,
397                                                                         AbstractMavenLifecycleParticipant.class ) );
398 
399         return lifecycleListeners;
400     }
401 
402     protected <T> Collection<T> getProjectScopedExtensionComponents( Collection<MavenProject> projects, Class<T> role )
403     {
404 
405         Collection<T> foundComponents = new LinkedHashSet<>();
406         Collection<ClassLoader> scannedRealms = new HashSet<>();
407 
408         Thread currentThread = Thread.currentThread();
409         ClassLoader originalContextClassLoader = currentThread.getContextClassLoader();
410         try
411         {
412             for ( MavenProject project : projects )
413             {
414                 ClassLoader projectRealm = project.getClassRealm();
415 
416                 if ( projectRealm != null && scannedRealms.add( projectRealm ) )
417                 {
418                     currentThread.setContextClassLoader( projectRealm );
419 
420                     try
421                     {
422                         foundComponents.addAll( container.lookupList( role ) );
423                     }
424                     catch ( ComponentLookupException e )
425                     {
426                         // this is just silly, lookupList should return an empty list!
427                         logger.warn( "Failed to lookup " + role + ": " + e.getMessage() );
428                     }
429                 }
430             }
431             return foundComponents;
432         }
433         finally
434         {
435             currentThread.setContextClassLoader( originalContextClassLoader );
436         }
437     }
438 
439     private MavenExecutionResult addExceptionToResult( MavenExecutionResult result, Throwable e )
440     {
441         if ( !result.getExceptions().contains( e ) )
442         {
443             result.addException( e );
444         }
445 
446         return result;
447     }
448 
449     private void validatePrerequisitesForNonMavenPluginProjects( List<MavenProject> projects )
450     {
451         for ( MavenProject mavenProject : projects )
452         {
453             if ( !"maven-plugin".equals( mavenProject.getPackaging() ) )
454             {
455                 Prerequisites prerequisites = mavenProject.getPrerequisites();
456                 if ( prerequisites != null && prerequisites.getMaven() != null )
457                 {
458                     logger.warn( "The project " + mavenProject.getId() + " uses prerequisites"
459                         + " which is only intended for maven-plugin projects "
460                         + "but not for non maven-plugin projects. "
461                         + "For such purposes you should use the maven-enforcer-plugin. "
462                         + "See https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html" );
463                 }
464             }
465         }
466     }
467 
468     private void validateActivatedProfiles( List<MavenProject> projects,
469                                             List<String> activeProfileIds,
470                                             List<String> inactiveProfileIds )
471     {
472         Collection<String> notActivatedProfileIds = new LinkedHashSet<>( activeProfileIds );
473 
474         for ( MavenProject project : projects )
475         {
476             for ( List<String> profileIds : project.getInjectedProfileIds().values() )
477             {
478                 notActivatedProfileIds.removeAll( profileIds );
479             }
480         }
481 
482         notActivatedProfileIds.removeAll( inactiveProfileIds );
483 
484         for ( String notActivatedProfileId : notActivatedProfileIds )
485         {
486             logger.warn( "The requested profile \"" + notActivatedProfileId
487                 + "\" could not be activated because it does not exist." );
488         }
489     }
490 
491     private Map<String, MavenProject> getProjectMap( Collection<MavenProject> projects )
492         throws DuplicateProjectException
493     {
494         Map<String, MavenProject> index = new LinkedHashMap<>();
495         Map<String, List<File>> collisions = new LinkedHashMap<>();
496 
497         for ( MavenProject project : projects )
498         {
499             String projectId = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
500 
501             MavenProject collision = index.get( projectId );
502 
503             if ( collision == null )
504             {
505                 index.put( projectId, project );
506             }
507             else
508             {
509                 List<File> pomFiles = collisions.get( projectId );
510 
511                 if ( pomFiles == null )
512                 {
513                     pomFiles = new ArrayList<>( Arrays.asList( collision.getFile(), project.getFile() ) );
514                     collisions.put( projectId, pomFiles );
515                 }
516                 else
517                 {
518                     pomFiles.add( project.getFile() );
519                 }
520             }
521         }
522 
523         if ( !collisions.isEmpty() )
524         {
525             throw new DuplicateProjectException( "Two or more projects in the reactor"
526                 + " have the same identifier, please make sure that <groupId>:<artifactId>:<version>"
527                 + " is unique for each project: " + collisions, collisions );
528         }
529 
530         return index;
531     }
532 
533     private Result<? extends ProjectDependencyGraph> buildGraph( MavenSession session, MavenExecutionResult result )
534     {
535         Result<? extends ProjectDependencyGraph> graphResult = graphBuilder.build( session );
536         for ( ModelProblem problem : graphResult.getProblems() )
537         {
538             if ( problem.getSeverity() == ModelProblem.Severity.WARNING )
539             {
540                 logger.warn( problem.toString() );
541             }
542             else
543             {
544                 logger.error( problem.toString() );
545             }
546         }
547 
548         if ( !graphResult.hasErrors() )
549         {
550             ProjectDependencyGraph projectDependencyGraph = graphResult.get();
551             session.setProjects( projectDependencyGraph.getSortedProjects() );
552             session.setAllProjects( projectDependencyGraph.getAllProjects() );
553             session.setProjectDependencyGraph( projectDependencyGraph );
554         }
555 
556         return graphResult;
557     }
558 
559     @Deprecated
560     // 5 January 2014
561     protected Logger getLogger()
562     {
563         return logger;
564     }
565 }