1 package org.apache.maven.plugins.dependency.tree;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.artifact.Artifact;
23 import org.apache.maven.artifact.DefaultArtifact;
24 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
25 import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
26 import org.apache.maven.artifact.versioning.ArtifactVersion;
27 import org.apache.maven.artifact.versioning.Restriction;
28 import org.apache.maven.artifact.versioning.VersionRange;
29 import org.apache.maven.execution.MavenSession;
30 import org.apache.maven.model.Exclusion;
31 import org.apache.maven.plugin.AbstractMojo;
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.plugin.MojoFailureException;
34 import org.apache.maven.plugins.annotations.Component;
35 import org.apache.maven.plugins.annotations.Mojo;
36 import org.apache.maven.plugins.annotations.Parameter;
37 import org.apache.maven.plugins.annotations.ResolutionScope;
38 import org.apache.maven.plugins.dependency.utils.DependencyUtil;
39 import org.apache.maven.project.DefaultProjectBuildingRequest;
40 import org.apache.maven.project.MavenProject;
41 import org.apache.maven.project.ProjectBuildingRequest;
42 import org.apache.maven.project.ProjectDependenciesResolver;
43 import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
44 import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
45 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
46 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
47 import org.apache.maven.shared.dependency.graph.DependencyNode;
48 import org.apache.maven.shared.dependency.graph.filter.AncestorOrSelfDependencyNodeFilter;
49 import org.apache.maven.shared.dependency.graph.filter.AndDependencyNodeFilter;
50 import org.apache.maven.shared.dependency.graph.filter.ArtifactDependencyNodeFilter;
51 import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
52 import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
53 import org.apache.maven.shared.dependency.graph.traversal.BuildingDependencyNodeVisitor;
54 import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
55 import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
56 import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
57 import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor;
58 import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor.GraphTokens;
59 import org.eclipse.aether.RepositorySystem;
60 import org.eclipse.aether.RepositorySystemSession;
61 import org.eclipse.aether.repository.RemoteRepository;
62
63 import java.io.File;
64 import java.io.IOException;
65 import java.io.StringWriter;
66 import java.io.Writer;
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.List;
70 import java.util.Objects;
71
72
73
74
75
76
77
78
79
80
81 @Mojo( name = "tree", requiresDependencyCollection = ResolutionScope.TEST, threadSafe = true )
82 public class TreeMojo
83 extends AbstractMojo
84 {
85
86
87
88
89
90 @Parameter( defaultValue = "${project}", readonly = true, required = true )
91 private MavenProject project;
92
93 @Parameter( defaultValue = "${session}", readonly = true, required = true )
94 private MavenSession session;
95
96 @Parameter( property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}" )
97 private String outputEncoding;
98
99
100
101
102 @Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
103 private List<MavenProject> reactorProjects;
104
105 @Component
106 private RepositorySystem repositorySystem;
107
108 @Parameter ( defaultValue = "${repositorySystem}" )
109 RepositorySystem repositorySystemParam;
110
111
112
113
114 @Parameter( defaultValue = "${repositorySystemSession}" )
115 private RepositorySystemSession repoSession;
116
117
118
119
120 @Parameter( defaultValue = "${project.remoteProjectRepositories}" )
121 private List<RemoteRepository> projectRepos;
122
123
124
125
126 @Component( hint = "default" )
127 private DependencyGraphBuilder dependencyGraphBuilder;
128
129
130
131
132
133
134
135 @Parameter( property = "outputFile" )
136 private File outputFile;
137
138
139
140
141
142
143
144
145 @Parameter( property = "outputType", defaultValue = "text" )
146 private String outputType;
147
148
149
150
151
152
153
154
155 @Parameter( property = "scope" )
156 private String scope;
157
158
159
160
161
162
163
164
165 @Parameter( property = "verbose", defaultValue = "false" )
166 private boolean verbose;
167
168
169
170
171
172
173
174
175 @Parameter( property = "tokens", defaultValue = "standard" )
176 private String tokens;
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196 @Parameter( property = "includes" )
197 private String includes;
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 @Parameter( property = "excludes" )
218 private String excludes;
219
220
221
222
223 private DependencyNode rootNode;
224
225
226
227
228
229
230 @Parameter( property = "appendOutput", defaultValue = "false" )
231 private boolean appendOutput;
232
233
234
235
236
237
238 @Parameter( property = "skip", defaultValue = "false" )
239 private boolean skip;
240
241
242 @Component
243 ProjectDependenciesResolver resolver;
244
245
246
247 @Override
248 public void execute()
249 throws MojoExecutionException, MojoFailureException
250 {
251 if ( isSkip() )
252 {
253 getLog().info( "Skipping plugin execution" );
254 return;
255 }
256
257 try
258 {
259 String dependencyTreeString;
260
261
262 ArtifactFilter artifactFilter = createResolvingArtifactFilter();
263
264 ProjectBuildingRequest buildingRequest =
265 new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
266
267 buildingRequest.setProject( project );
268
269 if ( verbose )
270 {
271
272
273 VerboseDependencyGraphBuilder builder = new VerboseDependencyGraphBuilder();
274 AbstractVerboseGraphSerializer serializer = getSerializer();
275
276 org.eclipse.aether.graph.DependencyNode verboseRootNode = builder.buildVerboseGraph(
277 project, resolver, repoSession, reactorProjects, buildingRequest );
278 dependencyTreeString = serializer.serialize( verboseRootNode );
279 rootNode = convertToCustomDependencyNode( verboseRootNode );
280 }
281 else
282 {
283
284
285 rootNode = dependencyGraphBuilder.buildDependencyGraph( buildingRequest, artifactFilter,
286 reactorProjects );
287
288 dependencyTreeString = serializeDependencyTree( rootNode );
289 }
290
291 if ( outputFile != null )
292 {
293 String encoding = Objects.toString( outputEncoding, "UTF-8" );
294 DependencyUtil.write( dependencyTreeString, outputFile, this.appendOutput, encoding );
295
296 getLog().info( "Wrote dependency tree to: " + outputFile );
297 }
298 else
299 {
300 DependencyUtil.log( dependencyTreeString, getLog() );
301 }
302 }
303 catch ( DependencyGraphBuilderException exception )
304 {
305 throw new MojoExecutionException( "Cannot build project dependency graph", exception );
306 }
307 catch ( IOException exception )
308 {
309 throw new MojoExecutionException( "Cannot serialize project dependency graph", exception );
310 }
311 }
312
313
314
315
316
317
318
319
320 public MavenProject getProject()
321 {
322 return project;
323 }
324
325
326
327
328
329
330 public DependencyNode getDependencyGraph()
331 {
332 return rootNode;
333 }
334
335
336
337
338 public boolean isSkip()
339 {
340 return skip;
341 }
342
343
344
345
346 public void setSkip( boolean skip )
347 {
348 this.skip = skip;
349 }
350
351
352
353 private AbstractVerboseGraphSerializer getSerializer( )
354 {
355 if ( "graphml".equals( outputType ) )
356 {
357 return new VerboseGraphGraphmlSerializer();
358 }
359 else if ( "tgf".equals( outputType ) )
360 {
361 return new VerboseGraphTgfSerializer();
362 }
363 else if ( "dot".equals( outputType ) )
364 {
365 return new VerboseGraphDotSerializer();
366 }
367 else
368 {
369 return new VerboseGraphTextSerializer();
370 }
371 }
372
373 private DependencyNode convertToCustomDependencyNode( org.eclipse.aether.graph.DependencyNode node )
374 {
375 DefaultDependencyNode rootNode = new DefaultDependencyNode( null,
376 convertAetherArtifactToMavenArtifact( node ), null, null, null );
377
378 rootNode.setChildren( new ArrayList<DependencyNode>() );
379
380 for ( org.eclipse.aether.graph.DependencyNode child : node.getChildren() )
381 {
382 rootNode.getChildren().add( buildTree( rootNode, child ) );
383 }
384
385 return rootNode;
386 }
387
388 private DependencyNode buildTree( DependencyNode parent, org.eclipse.aether.graph.DependencyNode child )
389 {
390 List<org.apache.maven.model.Exclusion> exclusions = new ArrayList<>();
391
392 for ( org.eclipse.aether.graph.Exclusion exclusion : child.getDependency().getExclusions() )
393 {
394 exclusions.add( convertAetherExclusionToMavenExclusion( exclusion ) );
395 }
396
397 DefaultDependencyNode newChild = new DefaultDependencyNode( parent,
398 convertAetherArtifactToMavenArtifact( child ),
399 child.getArtifact().getProperties().get( "preManagedVersion" ),
400 child.getArtifact().getProperties().get( "preManagedScope" ), null,
401 child.getDependency().isOptional() );
402
403 newChild.setChildren( new ArrayList<DependencyNode>() );
404
405 for ( org.eclipse.aether.graph.DependencyNode grandChild : child.getChildren() )
406 {
407 newChild.getChildren().add( buildTree( newChild, grandChild ) );
408 }
409
410 return newChild;
411 }
412
413 private static Artifact convertAetherArtifactToMavenArtifact( org.eclipse.aether.graph.DependencyNode node )
414 {
415 org.eclipse.aether.artifact.Artifact artifact = node.getArtifact();
416 return new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(),
417 artifact.getVersion(), node.getDependency().getScope(), artifact.getExtension(),
418 artifact.getClassifier(), null );
419 }
420
421 private static Exclusion convertAetherExclusionToMavenExclusion ( org.eclipse.aether.graph.Exclusion exclusion )
422 {
423 Exclusion mavenExclusion = new Exclusion();
424 mavenExclusion.setArtifactId( exclusion.getArtifactId() );
425 mavenExclusion.setGroupId( exclusion.getGroupId() );
426
427 return mavenExclusion;
428 }
429
430
431
432
433
434
435 private ArtifactFilter createResolvingArtifactFilter()
436 {
437 ArtifactFilter filter;
438
439
440 if ( scope != null )
441 {
442 getLog().debug( "+ Resolving dependency tree for scope '" + scope + "'" );
443
444 filter = new ScopeArtifactFilter( scope );
445 }
446 else
447 {
448 filter = null;
449 }
450
451 return filter;
452 }
453
454
455
456
457
458
459
460 private String serializeDependencyTree( DependencyNode theRootNode )
461 {
462 StringWriter writer = new StringWriter();
463
464 DependencyNodeVisitor visitor = getSerializingDependencyNodeVisitor( writer );
465
466
467 visitor = new BuildingDependencyNodeVisitor( visitor );
468
469 DependencyNodeFilter filter = createDependencyNodeFilter();
470
471 if ( filter != null )
472 {
473 CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
474 DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor( collectingVisitor, filter );
475 theRootNode.accept( firstPassVisitor );
476
477 DependencyNodeFilter secondPassFilter =
478 new AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
479 visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
480 }
481
482 theRootNode.accept( visitor );
483
484 return writer.toString();
485 }
486
487
488
489
490
491 public DependencyNodeVisitor getSerializingDependencyNodeVisitor( Writer writer )
492 {
493 if ( "graphml".equals( outputType ) )
494 {
495 return new GraphmlDependencyNodeVisitor( writer );
496 }
497 else if ( "tgf".equals( outputType ) )
498 {
499 return new TGFDependencyNodeVisitor( writer );
500 }
501 else if ( "dot".equals( outputType ) )
502 {
503 return new DOTDependencyNodeVisitor( writer );
504 }
505 else
506 {
507 return new SerializingDependencyNodeVisitor( writer, toGraphTokens( tokens ) );
508 }
509 }
510
511
512
513
514
515
516
517 private GraphTokens toGraphTokens( String theTokens )
518 {
519 GraphTokens graphTokens;
520
521 if ( "whitespace".equals( theTokens ) )
522 {
523 getLog().debug( "+ Using whitespace tree tokens" );
524
525 graphTokens = SerializingDependencyNodeVisitor.WHITESPACE_TOKENS;
526 }
527 else if ( "extended".equals( theTokens ) )
528 {
529 getLog().debug( "+ Using extended tree tokens" );
530
531 graphTokens = SerializingDependencyNodeVisitor.EXTENDED_TOKENS;
532 }
533 else
534 {
535 graphTokens = SerializingDependencyNodeVisitor.STANDARD_TOKENS;
536 }
537
538 return graphTokens;
539 }
540
541
542
543
544
545
546 private DependencyNodeFilter createDependencyNodeFilter()
547 {
548 List<DependencyNodeFilter> filters = new ArrayList<>();
549
550
551 if ( includes != null )
552 {
553 List<String> patterns = Arrays.asList( includes.split( "," ) );
554
555 getLog().debug( "+ Filtering dependency tree by artifact include patterns: " + patterns );
556
557 ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter( patterns );
558 filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
559 }
560
561
562 if ( excludes != null )
563 {
564 List<String> patterns = Arrays.asList( excludes.split( "," ) );
565
566 getLog().debug( "+ Filtering dependency tree by artifact exclude patterns: " + patterns );
567
568 ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter( patterns );
569 filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
570 }
571
572 return filters.isEmpty() ? null : new AndDependencyNodeFilter( filters );
573 }
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588 @Deprecated
589 public static boolean containsVersion( VersionRange allowedRange, ArtifactVersion theVersion )
590 {
591 ArtifactVersion recommendedVersion = allowedRange.getRecommendedVersion();
592 if ( recommendedVersion == null )
593 {
594 List<Restriction> restrictions = allowedRange.getRestrictions();
595 for ( Restriction restriction : restrictions )
596 {
597 if ( restriction.containsVersion( theVersion ) )
598 {
599 return true;
600 }
601 }
602 return false;
603 }
604 else
605 {
606
607 return recommendedVersion.compareTo( theVersion ) <= 0;
608 }
609 }
610 }