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