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<>();
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<>();
175         }
176 
177         scopesToCollect = new HashSet<>( 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(),
207                                                              result.getUnresolvedDependencies() ) )
208             {
209                 logger.warn( "The following dependencies could not be resolved at this point of the build"
210                     + " but seem to be part of the reactor:" );
211 
212                 for ( Dependency dependency : result.getUnresolvedDependencies() )
213                 {
214                     logger.warn( "o " + dependency );
215                 }
216 
217                 logger.warn( "Try running the build up to the lifecycle phase \"package\"" );
218             }
219             else
220             {
221                 throw new LifecycleExecutionException( null, project, e );
222             }
223         }
224 
225         eventSpyDispatcher.onEvent( result );
226 
227         Set<Artifact> artifacts = new LinkedHashSet<>();
228         if ( result.getDependencyGraph() != null && !result.getDependencyGraph().getChildren().isEmpty() )
229         {
230             RepositoryUtils.toArtifacts( artifacts, result.getDependencyGraph().getChildren(),
231                                          Collections.singletonList( project.getArtifact().getId() ), collectionFilter );
232         }
233         return artifacts;
234     }
235 
236     private boolean areAllDependenciesInReactor( Collection<MavenProject> projects,
237                                                  Collection<Dependency> dependencies )
238     {
239         Set<String> projectKeys = getReactorProjectKeys( projects );
240 
241         for ( Dependency dependency : dependencies )
242         {
243             org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
244             String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
245             if ( !projectKeys.contains( key ) )
246             {
247                 return false;
248             }
249         }
250 
251         return true;
252     }
253 
254     private Set<String> getReactorProjectKeys( Collection<MavenProject> projects )
255     {
256         Set<String> projectKeys = new HashSet<>( projects.size() * 2 );
257         for ( MavenProject project : projects )
258         {
259             String key = ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
260             projectKeys.add( key );
261         }
262         return projectKeys;
263     }
264 
265     private Collection<String> negate( Collection<String> scopes )
266     {
267         Collection<String> result = new HashSet<>();
268         Collections.addAll( result, "system", "compile", "provided", "runtime", "test" );
269 
270         for ( String scope : scopes )
271         {
272             if ( "compile".equals( scope ) )
273             {
274                 result.remove( "compile" );
275                 result.remove( "system" );
276                 result.remove( "provided" );
277             }
278             else if ( "runtime".equals( scope ) )
279             {
280                 result.remove( "compile" );
281                 result.remove( "runtime" );
282             }
283             else if ( "compile+runtime".equals( scope ) )
284             {
285                 result.remove( "compile" );
286                 result.remove( "system" );
287                 result.remove( "provided" );
288                 result.remove( "runtime" );
289             }
290             else if ( "runtime+system".equals( scope ) )
291             {
292                 result.remove( "compile" );
293                 result.remove( "system" );
294                 result.remove( "runtime" );
295             }
296             else if ( "test".equals( scope ) )
297             {
298                 result.clear();
299             }
300         }
301 
302         return result;
303     }
304 
305     private static class ReactorDependencyFilter
306         implements DependencyFilter
307     {
308 
309         private Set<String> keys = new HashSet<>();
310 
311         public ReactorDependencyFilter( Collection<Artifact> artifacts )
312         {
313             for ( Artifact artifact : artifacts )
314             {
315                 String key = ArtifactUtils.key( artifact );
316                 keys.add( key );
317             }
318         }
319 
320         public boolean accept( DependencyNode node, List<DependencyNode> parents )
321         {
322             Dependency dependency = node.getDependency();
323             if ( dependency != null )
324             {
325                 org.eclipse.aether.artifact.Artifact a = dependency.getArtifact();
326                 String key = ArtifactUtils.key( a.getGroupId(), a.getArtifactId(), a.getVersion() );
327                 return !keys.contains( key );
328             }
329             return false;
330         }
331 
332     }
333 
334 }