View Javadoc

1   package org.apache.maven.plugin.dependency;
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.resolver.filter.ArtifactFilter;
23  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
24  import org.apache.maven.artifact.versioning.ArtifactVersion;
25  import org.apache.maven.artifact.versioning.Restriction;
26  import org.apache.maven.artifact.versioning.VersionRange;
27  import org.apache.maven.plugin.AbstractMojo;
28  import org.apache.maven.plugin.MojoExecutionException;
29  import org.apache.maven.plugin.MojoFailureException;
30  import org.apache.maven.plugin.dependency.treeSerializers.DOTDependencyNodeVisitor;
31  import org.apache.maven.plugin.dependency.treeSerializers.GraphmlDependencyNodeVisitor;
32  import org.apache.maven.plugin.dependency.treeSerializers.TGFDependencyNodeVisitor;
33  import org.apache.maven.plugin.dependency.utils.DependencyUtil;
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.project.MavenProject;
39  import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
40  import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
41  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
42  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
43  import org.apache.maven.shared.dependency.graph.DependencyNode;
44  import org.apache.maven.shared.dependency.graph.filter.AncestorOrSelfDependencyNodeFilter;
45  import org.apache.maven.shared.dependency.graph.filter.AndDependencyNodeFilter;
46  import org.apache.maven.shared.dependency.graph.filter.ArtifactDependencyNodeFilter;
47  import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
48  import org.apache.maven.shared.dependency.graph.traversal.BuildingDependencyNodeVisitor;
49  import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
50  import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
51  import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
52  import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor;
53  import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor.TreeTokens;
54  
55  import java.io.File;
56  import java.io.IOException;
57  import java.io.StringWriter;
58  import java.io.Writer;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.List;
62  
63  /**
64   * Displays the dependency tree for this project.
65   *
66   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
67   * @version $Id: TreeMojo.java 1451088 2013-02-28 04:22:41Z brianf $
68   * @since 2.0-alpha-5
69   */
70  @Mojo( name = "tree", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
71  public class TreeMojo
72      extends AbstractMojo
73  {
74      // fields -----------------------------------------------------------------
75  
76      /**
77       * The Maven project.
78       */
79      @Component
80      private MavenProject project;
81  
82      /**
83       * The dependency tree builder to use.
84       */
85      @Component( hint = "default" )
86      private DependencyGraphBuilder dependencyGraphBuilder;
87  
88      /**
89       * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of
90       * writing to the console.
91       *
92       * @deprecated use outputFile instead.
93       */
94      @Parameter( property = "output" )
95      private File output;
96  
97      /**
98       * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of
99       * writing to the console.
100      *
101      * @since 2.0-alpha-5
102      */
103     @Parameter( property = "outputFile" )
104     private File outputFile;
105 
106     /**
107      * If specified, this parameter will cause the dependency tree to be written using the specified format. Currently
108      * supported format are text, dot, graphml and tgf.
109      * <p/>
110      * These formats can be plotted to image files. An example of how to plot a dot file using
111      * pygraphviz can be found <a href="http://networkx.lanl.gov/pygraphviz/tutorial.html#layout-and-drawing">here</a>
112      *
113      * @since 2.2
114      */
115     @Parameter( property = "outputType", defaultValue = "text" )
116     private String outputType;
117 
118     /**
119      * The scope to filter by when resolving the dependency tree, or <code>null</code> to include dependencies from
120      * all scopes. Note that this feature does not currently work due to MNG-3236.
121      *
122      * @see <a href="http://jira.codehaus.org/browse/MNG-3236">MNG-3236</a>
123      * @since 2.0-alpha-5
124      */
125     @Parameter( property = "scope" )
126     private String scope;
127 
128     /**
129      * Whether to include omitted nodes in the serialized dependency tree.
130      *
131      * @since 2.0-alpha-6
132      * @deprecated in 2.5
133      */
134     @Parameter( property = "verbose", defaultValue = "false" )
135     private boolean verbose;
136 
137     /**
138      * The token set name to use when outputting the dependency tree. Possible values are <code>whitespace</code>,
139      * <code>standard</code> or <code>extended</code>, which use whitespace, standard or extended ASCII sets
140      * respectively.
141      *
142      * @since 2.0-alpha-6
143      */
144     @Parameter( property = "tokens", defaultValue = "standard" )
145     private String tokens;
146 
147     /**
148      * A comma-separated list of artifacts to filter the serialized dependency tree by, or <code>null</code> not to
149      * filter the dependency tree. The artifact syntax is defined by <code>StrictPatternIncludesArtifactFilter</code>.
150      *
151      * @see StrictPatternIncludesArtifactFilter
152      * @since 2.0-alpha-6
153      */
154     @Parameter( property = "includes" )
155     private String includes;
156 
157     /**
158      * A comma-separated list of artifacts to filter from the serialized dependency tree, or <code>null</code> not to
159      * filter any artifacts from the dependency tree. The artifact syntax is defined by
160      * <code>StrictPatternExcludesArtifactFilter</code>.
161      *
162      * @see StrictPatternExcludesArtifactFilter
163      * @since 2.0-alpha-6
164      */
165     @Parameter( property = "excludes" )
166     private String excludes;
167 
168     /**
169      * The computed dependency tree root node of the Maven project.
170      */
171     private DependencyNode rootNode;
172 
173     /**
174      * Whether to append outputs into the output file or overwrite it.
175      *
176      * @since 2.2
177      */
178     @Parameter( property = "appendOutput", defaultValue = "false" )
179     private boolean appendOutput;
180 
181     /**
182      * Skip plugin execution completely.
183      *
184      * @since 2.7
185      */
186     @Parameter( property = "skip", defaultValue = "false" )
187     private boolean skip;
188 
189 
190     // Mojo methods -----------------------------------------------------------
191 
192     /*
193      * @see org.apache.maven.plugin.Mojo#execute()
194      */
195     public void execute()
196         throws MojoExecutionException, MojoFailureException
197     {
198         if ( isSkip() )
199         {
200             getLog().info( "Skipping plugin execution" );
201             return;
202         }
203 
204         if ( output != null )
205         {
206             getLog().warn( "The parameter output is deprecated. Use outputFile instead." );
207             this.outputFile = output;
208         }
209 
210         ArtifactFilter artifactFilter = createResolvingArtifactFilter();
211 
212         try
213         {
214             // TODO: note that filter does not get applied due to MNG-3236
215 
216             rootNode = dependencyGraphBuilder.buildDependencyGraph( project, artifactFilter );
217 
218             String dependencyTreeString = serializeDependencyTree( rootNode );
219 
220             if ( outputFile != null )
221             {
222                 DependencyUtil.write( dependencyTreeString, outputFile, this.appendOutput, getLog() );
223 
224                 getLog().info( "Wrote dependency tree to: " + outputFile );
225             }
226             else
227             {
228                 DependencyUtil.log( dependencyTreeString, getLog() );
229             }
230         }
231         catch ( DependencyGraphBuilderException exception )
232         {
233             throw new MojoExecutionException( "Cannot build project dependency graph", exception );
234         }
235         catch ( IOException exception )
236         {
237             throw new MojoExecutionException( "Cannot serialise project dependency graph", exception );
238         }
239     }
240 
241     // public methods ---------------------------------------------------------
242 
243     /**
244      * Gets the Maven project used by this mojo.
245      *
246      * @return the Maven project
247      */
248     public MavenProject getProject()
249     {
250         return project;
251     }
252 
253     /**
254      * Gets the computed dependency graph root node for the Maven project.
255      *
256      * @return the dependency tree root node
257      */
258     public DependencyNode getDependencyGraph()
259     {
260         return rootNode;
261     }
262 
263     public boolean isSkip()
264     {
265         return skip;
266     }
267 
268     public void setSkip( boolean skip )
269     {
270         this.skip = skip;
271     }
272 
273     // private methods --------------------------------------------------------
274 
275     /**
276      * Gets the artifact filter to use when resolving the dependency tree.
277      *
278      * @return the artifact filter
279      */
280     private ArtifactFilter createResolvingArtifactFilter()
281     {
282         ArtifactFilter filter;
283 
284         // filter scope
285         if ( scope != null )
286         {
287             getLog().debug( "+ Resolving dependency tree for scope '" + scope + "'" );
288 
289             filter = new ScopeArtifactFilter( scope );
290         }
291         else
292         {
293             filter = null;
294         }
295 
296         return filter;
297     }
298 
299     /**
300      * Serializes the specified dependency tree to a string.
301      *
302      * @param rootNode the dependency tree root node to serialize
303      * @return the serialized dependency tree
304      */
305     private String serializeDependencyTree( DependencyNode rootNode )
306     {
307         StringWriter writer = new StringWriter();
308 
309         DependencyNodeVisitor visitor = getSerializingDependencyNodeVisitor( writer );
310 
311         // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
312         visitor = new BuildingDependencyNodeVisitor( visitor );
313 
314         DependencyNodeFilter filter = createDependencyNodeFilter();
315 
316         if ( filter != null )
317         {
318             CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
319             DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor( collectingVisitor, filter );
320             rootNode.accept( firstPassVisitor );
321 
322             DependencyNodeFilter secondPassFilter =
323                 new AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
324             visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
325         }
326 
327         rootNode.accept( visitor );
328 
329         return writer.toString();
330     }
331 
332     public DependencyNodeVisitor getSerializingDependencyNodeVisitor( Writer writer )
333     {
334         if ( "graphml".equals( outputType ) )
335         {
336             return new GraphmlDependencyNodeVisitor( writer );
337         }
338         else if ( "tgf".equals( outputType ) )
339         {
340             return new TGFDependencyNodeVisitor( writer );
341         }
342         else if ( "dot".equals( outputType ) )
343         {
344             return new DOTDependencyNodeVisitor( writer );
345         }
346         else
347         {
348             return new SerializingDependencyNodeVisitor( writer, toTreeTokens( tokens ) );
349         }
350     }
351 
352     /**
353      * Gets the tree tokens instance for the specified name.
354      *
355      * @param tokens the tree tokens name
356      * @return the <code>TreeTokens</code> instance
357      */
358     private TreeTokens toTreeTokens( String tokens )
359     {
360         TreeTokens treeTokens;
361 
362         if ( "whitespace".equals( tokens ) )
363         {
364             getLog().debug( "+ Using whitespace tree tokens" );
365 
366             treeTokens = SerializingDependencyNodeVisitor.WHITESPACE_TOKENS;
367         }
368         else if ( "extended".equals( tokens ) )
369         {
370             getLog().debug( "+ Using extended tree tokens" );
371 
372             treeTokens = SerializingDependencyNodeVisitor.EXTENDED_TOKENS;
373         }
374         else
375         {
376             treeTokens = SerializingDependencyNodeVisitor.STANDARD_TOKENS;
377         }
378 
379         return treeTokens;
380     }
381 
382     /**
383      * Gets the dependency node filter to use when serializing the dependency tree.
384      *
385      * @return the dependency node filter, or <code>null</code> if none required
386      */
387     private DependencyNodeFilter createDependencyNodeFilter()
388     {
389         List<DependencyNodeFilter> filters = new ArrayList<DependencyNodeFilter>();
390 
391         // filter node states
392         /*if ( !verbose )
393         {
394             getLog().debug( "+ Filtering omitted nodes from dependency tree" );
395 
396             filters.add( StateDependencyNodeFilter.INCLUDED );
397         }*/
398 
399         // filter includes
400         if ( includes != null )
401         {
402             List<String> patterns = Arrays.asList( includes.split( "," ) );
403 
404             getLog().debug( "+ Filtering dependency tree by artifact include patterns: " + patterns );
405 
406             ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter( patterns );
407             filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
408         }
409 
410         // filter excludes
411         if ( excludes != null )
412         {
413             List<String> patterns = Arrays.asList( excludes.split( "," ) );
414 
415             getLog().debug( "+ Filtering dependency tree by artifact exclude patterns: " + patterns );
416 
417             ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter( patterns );
418             filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
419         }
420 
421         return filters.isEmpty() ? null : new AndDependencyNodeFilter( filters );
422     }
423 
424     //following is required because the version handling in maven code
425     //doesn't work properly. I ripped it out of the enforcer rules.
426 
427 
428     /**
429      * Copied from Artifact.VersionRange. This is tweaked to handle singular ranges properly. Currently the default
430      * containsVersion method assumes a singular version means allow everything. This method assumes that "2.0.4" ==
431      * "[2.0.4,)"
432      *
433      * @param allowedRange range of allowed versions.
434      * @param theVersion   the version to be checked.
435      * @return true if the version is contained by the range.
436      */
437     public static boolean containsVersion( VersionRange allowedRange, ArtifactVersion theVersion )
438     {
439         ArtifactVersion recommendedVersion = allowedRange.getRecommendedVersion();
440         if ( recommendedVersion == null )
441         {
442             @SuppressWarnings ("unchecked") List<Restriction> restrictions = allowedRange.getRestrictions();
443             for ( Restriction restriction : restrictions )
444             {
445                 if ( restriction.containsVersion( theVersion ) )
446                 {
447                     return true;
448                 }
449             }
450         }
451 
452         // only singular versions ever have a recommendedVersion
453         return recommendedVersion.compareTo( theVersion ) <= 0;
454     }
455 
456 }