View Javadoc
1   package org.apache.maven.plugins.dependency.tree;
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 org.apache.maven.model.DependencyManagement;
23  import org.apache.maven.model.Model;
24  import org.apache.maven.project.DefaultDependencyResolutionRequest;
25  import org.apache.maven.project.DependencyResolutionException;
26  import org.apache.maven.project.DependencyResolutionRequest;
27  import org.apache.maven.project.DependencyResolutionResult;
28  import org.apache.maven.project.MavenProject;
29  import org.apache.maven.project.ProjectBuildingRequest;
30  import org.apache.maven.project.ProjectDependenciesResolver;
31  import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
32  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
33  import org.eclipse.aether.DefaultRepositorySystemSession;
34  import org.eclipse.aether.RepositorySystemSession;
35  import org.eclipse.aether.artifact.Artifact;
36  import org.eclipse.aether.artifact.DefaultArtifact;
37  import org.eclipse.aether.collection.DependencySelector;
38  import org.eclipse.aether.graph.DefaultDependencyNode;
39  import org.eclipse.aether.graph.Dependency;
40  import org.eclipse.aether.graph.DependencyNode;
41  import org.eclipse.aether.util.graph.selector.AndDependencySelector;
42  import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
43  import org.eclipse.aether.util.graph.selector.ScopeDependencySelector;
44  import org.eclipse.aether.util.graph.transformer.ChainedDependencyGraphTransformer;
45  import org.eclipse.aether.util.graph.transformer.JavaDependencyContextRefiner;
46  
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.HashMap;
50  import java.util.HashSet;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.Objects;
55  import java.util.Set;
56  
57  /**
58   * Builds the VerboseDependencyGraph
59   */
60  class VerboseDependencyGraphBuilder
61  {
62      private static final String PRE_MANAGED_SCOPE = "preManagedScope", PRE_MANAGED_VERSION = "preManagedVersion",
63              MANAGED_SCOPE = "managedScope";
64  
65      public DependencyNode buildVerboseGraph( MavenProject project, ProjectDependenciesResolver resolver,
66                                               RepositorySystemSession repositorySystemSession,
67                                               Collection<MavenProject> reactorProjects,
68                                               ProjectBuildingRequest buildingRequest )
69              throws DependencyGraphBuilderException
70      {
71          DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
72          session.setLocalRepositoryManager( repositorySystemSession.getLocalRepositoryManager() );
73  
74          DependencySelector dependencySelector = new AndDependencySelector(
75                  // ScopeDependencySelector takes exclusions. 'Provided' scope is not here to avoid
76                  // false positive in LinkageChecker.
77                  new ScopeDependencySelector(), new ExclusionDependencySelector() );
78  
79          session.setDependencySelector( dependencySelector );
80          session.setDependencyGraphTransformer(
81                  new ChainedDependencyGraphTransformer( new CycleBreakerGraphTransformer(), // Avoids StackOverflowError
82                          new JavaDependencyContextRefiner() ) );
83          session.setDependencyManager( null );
84  
85          DependencyResolutionRequest request = new DefaultDependencyResolutionRequest();
86          request.setMavenProject( buildingRequest.getProject() );
87          request.setRepositorySession( session ) ;
88          DependencyNode rootNode;
89          boolean reactor = false;
90  
91          try
92          {
93              rootNode = resolver.resolve( request ).getDependencyGraph();
94          }
95          catch ( DependencyResolutionException e )
96          {
97              // cannot properly resolve reactor dependencies with verbose RepositorySystemSession
98              // this should be fixed in the future
99              DependencyResolutionRequest reactorRequest = new DefaultDependencyResolutionRequest();
100             reactorRequest.setMavenProject( buildingRequest.getProject() );
101             reactorRequest.setRepositorySession( buildingRequest.getRepositorySession() ) ;
102             try
103             {
104                 rootNode = resolver.resolve( reactorRequest ).getDependencyGraph();
105             }
106             catch ( DependencyResolutionException exception )
107             {
108                 if ( reactorProjects == null )
109                 {
110                     throw new DependencyGraphBuilderException( "Could not resolve following dependencies: "
111                             + exception.getResult().getUnresolvedDependencies(), exception );
112                 }
113                 reactor = true;
114                 // try collecting from reactor
115                 rootNode = collectDependenciesFromReactor( exception, reactorProjects ).getDependencyGraph();
116                 rootNode.setData( "ContainsModule", "True" );
117                 // rootNode.setArtifact( rootArtifact.setProperties( artifactProperties ) );
118             }
119         }
120 
121         // Don't want transitive test dependencies included in analysis
122         DependencyNode prunedRoot = pruneTransitiveTestDependencies( rootNode, project );
123         applyDependencyManagement( project, prunedRoot );
124         if ( reactor )
125         {
126             prunedRoot.setData( "ContainsModule", "True" );
127         }
128         return prunedRoot;
129     }
130 
131     private void applyDependencyManagement( MavenProject project, DependencyNode root )
132     {
133         Map<String, org.apache.maven.model.Dependency> dependencyManagementMap = createDependencyManagementMap(
134                 project.getDependencyManagement() );
135 
136         for ( DependencyNode child : root.getChildren() )
137         {
138             for ( DependencyNode nonTransitiveDependencyNode : child.getChildren() )
139             {
140                 applyDependencyManagementDfs( dependencyManagementMap, nonTransitiveDependencyNode );
141             }
142         }
143     }
144 
145     private void applyDependencyManagementDfs( Map<String, org.apache.maven.model.Dependency> dependencyManagementMap,
146                                                DependencyNode node )
147     {
148         if ( dependencyManagementMap.containsKey( getDependencyManagementCoordinate( node.getArtifact() ) ) )
149         {
150             org.apache.maven.model.Dependency manager = dependencyManagementMap.get(
151                     getDependencyManagementCoordinate( node.getArtifact() ) );
152             Map<String, String> artifactProperties = new HashMap<>();
153             for ( Map.Entry<String, String> entry : node.getArtifact().getProperties().entrySet() )
154             {
155                 artifactProperties.put( entry.getKey(), entry.getValue() );
156             }
157 
158             if ( !manager.getVersion().equals( node.getArtifact().getVersion() ) )
159             {
160                 artifactProperties.put( PRE_MANAGED_VERSION, node.getArtifact().getVersion() );
161                 node.setArtifact( node.getArtifact().setVersion( manager.getVersion() ) );
162             }
163 
164             String managerScope = Objects.toString( manager.getScope(), "compile" );
165             Dependency dependency = node.getDependency();
166             String dependencyScope = dependency.getScope();
167             if ( !managerScope.equals( dependencyScope ) )
168             {
169                 artifactProperties.put( PRE_MANAGED_SCOPE, dependencyScope );
170                 // be aware this does not actually change the node's scope, it may need to be fixed in the future
171                 artifactProperties.put( MANAGED_SCOPE, managerScope );
172             }
173             node.setArtifact( node.getArtifact().setProperties( artifactProperties ) );
174             dependency.setArtifact( dependency.getArtifact().setProperties( artifactProperties ) );
175         }
176         for ( DependencyNode child : node.getChildren() )
177         {
178             applyDependencyManagementDfs( dependencyManagementMap, child );
179         }
180     }
181 
182     private static Map<String, org.apache.maven.model.Dependency> createDependencyManagementMap(
183             DependencyManagement dependencyManagement )
184     {
185         Map<String, org.apache.maven.model.Dependency> dependencyManagementMap = new HashMap<>();
186         if ( dependencyManagement == null )
187         {
188             return dependencyManagementMap;
189         }
190         for ( org.apache.maven.model.Dependency dependency : dependencyManagement.getDependencies() )
191         {
192             dependencyManagementMap.put( getDependencyManagementCoordinate( dependency ), dependency );
193         }
194         return dependencyManagementMap;
195     }
196 
197     private static String getDependencyManagementCoordinate( org.apache.maven.model.Dependency dependency )
198     {
199         StringBuilder builder = new StringBuilder();
200         builder.append( dependency.getGroupId() ).append( ":" ).append( dependency.getArtifactId() ).append( ":" )
201                 .append( dependency.getType() );
202         if ( dependency.getClassifier() != null && !dependency.getClassifier().equals( "" ) )
203         {
204             builder.append( ":" ).append( dependency.getClassifier() );
205         }
206         return builder.toString();
207     }
208 
209     private static String getDependencyManagementCoordinate( Artifact artifact )
210     {
211         StringBuilder builder = new StringBuilder();
212         builder.append( artifact.getGroupId() ).append( ":" ).append( artifact.getArtifactId() ).append( ":" ).append(
213                 artifact.getExtension() );
214         if ( artifact.getClassifier() != null && !artifact.getClassifier().equals( "" ) )
215         {
216             builder.append( ":" ).append( artifact.getClassifier() );
217         }
218         return builder.toString();
219     }
220 
221     private Dependency getProjectDependency( MavenProject project )
222     {
223         Model model = project.getModel();
224 
225         return new Dependency( new DefaultArtifact( model.getGroupId(), model.getArtifactId(), model.getPackaging(),
226                 model.getVersion() ), "" );
227     }
228 
229     private DependencyNode pruneTransitiveTestDependencies( DependencyNode rootNode, MavenProject project )
230     {
231         Set<DependencyNode> visitedNodes = new HashSet<>();
232         DependencyNode newRoot = new DefaultDependencyNode( getProjectDependency( project ) );
233         newRoot.setChildren( new ArrayList<DependencyNode>() );
234 
235         for ( int i = 0; i < rootNode.getChildren().size(); i++ )
236         {
237             DependencyNode childNode = rootNode.getChildren().get( i );
238             newRoot.getChildren().add( childNode );
239 
240             pruneTransitiveTestDependenciesDfs( childNode, visitedNodes );
241         }
242 
243         return newRoot;
244     }
245 
246     private void pruneTransitiveTestDependenciesDfs( DependencyNode node, Set<DependencyNode> visitedNodes )
247     {
248         if ( !visitedNodes.contains( node ) )
249         {
250             visitedNodes.add( node );
251             // iterator needed to avoid concurrentModificationException
252             Iterator<DependencyNode> iterator = node.getChildren().iterator();
253             while ( iterator.hasNext() )
254             {
255                 DependencyNode child = iterator.next();
256                 if ( child.getDependency().getScope().equals( "test" ) )
257                 {
258                     iterator.remove();
259                 }
260                 else
261                 {
262                     pruneTransitiveTestDependenciesDfs( child, visitedNodes );
263                 }
264             }
265         }
266     }
267 
268     private DependencyResolutionResult collectDependenciesFromReactor( DependencyResolutionException e,
269                                                                        Collection<MavenProject> reactorProjects )
270             throws DependencyGraphBuilderException
271     {
272         DependencyResolutionResult result = e.getResult();
273 
274         List<Dependency> reactorDeps = getReactorDependencies( reactorProjects, result.getUnresolvedDependencies() );
275         result.getUnresolvedDependencies().removeAll( reactorDeps );
276         result.getResolvedDependencies().addAll( reactorDeps );
277 
278         if ( !result.getUnresolvedDependencies().isEmpty() )
279         {
280             throw new DependencyGraphBuilderException( "Could not resolve nor collect following dependencies: "
281                     + result.getUnresolvedDependencies(), e );
282         }
283 
284         return result;
285     }
286 
287     private List<Dependency> getReactorDependencies( Collection<MavenProject> reactorProjects, List<?> dependencies )
288     {
289         Set<ArtifactKey> reactorProjectsIds = new HashSet<ArtifactKey>();
290         for ( MavenProject project : reactorProjects )
291         {
292             reactorProjectsIds.add( new ArtifactKey( project ) );
293         }
294 
295         List<Dependency> reactorDeps = new ArrayList<Dependency>();
296         for ( Object untypedDependency : dependencies )
297         {
298             Dependency dependency = (Dependency) untypedDependency;
299             org.eclipse.aether.artifact.Artifact depArtifact = dependency.getArtifact();
300 
301             ArtifactKey key =
302                     new ArtifactKey( depArtifact.getGroupId(), depArtifact.getArtifactId(), depArtifact.getVersion() );
303 
304             if ( reactorProjectsIds.contains( key ) )
305             {
306                 reactorDeps.add( dependency );
307             }
308         }
309 
310         return reactorDeps;
311     }
312 }