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