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