View Javadoc
1   package org.apache.maven.lifecycle.internal;
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  
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import javax.inject.Inject;
36  import javax.inject.Named;
37  
38  import org.apache.maven.RepositoryUtils;
39  import org.apache.maven.artifact.Artifact;
40  import org.apache.maven.artifact.ArtifactUtils;
41  import org.apache.maven.eventspy.internal.EventSpyDispatcher;
42  import org.apache.maven.execution.MavenSession;
43  import org.apache.maven.lifecycle.LifecycleExecutionException;
44  import org.apache.maven.project.DefaultDependencyResolutionRequest;
45  import org.apache.maven.project.DependencyResolutionException;
46  import org.apache.maven.project.DependencyResolutionResult;
47  import org.apache.maven.project.MavenProject;
48  import org.apache.maven.project.ProjectDependenciesResolver;
49  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
50  import org.apache.maven.project.artifact.ProjectArtifactsCache;
51  import org.codehaus.plexus.logging.Logger;
52  import org.eclipse.aether.graph.Dependency;
53  import org.eclipse.aether.graph.DependencyFilter;
54  import org.eclipse.aether.graph.DependencyNode;
55  import org.eclipse.aether.util.filter.AndDependencyFilter;
56  import org.eclipse.aether.util.filter.ScopeDependencyFilter;
57  
58  /**
59   * <p>
60   * Resolves dependencies for the artifacts in context of the lifecycle build
61   * </p>
62   * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
63   * @since 3.0
64   * @author Benjamin Bentmann
65   * @author Jason van Zyl
66   * @author Kristian Rosenvold (extracted class)
67   */
68  @Named
69  public class LifecycleDependencyResolver
70  {
71  
72      @Inject
73      private ProjectDependenciesResolver dependenciesResolver;
74  
75      @Inject
76      private Logger logger;
77  
78      @Inject
79      private ProjectArtifactFactory artifactFactory;
80  
81      @Inject
82      private EventSpyDispatcher eventSpyDispatcher;
83      
84      @Inject
85      private ProjectArtifactsCache projectArtifactsCache;
86  
87      public LifecycleDependencyResolver()
88      {
89      }
90  
91      public LifecycleDependencyResolver( ProjectDependenciesResolver projectDependenciesResolver, Logger logger )
92      {
93          this.dependenciesResolver = projectDependenciesResolver;
94          this.logger = logger;
95      }
96  
97      public static List<MavenProject> getProjects( MavenProject project, MavenSession session, boolean aggregator )
98      {
99          if ( aggregator && project.getCollectedProjects() != null )
100         {
101             List<MavenProject> sortedProjects = new ArrayList<>( session.getProjects() ); // sorted but all
102             List<MavenProject> projectAndSubmodules = new ArrayList<>();
103             addProjectAndSubModules( projectAndSubmodules, project ); // not sorted but what we need
104             Iterator<MavenProject> sortedProjectsIterator = sortedProjects.listIterator();
105             while ( sortedProjectsIterator.hasNext() )
106             {
107                 if ( !projectAndSubmodules.contains( sortedProjectsIterator.next() ) )
108                 {
109                     sortedProjectsIterator.remove();
110                 }
111             }
112             return sortedProjects;
113         }
114         else
115         {
116             return Collections.singletonList( project );
117         }
118     }
119 
120     private static void addProjectAndSubModules( List<MavenProject> projects, MavenProject project )
121     {
122         projects.add( project );
123         for ( MavenProject submodule : project.getCollectedProjects() )
124         {
125             addProjectAndSubModules( projects, submodule );
126         }
127     }
128 
129 
130     public void resolveProjectDependencies( MavenProject project, Collection<String> scopesToCollect,
131                                             Collection<String> scopesToResolve, MavenSession session,
132                                             boolean aggregating, Set<Artifact> projectArtifacts )
133         throws LifecycleExecutionException
134     {
135         ClassLoader tccl = Thread.currentThread().getContextClassLoader();
136         try
137         {
138             ClassLoader projectRealm = project.getClassRealm();
139             if ( projectRealm != null && projectRealm != tccl )
140             {
141                 Thread.currentThread().setContextClassLoader( projectRealm );
142             }
143 
144             if ( project.getDependencyArtifacts() == null )
145             {
146                 try
147                 {
148                     project.setDependencyArtifacts( artifactFactory.createArtifacts( project ) );
149                 }
150                 catch ( InvalidDependencyVersionException e )
151                 {
152                     throw new LifecycleExecutionException( e );
153                 }
154             }
155             
156             Set<Artifact> resolvedArtifacts;
157             ProjectArtifactsCache.Key cacheKey = projectArtifactsCache.createKey( project,  scopesToCollect, 
158                 scopesToResolve, aggregating, session.getRepositorySession() );
159             ProjectArtifactsCache.CacheRecord recordArtifacts;
160             recordArtifacts = projectArtifactsCache.get( cacheKey );
161             
162             if ( recordArtifacts != null )
163             {
164                 resolvedArtifacts = recordArtifacts.getArtifacts();
165             }
166             else
167             {
168                 try
169                 {
170                     resolvedArtifacts = getDependencies( project, scopesToCollect, scopesToResolve, session,
171                                                          aggregating, projectArtifacts );
172                     recordArtifacts = projectArtifactsCache.put( cacheKey, resolvedArtifacts );
173                 }
174                 catch ( LifecycleExecutionException e )
175                 {
176                   projectArtifactsCache.put( cacheKey, e );
177                   projectArtifactsCache.register( project, cacheKey, recordArtifacts );
178                     throw e;
179                 }
180             }
181             projectArtifactsCache.register( project, cacheKey, recordArtifacts );
182 
183             Map<Artifact, File> reactorProjects = new HashMap<>( session.getProjects().size() );
184             for ( MavenProject reactorProject : session.getProjects() )
185             {
186                 reactorProjects.put( reactorProject.getArtifact(), reactorProject.getArtifact().getFile() );
187             }
188 
189             Map<String, Artifact> map = new HashMap<>();
190             for ( Artifact artifact : resolvedArtifacts )
191             {
192                 /**
193                  * MNG-6300: resolvedArtifacts can be cache result; this ensures reactor files are always up to date 
194                  * During lifecycle the Artifact.getFile() can change from target/classes to the actual jar.
195                  * This clearly shows that target/classes should not be abused as artifactFile just for the classpath
196                  */
197                 File reactorProjectFile = reactorProjects.get( artifact );
198                 if ( reactorProjectFile != null )
199                 {
200                     artifact.setFile( reactorProjectFile );
201                 }
202 
203                 map.put( artifact.getDependencyConflictId(), artifact );
204             }
205             
206             project.setResolvedArtifacts( resolvedArtifacts );
207             
208             for ( Artifact artifact : project.getDependencyArtifacts() )
209             {
210                 if ( artifact.getFile() == null )
211                 {
212                     Artifact resolved = map.get( artifact.getDependencyConflictId() );
213                     if ( resolved != null )
214                     {
215                         artifact.setFile( resolved.getFile() );
216                         artifact.setDependencyTrail( resolved.getDependencyTrail() );
217                         artifact.setResolvedVersion( resolved.getVersion() );
218                         artifact.setResolved( true );
219                     }
220                 }
221             }
222         }
223         finally
224         {
225             Thread.currentThread().setContextClassLoader( tccl );
226         }
227     }
228 
229     private Set<Artifact> getDependencies( MavenProject project, Collection<String> scopesToCollect,
230                                            Collection<String> scopesToResolve, MavenSession session,
231                                            boolean aggregating, Set<Artifact> projectArtifacts )
232         throws LifecycleExecutionException
233     {
234         if ( scopesToCollect == null )
235         {
236             scopesToCollect = Collections.emptySet();
237         }
238         if ( scopesToResolve == null )
239         {
240             scopesToResolve = Collections.emptySet();
241         }
242 
243         if ( scopesToCollect.isEmpty() && scopesToResolve.isEmpty() )
244         {
245             return new LinkedHashSet<>();
246         }
247 
248         scopesToCollect = new HashSet<>( scopesToCollect );
249         scopesToCollect.addAll( scopesToResolve );
250 
251         DependencyFilter collectionFilter = new ScopeDependencyFilter( null, negate( scopesToCollect ) );
252         DependencyFilter resolutionFilter = new ScopeDependencyFilter( null, negate( scopesToResolve ) );
253         resolutionFilter = AndDependencyFilter.newInstance( collectionFilter, resolutionFilter );
254         resolutionFilter =
255             AndDependencyFilter.newInstance( resolutionFilter, new ReactorDependencyFilter( projectArtifacts ) );
256 
257         DependencyResolutionResult result;
258         try
259         {
260             DefaultDependencyResolutionRequest request =
261                 new DefaultDependencyResolutionRequest( project, session.getRepositorySession() );
262             request.setResolutionFilter( resolutionFilter );
263 
264             eventSpyDispatcher.onEvent( request );
265 
266             result = dependenciesResolver.resolve( request );
267         }
268         catch ( DependencyResolutionException e )
269         {
270             result = e.getResult();
271 
272             /*
273              * MNG-2277, the check below compensates for our bad plugin support where we ended up with aggregator
274              * plugins that require dependency resolution although they usually run in phases of the build where project
275              * artifacts haven't been assembled yet. The prime example of this is "mvn release:prepare".
276              */
277             if ( aggregating && areAllDependenciesInReactor( session.getProjects(),
278                                                              result.getUnresolvedDependencies() ) )
279             {
280                 logger.warn( "The following dependencies could not be resolved at this point of the build"
281                     + " but seem to be part of the reactor:" );
282 
283                 for ( Dependency dependency : result.getUnresolvedDependencies() )
284                 {
285                     logger.warn( "o " + dependency );
286                 }
287 
288                 logger.warn( "Try running the build up to the lifecycle phase \"package\"" );
289             }
290             else
291             {
292                 throw new LifecycleExecutionException( null, project, e );
293             }
294         }
295 
296         eventSpyDispatcher.onEvent( result );
297 
298         Set<Artifact> artifacts = new LinkedHashSet<>();
299         if ( result.getDependencyGraph() != null && !result.getDependencyGraph().getChildren().isEmpty() )
300         {
301             RepositoryUtils.toArtifacts( artifacts, result.getDependencyGraph().getChildren(),
302                                          Collections.singletonList( project.getArtifact().getId() ), collectionFilter );
303         }
304         return artifacts;
305     }
306 
307     private boolean areAllDependenciesInReactor( Collection<MavenProject> projects,
308                                                  Collection<Dependency> dependencies )
309     {
310         Set<String> projectKeys = getReactorProjectKeys( projects );
311 
312         for ( Dependency dependency : dependencies )
313         {
314             org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
315             String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
316             if ( !projectKeys.contains( key ) )
317             {
318                 return false;
319             }
320         }
321 
322         return true;
323     }
324 
325     private Set<String> getReactorProjectKeys( Collection<MavenProject> projects )
326     {
327         Set<String> projectKeys = new HashSet<>( projects.size() * 2 );
328         for ( MavenProject project : projects )
329         {
330             String key = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
331             projectKeys.add( key );
332         }
333         return projectKeys;
334     }
335 
336     private Collection<String> negate( Collection<String> scopes )
337     {
338         Collection<String> result = new HashSet<>();
339         Collections.addAll( result, "system", "compile", "provided", "runtime", "test" );
340 
341         for ( String scope : scopes )
342         {
343             if ( "compile".equals( scope ) )
344             {
345                 result.remove( "compile" );
346                 result.remove( "system" );
347                 result.remove( "provided" );
348             }
349             else if ( "runtime".equals( scope ) )
350             {
351                 result.remove( "compile" );
352                 result.remove( "runtime" );
353             }
354             else if ( "compile+runtime".equals( scope ) )
355             {
356                 result.remove( "compile" );
357                 result.remove( "system" );
358                 result.remove( "provided" );
359                 result.remove( "runtime" );
360             }
361             else if ( "runtime+system".equals( scope ) )
362             {
363                 result.remove( "compile" );
364                 result.remove( "system" );
365                 result.remove( "runtime" );
366             }
367             else if ( "test".equals( scope ) )
368             {
369                 result.clear();
370             }
371         }
372 
373         return result;
374     }
375 
376     private static class ReactorDependencyFilter
377         implements DependencyFilter
378     {
379 
380         private Set<String> keys = new HashSet<>();
381 
382         ReactorDependencyFilter( Collection<Artifact> artifacts )
383         {
384             for ( Artifact artifact : artifacts )
385             {
386                 String key = ArtifactUtils.key( artifact );
387                 keys.add( key );
388             }
389         }
390 
391         public boolean accept( DependencyNode node, List<DependencyNode> parents )
392         {
393             Dependency dependency = node.getDependency();
394             if ( dependency != null )
395             {
396                 org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
397                 String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
398                 return !keys.contains( key );
399             }
400             return false;
401         }
402 
403     }
404 
405 }