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