View Javadoc

1   package org.apache.maven.plugin.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.artifact.repository.ArtifactRepository;
23  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
24  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
25  import org.apache.maven.artifact.versioning.ArtifactVersion;
26  import org.apache.maven.artifact.versioning.Restriction;
27  import org.apache.maven.artifact.versioning.VersionRange;
28  import org.apache.maven.plugin.AbstractMojo;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.MojoFailureException;
31  import org.apache.maven.plugin.dependency.utils.DependencyUtil;
32  import org.apache.maven.plugins.annotations.Component;
33  import org.apache.maven.plugins.annotations.Mojo;
34  import org.apache.maven.plugins.annotations.Parameter;
35  import org.apache.maven.plugins.annotations.ResolutionScope;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
38  import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
39  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
40  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
41  import org.apache.maven.shared.dependency.graph.DependencyNode;
42  import org.apache.maven.shared.dependency.graph.filter.AncestorOrSelfDependencyNodeFilter;
43  import org.apache.maven.shared.dependency.graph.filter.AndDependencyNodeFilter;
44  import org.apache.maven.shared.dependency.graph.filter.ArtifactDependencyNodeFilter;
45  import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
46  import org.apache.maven.shared.dependency.graph.traversal.BuildingDependencyNodeVisitor;
47  import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
48  import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
49  import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
50  import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor;
51  import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor.GraphTokens;
52  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
53  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
54  import org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.TreeTokens;
55  
56  import java.io.File;
57  import java.io.IOException;
58  import java.io.StringWriter;
59  import java.io.Writer;
60  import java.util.ArrayList;
61  import java.util.Arrays;
62  import java.util.List;
63  
64  /**
65   * Displays the dependency tree for this project.
66   *
67   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
68   * @version $Id: TreeMojo.html 861760 2013-05-12 17:31:26Z hboutemy $
69   * @since 2.0-alpha-5
70   */
71  @Mojo( name = "tree", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
72  public class TreeMojo
73      extends AbstractMojo
74  {
75      // fields -----------------------------------------------------------------
76  
77      /**
78       * The Maven project.
79       */
80      @Component
81      private MavenProject project;
82  
83      /**
84       * The dependency tree builder to use.
85       */
86      @Component( hint = "default" )
87      private DependencyGraphBuilder dependencyGraphBuilder;
88  
89      /**
90       * The dependency tree builder to use for verbose output.
91       */
92      @Component
93      private DependencyTreeBuilder dependencyTreeBuilder;
94  
95      /**
96       * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of
97       * writing to the console.
98       *
99       * @deprecated use outputFile instead.
100      */
101     @Parameter( property = "output" )
102     private File output;
103 
104     /**
105      * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of
106      * writing to the console.
107      *
108      * @since 2.0-alpha-5
109      */
110     @Parameter( property = "outputFile" )
111     private File outputFile;
112 
113     /**
114      * If specified, this parameter will cause the dependency tree to be written using the specified format. Currently
115      * supported format are: <code>text</code>, <code>dot</code>, <code>graphml</code> and <code>tgf</code>.
116      * <p/>
117      * These formats can be plotted to image files. An example of how to plot a dot file using
118      * pygraphviz can be found <a href="http://networkx.lanl.gov/pygraphviz/tutorial.html#layout-and-drawing">here</a>.
119      *
120      * @since 2.2
121      */
122     @Parameter( property = "outputType", defaultValue = "text" )
123     private String outputType;
124 
125     /**
126      * The scope to filter by when resolving the dependency tree, or <code>null</code> to include dependencies from
127      * all scopes. Note that this feature does not currently work due to MNG-3236.
128      *
129      * @see <a href="http://jira.codehaus.org/browse/MNG-3236">MNG-3236</a>
130      * @since 2.0-alpha-5
131      */
132     @Parameter( property = "scope" )
133     private String scope;
134 
135     /**
136      * Whether to include omitted nodes in the serialized dependency tree.
137      *
138      * @since 2.0-alpha-6
139      */
140     @Parameter( property = "verbose", defaultValue = "false" )
141     private boolean verbose;
142 
143     /**
144      * The token set name to use when outputting the dependency tree. Possible values are <code>whitespace</code>,
145      * <code>standard</code> or <code>extended</code>, which use whitespace, standard (ie ASCII) or extended
146      * character sets respectively.
147      *
148      * @since 2.0-alpha-6
149      */
150     @Parameter( property = "tokens", defaultValue = "standard" )
151     private String tokens;
152 
153     /**
154      * A comma-separated list of artifacts to filter the serialized dependency tree by, or <code>null</code> not to
155      * filter the dependency tree. The filter syntax is:
156      * 
157      * <pre>
158      * [groupId]:[artifactId]:[type]:[version]
159      * </pre>
160      * 
161      * where each pattern segment is optional and supports full and partial <code>*</code> wildcards. An empty pattern
162      * segment is treated as an implicit wildcard.
163      * <p>For example, <code>org.apache.*</code> will match all artifacts whose group id starts with
164      * <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> will match all snapshot artifacts.</p>
165      * 
166      * @see StrictPatternIncludesArtifactFilter
167      * @since 2.0-alpha-6
168      */
169     @Parameter( property = "includes" )
170     private String includes;
171 
172     /**
173      * A comma-separated list of artifacts to filter from the serialized dependency tree, or <code>null</code> not to
174      * filter any artifacts from the dependency tree. The filter syntax is:
175      * 
176      * <pre>
177      * [groupId]:[artifactId]:[type]:[version]
178      * </pre>
179      * 
180      * where each pattern segment is optional and supports full and partial <code>*</code> wildcards. An empty pattern
181      * segment is treated as an implicit wildcard.
182      * <p>For example, <code>org.apache.*</code> will match all artifacts whose group id starts with
183      * <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> will match all snapshot artifacts.</p>
184      *
185      * @see StrictPatternExcludesArtifactFilter
186      * @since 2.0-alpha-6
187      */
188     @Parameter( property = "excludes" )
189     private String excludes;
190 
191     /**
192      * The computed dependency tree root node of the Maven project.
193      */
194     private DependencyNode rootNode;
195 
196     /**
197      * Whether to append outputs into the output file or overwrite it.
198      *
199      * @since 2.2
200      */
201     @Parameter( property = "appendOutput", defaultValue = "false" )
202     private boolean appendOutput;
203 
204     /**
205      * Skip plugin execution completely.
206      *
207      * @since 2.7
208      */
209     @Parameter( property = "skip", defaultValue = "false" )
210     private boolean skip;
211 
212     @Parameter( defaultValue = "${localRepository}", readonly = true )
213     private ArtifactRepository localRepository;
214 
215     // Mojo methods -----------------------------------------------------------
216 
217     /*
218      * @see org.apache.maven.plugin.Mojo#execute()
219      */
220     public void execute()
221         throws MojoExecutionException, MojoFailureException
222     {
223         if ( isSkip() )
224         {
225             getLog().info( "Skipping plugin execution" );
226             return;
227         }
228 
229         if ( output != null )
230         {
231             getLog().warn( "The parameter output is deprecated. Use outputFile instead." );
232             this.outputFile = output;
233         }
234 
235         try
236         {
237             String dependencyTreeString = null;
238 
239             // TODO: note that filter does not get applied due to MNG-3236
240 
241             ArtifactFilter artifactFilter = createResolvingArtifactFilter();
242 
243             if ( verbose )
244             {
245                 // verbose mode force Maven 2 dependency tree component use
246                 dependencyTreeString =
247                     serializeVerboseDependencyTree( dependencyTreeBuilder.buildDependencyTree( project,
248                                                                                                localRepository,
249                                                                                                artifactFilter ) );
250             }
251             else
252             {
253                 // non-verbose mode use dependency graph component, which gives consistent results with Maven version
254                 // running
255                 rootNode = dependencyGraphBuilder.buildDependencyGraph( project, artifactFilter );
256 
257                 dependencyTreeString = serializeDependencyTree( rootNode );
258             }
259 
260             if ( outputFile != null )
261             {
262                 DependencyUtil.write( dependencyTreeString, outputFile, this.appendOutput, getLog() );
263 
264                 getLog().info( "Wrote dependency tree to: " + outputFile );
265             }
266             else
267             {
268                 DependencyUtil.log( dependencyTreeString, getLog() );
269             }
270         }
271         catch ( DependencyGraphBuilderException exception )
272         {
273             throw new MojoExecutionException( "Cannot build project dependency graph", exception );
274         }
275         catch ( DependencyTreeBuilderException exception )
276         {
277             throw new MojoExecutionException( "Cannot build project dependency tree", exception );
278         }
279         catch ( IOException exception )
280         {
281             throw new MojoExecutionException( "Cannot serialise project dependency graph", exception );
282         }
283     }
284 
285     // public methods ---------------------------------------------------------
286 
287     /**
288      * Gets the Maven project used by this mojo.
289      *
290      * @return the Maven project
291      */
292     public MavenProject getProject()
293     {
294         return project;
295     }
296 
297     /**
298      * Gets the computed dependency graph root node for the Maven project.
299      *
300      * @return the dependency tree root node
301      */
302     public DependencyNode getDependencyGraph()
303     {
304         return rootNode;
305     }
306 
307     public boolean isSkip()
308     {
309         return skip;
310     }
311 
312     public void setSkip( boolean skip )
313     {
314         this.skip = skip;
315     }
316 
317     // private methods --------------------------------------------------------
318 
319     /**
320      * Gets the artifact filter to use when resolving the dependency tree.
321      *
322      * @return the artifact filter
323      */
324     private ArtifactFilter createResolvingArtifactFilter()
325     {
326         ArtifactFilter filter;
327 
328         // filter scope
329         if ( scope != null )
330         {
331             getLog().debug( "+ Resolving dependency tree for scope '" + scope + "'" );
332 
333             filter = new ScopeArtifactFilter( scope );
334         }
335         else
336         {
337             filter = null;
338         }
339 
340         return filter;
341     }
342 
343     /**
344      * Serializes the specified dependency tree to a string.
345      *
346      * @param rootNode the dependency tree root node to serialize
347      * @return the serialized dependency tree
348      */
349     private String serializeDependencyTree( DependencyNode rootNode )
350     {
351         StringWriter writer = new StringWriter();
352 
353         DependencyNodeVisitor visitor = getSerializingDependencyNodeVisitor( writer );
354 
355         // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
356         visitor = new BuildingDependencyNodeVisitor( visitor );
357 
358         DependencyNodeFilter filter = createDependencyNodeFilter();
359 
360         if ( filter != null )
361         {
362             CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
363             DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor( collectingVisitor, filter );
364             rootNode.accept( firstPassVisitor );
365 
366             DependencyNodeFilter secondPassFilter =
367                 new AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
368             visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
369         }
370 
371         rootNode.accept( visitor );
372 
373         return writer.toString();
374     }
375 
376     /**
377      * Serializes the specified dependency tree to a string.
378      *
379      * @param rootNode the dependency tree root node to serialize
380      * @return the serialized dependency tree
381      */
382     private String serializeVerboseDependencyTree( org.apache.maven.shared.dependency.tree.DependencyNode rootNode )
383     {
384         StringWriter writer = new StringWriter();
385 
386         org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor visitor =
387             new org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor( writer,
388                                                                                                     toTreeTokens( tokens ) );
389 
390         // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
391         visitor = new org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor( visitor );
392 
393         org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter filter = createVerboseDependencyNodeFilter();
394 
395         if ( filter != null )
396         {
397             org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor collectingVisitor =
398                 new org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor();
399             org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor firstPassVisitor =
400                 new org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor( collectingVisitor,
401                                                                                                       filter );
402             rootNode.accept( firstPassVisitor );
403 
404             org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter secondPassFilter =
405                 new org.apache.maven.shared.dependency.tree.filter.AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
406             visitor =
407                 new org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor( visitor,
408                                                                                                       secondPassFilter );
409         }
410 
411         rootNode.accept( visitor );
412 
413         return writer.toString();
414     }
415 
416     public DependencyNodeVisitor getSerializingDependencyNodeVisitor( Writer writer )
417     {
418         if ( "graphml".equals( outputType ) )
419         {
420             return new GraphmlDependencyNodeVisitor( writer );
421         }
422         else if ( "tgf".equals( outputType ) )
423         {
424             return new TGFDependencyNodeVisitor( writer );
425         }
426         else if ( "dot".equals( outputType ) )
427         {
428             return new DOTDependencyNodeVisitor( writer );
429         }
430         else
431         {
432             return new SerializingDependencyNodeVisitor( writer, toGraphTokens( tokens ) );
433         }
434     }
435 
436     /**
437      * Gets the graph tokens instance for the specified name.
438      *
439      * @param tokens the graph tokens name
440      * @return the <code>GraphTokens</code> instance
441      */
442     private GraphTokens toGraphTokens( String tokens )
443     {
444         GraphTokens graphTokens;
445 
446         if ( "whitespace".equals( tokens ) )
447         {
448             getLog().debug( "+ Using whitespace tree tokens" );
449 
450             graphTokens = SerializingDependencyNodeVisitor.WHITESPACE_TOKENS;
451         }
452         else if ( "extended".equals( tokens ) )
453         {
454             getLog().debug( "+ Using extended tree tokens" );
455 
456             graphTokens = SerializingDependencyNodeVisitor.EXTENDED_TOKENS;
457         }
458         else
459         {
460             graphTokens = SerializingDependencyNodeVisitor.STANDARD_TOKENS;
461         }
462 
463         return graphTokens;
464     }
465 
466     /**
467      * Gets the tree tokens instance for the specified name.
468      *
469      * @param tokens the tree tokens name
470      * @return the <code>TreeTokens</code> instance
471      */
472     private TreeTokens toTreeTokens( String tokens )
473     {
474         TreeTokens treeTokens;
475 
476         if ( "whitespace".equals( tokens ) )
477         {
478             getLog().debug( "+ Using whitespace tree tokens" );
479 
480             treeTokens =
481                 org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.WHITESPACE_TOKENS;
482         }
483         else if ( "extended".equals( tokens ) )
484         {
485             getLog().debug( "+ Using extended tree tokens" );
486 
487             treeTokens =
488                 org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.EXTENDED_TOKENS;
489         }
490         else
491         {
492             treeTokens =
493                 org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.STANDARD_TOKENS;
494         }
495 
496         return treeTokens;
497     }
498 
499     /**
500      * Gets the dependency node filter to use when serializing the dependency graph.
501      *
502      * @return the dependency node filter, or <code>null</code> if none required
503      */
504     private DependencyNodeFilter createDependencyNodeFilter()
505     {
506         List<DependencyNodeFilter> filters = new ArrayList<DependencyNodeFilter>();
507 
508         // filter includes
509         if ( includes != null )
510         {
511             List<String> patterns = Arrays.asList( includes.split( "," ) );
512 
513             getLog().debug( "+ Filtering dependency tree by artifact include patterns: " + patterns );
514 
515             ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter( patterns );
516             filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
517         }
518 
519         // filter excludes
520         if ( excludes != null )
521         {
522             List<String> patterns = Arrays.asList( excludes.split( "," ) );
523 
524             getLog().debug( "+ Filtering dependency tree by artifact exclude patterns: " + patterns );
525 
526             ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter( patterns );
527             filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
528         }
529 
530         return filters.isEmpty() ? null : new AndDependencyNodeFilter( filters );
531     }
532 
533     /**
534      * Gets the dependency node filter to use when serializing the dependency tree.
535      *
536      * @return the dependency node filter, or <code>null</code> if none required
537      */
538     private org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter createVerboseDependencyNodeFilter()
539     {
540         List<org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter> filters =
541             new ArrayList<org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter>();
542 
543         // filter includes
544         if ( includes != null )
545         {
546             List<String> patterns = Arrays.asList( includes.split( "," ) );
547 
548             getLog().debug( "+ Filtering dependency tree by artifact include patterns: " + patterns );
549 
550             ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter( patterns );
551             filters.add( new org.apache.maven.shared.dependency.tree.filter.ArtifactDependencyNodeFilter(
552                                                                                                           artifactFilter ) );
553         }
554 
555         // filter excludes
556         if ( excludes != null )
557         {
558             List<String> patterns = Arrays.asList( excludes.split( "," ) );
559 
560             getLog().debug( "+ Filtering dependency tree by artifact exclude patterns: " + patterns );
561 
562             ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter( patterns );
563             filters.add( new org.apache.maven.shared.dependency.tree.filter.ArtifactDependencyNodeFilter(
564                                                                                                           artifactFilter ) );
565         }
566 
567         return filters.isEmpty() ? null
568                         : new org.apache.maven.shared.dependency.tree.filter.AndDependencyNodeFilter( filters );
569     }
570 
571     //following is required because the version handling in maven code
572     //doesn't work properly. I ripped it out of the enforcer rules.
573 
574 
575     /**
576      * Copied from Artifact.VersionRange. This is tweaked to handle singular ranges properly. Currently the default
577      * containsVersion method assumes a singular version means allow everything. This method assumes that "2.0.4" ==
578      * "[2.0.4,)"
579      *
580      * @param allowedRange range of allowed versions.
581      * @param theVersion   the version to be checked.
582      * @return true if the version is contained by the range.
583      */
584     public static boolean containsVersion( VersionRange allowedRange, ArtifactVersion theVersion )
585     {
586         ArtifactVersion recommendedVersion = allowedRange.getRecommendedVersion();
587         if ( recommendedVersion == null )
588         {
589             @SuppressWarnings( "unchecked" )
590             List<Restriction> restrictions = allowedRange.getRestrictions();
591             for ( Restriction restriction : restrictions )
592             {
593                 if ( restriction.containsVersion( theVersion ) )
594                 {
595                     return true;
596                 }
597             }
598         }
599 
600         // only singular versions ever have a recommendedVersion
601         return recommendedVersion.compareTo( theVersion ) <= 0;
602     }
603 
604 }