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