View Javadoc
1   package org.apache.maven.plugin.dependency.analyze;
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.StringWriter;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Set;
30  
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.maven.artifact.Artifact;
33  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
34  import org.apache.maven.plugin.AbstractMojo;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugin.MojoFailureException;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.project.MavenProject;
39  import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
40  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
41  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer;
42  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException;
43  import org.codehaus.plexus.PlexusConstants;
44  import org.codehaus.plexus.PlexusContainer;
45  import org.codehaus.plexus.context.Context;
46  import org.codehaus.plexus.context.ContextException;
47  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
48  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
49  
50  /**
51   * Analyzes the dependencies of this project and determines which are: used and declared; used and undeclared; unused
52   * and declared.
53   *
54   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
55   * @version $Id: AbstractAnalyzeMojo.html 937155 2015-01-21 21:53:50Z khmarbaise $
56   * @since 2.0-alpha-5
57   */
58  public abstract class AbstractAnalyzeMojo
59      extends AbstractMojo
60      implements Contextualizable
61  {
62      // fields -----------------------------------------------------------------
63  
64      /**
65       * The plexus context to look-up the right {@link ProjectDependencyAnalyzer} implementation depending on the mojo
66       * configuration.
67       */
68      private Context context;
69  
70      /**
71       * The Maven project to analyze.
72       */
73      @Parameter( defaultValue = "${project}", readonly = true, required = true )
74      private MavenProject project;
75  
76      /**
77       * Specify the project dependency analyzer to use (plexus component role-hint).
78       * By default, <a href="/shared/maven-dependency-analyzer/">maven-dependency-analyzer</a> is used.
79       *
80       * To use this, you must declare a dependency for this plugin that contains the code for the
81       * analyzer. The analyzer must have a declared Plexus role name, and you specify the role name
82       * here.
83       *
84       * @since 2.2
85       */
86      @Parameter( property = "analyzer", defaultValue = "default" )
87      private String analyzer;
88  
89      /**
90       * Whether to fail the build if a dependency warning is found.
91       */
92      @Parameter( property = "failOnWarning", defaultValue = "false" )
93      private boolean failOnWarning;
94  
95      /**
96       * Output used dependencies.
97       */
98      @Parameter( property = "verbose", defaultValue = "false" )
99      private boolean verbose;
100 
101     /**
102      * Ignore Runtime/Provided/Test/System scopes for unused dependency analysis.
103      */
104     @Parameter( property = "ignoreNonCompile", defaultValue = "false" )
105     private boolean ignoreNonCompile;
106 
107     /**
108      * Output the xml for the missing dependencies (used but not declared).
109      *
110      * @since 2.0-alpha-5
111      */
112     @Parameter( property = "outputXML", defaultValue = "false" )
113     private boolean outputXML;
114 
115     /**
116      * Output scriptable values for the missing dependencies (used but not declared).
117      *
118      * @since 2.0-alpha-5
119      */
120     @Parameter( property = "scriptableOutput", defaultValue = "false" )
121     private boolean scriptableOutput;
122 
123     /**
124      * Flag to use for scriptable output.
125      *
126      * @since 2.0-alpha-5
127      */
128     @Parameter( property = "scriptableFlag", defaultValue = "$$$%%%" )
129     private String scriptableFlag;
130 
131     /**
132      * Flag to use for scriptable output
133      *
134      * @since 2.0-alpha-5
135      */
136     @Parameter( defaultValue = "${basedir}", readonly = true )
137     private File baseDir;
138 
139     /**
140      * Target folder
141      *
142      * @since 2.0-alpha-5
143      */
144     @Parameter( defaultValue = "${project.build.directory}", readonly = true )
145     private File outputDirectory;
146 
147     /**
148      * Force dependencies as used, to override incomplete result caused by bytecode-level analysis.
149      * Dependency format is <code>groupId:artifactId</code>.
150      *
151      * @since 2.6
152      */
153     @Parameter
154     private String[] usedDependencies;
155 
156     /**
157      * Skip plugin execution completely.
158      *
159      * @since 2.7
160      */
161     @Parameter( property = "mdep.analyze.skip", defaultValue = "false" )
162     private boolean skip;
163 
164     /**
165      * List of dependencies that will be ignored.
166      *
167      * Any dependency on this list will be excluded from the "declared but unused" and the "used but undeclared" list.
168      *
169      * The filter syntax is:
170      *
171      * <pre>
172      * [groupId]:[artifactId]:[type]:[version]
173      * </pre>
174      *
175      * where each pattern segment is optional and supports full and partial <code>*</code> wildcards. An empty pattern
176      * segment is treated as an implicit wildcard.
177      * *
178      * <p>For example, <code>org.apache.*</code> will match all artifacts whose group id starts with
179      * <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> will match all snapshot artifacts.</p>
180      *
181      * @since 2.10
182      * @see StrictPatternIncludesArtifactFilter
183      */
184     @Parameter
185     private String [] ignoredDependencies = new String[0];
186 
187     /**
188      * List of dependencies that will be ignored if they are used but undeclared.
189      *
190      * The filter syntax is:
191      *
192      * <pre>
193      * [groupId]:[artifactId]:[type]:[version]
194      * </pre>
195      *
196      * where each pattern segment is optional and supports full and partial <code>*</code> wildcards. An empty pattern
197      * segment is treated as an implicit wildcard.
198      * *
199      * <p>For example, <code>org.apache.*</code> will match all artifacts whose group id starts with
200      * <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> will match all snapshot artifacts.</p>
201      *
202      * @since 2.10
203      * @see StrictPatternIncludesArtifactFilter
204      */
205     @Parameter
206     private String [] ignoredUsedUndeclaredDependencies = new String[0];
207 
208     /**
209      * List of dependencies that will be ignored if they are declared but unused.
210      *
211      * The filter syntax is:
212      *
213      * <pre>
214      * [groupId]:[artifactId]:[type]:[version]
215      * </pre>
216      *
217      * where each pattern segment is optional and supports full and partial <code>*</code> wildcards. An empty pattern
218      * segment is treated as an implicit wildcard.
219      * *
220      * <p>For example, <code>org.apache.*</code> will match all artifacts whose group id starts with
221      * <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> will match all snapshot artifacts.</p>
222      *
223      * @since 2.10
224      * @see StrictPatternIncludesArtifactFilter
225      */
226     @Parameter
227     private String [] ignoredUnusedDeclaredDependencies = new String[0];
228 
229 
230     // Mojo methods -----------------------------------------------------------
231 
232     /*
233      * @see org.apache.maven.plugin.Mojo#execute()
234      */
235     public void execute()
236         throws MojoExecutionException, MojoFailureException
237     {
238         if ( isSkip() )
239         {
240             getLog().info( "Skipping plugin execution" );
241             return;
242         }
243 
244         if ( "pom".equals( project.getPackaging() ) )
245         {
246             getLog().info( "Skipping pom project" );
247             return;
248         }
249 
250         if ( outputDirectory == null || !outputDirectory.exists() )
251         {
252             getLog().info( "Skipping project with no build directory" );
253             return;
254         }
255 
256         boolean warning = checkDependencies();
257 
258         if ( warning && failOnWarning )
259         {
260             throw new MojoExecutionException( "Dependency problems found" );
261         }
262     }
263 
264     protected ProjectDependencyAnalyzer createProjectDependencyAnalyzer()
265         throws MojoExecutionException
266     {
267 
268         final String role = ProjectDependencyAnalyzer.ROLE;
269         final String roleHint = analyzer;
270 
271         try
272         {
273             final PlexusContainer container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
274 
275             return (ProjectDependencyAnalyzer) container.lookup( role, roleHint );
276         }
277         catch ( Exception exception )
278         {
279             throw new MojoExecutionException(
280                 "Failed to instantiate ProjectDependencyAnalyser with role " + role + " / role-hint " + roleHint,
281                 exception );
282         }
283     }
284 
285     public void contextualize( Context context )
286         throws ContextException
287     {
288         this.context = context;
289     }
290 
291     public boolean isSkip()
292     {
293         return skip;
294     }
295 
296     public void setSkip( boolean skip )
297     {
298         this.skip = skip;
299     }
300 
301     // private methods --------------------------------------------------------
302 
303     private boolean checkDependencies()
304         throws MojoExecutionException
305     {
306         ProjectDependencyAnalysis analysis;
307         try
308         {
309             analysis = createProjectDependencyAnalyzer().analyze( project );
310 
311             if ( usedDependencies != null )
312             {
313                 analysis = analysis.forceDeclaredDependenciesUsage( usedDependencies );
314             }
315         }
316         catch ( ProjectDependencyAnalyzerException exception )
317         {
318             throw new MojoExecutionException( "Cannot analyze dependencies", exception );
319         }
320 
321         if ( ignoreNonCompile )
322         {
323             analysis = analysis.ignoreNonCompile();
324         }
325 
326         Set<Artifact> usedDeclared = new HashSet<Artifact>( analysis.getUsedDeclaredArtifacts() );
327         Set<Artifact> usedUndeclared = new HashSet<Artifact>( analysis.getUsedUndeclaredArtifacts() );
328         Set<Artifact> unusedDeclared = new HashSet<Artifact>( analysis.getUnusedDeclaredArtifacts() );
329 
330         Set<Artifact> ignoredUsedUndeclared = new HashSet<Artifact>();
331         Set<Artifact> ignoredUnusedDeclared = new HashSet<Artifact>();
332 
333         ignoredUsedUndeclared.addAll( filterDependencies( usedUndeclared, ignoredDependencies ) );
334         ignoredUsedUndeclared.addAll( filterDependencies( usedUndeclared, ignoredUsedUndeclaredDependencies ) );
335 
336         ignoredUnusedDeclared.addAll( filterDependencies( unusedDeclared, ignoredDependencies ) );
337         ignoredUnusedDeclared.addAll( filterDependencies( unusedDeclared, ignoredUnusedDeclaredDependencies ) );
338 
339         boolean reported = false;
340         boolean warning = false;
341 
342 
343         if ( verbose && !usedDeclared.isEmpty() )
344         {
345             getLog().info( "Used declared dependencies found:" );
346 
347             logArtifacts( analysis.getUsedDeclaredArtifacts(), false );
348             reported = true;
349         }
350 
351         if ( !usedUndeclared.isEmpty() )
352         {
353             getLog().warn( "Used undeclared dependencies found:" );
354 
355             logArtifacts( usedUndeclared, true );
356             reported = true;
357             warning = true;
358         }
359 
360         if ( !unusedDeclared.isEmpty() )
361         {
362             getLog().warn( "Unused declared dependencies found:" );
363 
364             logArtifacts( unusedDeclared, true );
365             reported = true;
366             warning = true;
367         }
368 
369         if ( verbose && !ignoredUsedUndeclared.isEmpty() )
370         {
371             getLog().info( "Ignored used undeclared dependencies:" );
372 
373             logArtifacts( ignoredUsedUndeclared, false );
374             reported = true;
375         }
376 
377         if ( verbose && !ignoredUnusedDeclared.isEmpty() )
378         {
379             getLog().info( "Ignored unused declared dependencies:" );
380 
381             logArtifacts( ignoredUnusedDeclared, false );
382             reported = true;
383         }
384 
385         if ( outputXML )
386         {
387             writeDependencyXML( usedUndeclared );
388         }
389 
390         if ( scriptableOutput )
391         {
392             writeScriptableOutput( usedUndeclared );
393         }
394 
395         if ( !reported )
396         {
397             getLog().info( "No dependency problems found" );
398         }
399 
400         return warning;
401     }
402 
403     private void logArtifacts( Set<Artifact> artifacts, boolean warn )
404     {
405         if ( artifacts.isEmpty() )
406         {
407             getLog().info( "   None" );
408         }
409         else
410         {
411             for ( Artifact artifact : artifacts )
412             {
413                 // called because artifact will set the version to -SNAPSHOT only if I do this. MNG-2961
414                 artifact.isSnapshot();
415 
416                 if ( warn )
417                 {
418                     getLog().warn( "   " + artifact );
419                 }
420                 else
421                 {
422                     getLog().info( "   " + artifact );
423                 }
424 
425             }
426         }
427     }
428 
429     private void writeDependencyXML( Set<Artifact> artifacts )
430     {
431         if ( !artifacts.isEmpty() )
432         {
433             getLog().info( "Add the following to your pom to correct the missing dependencies: " );
434 
435             StringWriter out = new StringWriter();
436             PrettyPrintXMLWriter writer = new PrettyPrintXMLWriter( out );
437 
438             for ( Artifact artifact : artifacts )
439             {
440                 // called because artifact will set the version to -SNAPSHOT only if I do this. MNG-2961
441                 artifact.isSnapshot();
442 
443                 writer.startElement( "dependency" );
444                 writer.startElement( "groupId" );
445                 writer.writeText( artifact.getGroupId() );
446                 writer.endElement();
447                 writer.startElement( "artifactId" );
448                 writer.writeText( artifact.getArtifactId() );
449                 writer.endElement();
450                 writer.startElement( "version" );
451                 writer.writeText( artifact.getBaseVersion() );
452                 if ( !StringUtils.isBlank( artifact.getClassifier() ) )
453                 {
454                     writer.startElement( "classifier" );
455                     writer.writeText( artifact.getClassifier() );
456                     writer.endElement();
457                 }
458                 writer.endElement();
459 
460                 if ( !Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) )
461                 {
462                     writer.startElement( "scope" );
463                     writer.writeText( artifact.getScope() );
464                     writer.endElement();
465                 }
466                 writer.endElement();
467             }
468 
469             getLog().info( "\n" + out.getBuffer() );
470         }
471     }
472 
473     private void writeScriptableOutput( Set<Artifact> artifacts )
474     {
475         if ( !artifacts.isEmpty() )
476         {
477             getLog().info( "Missing dependencies: " );
478             String pomFile = baseDir.getAbsolutePath() + File.separatorChar + "pom.xml";
479             StringBuilder buf = new StringBuilder();
480 
481             for ( Artifact artifact : artifacts )
482             {
483                 // called because artifact will set the version to -SNAPSHOT only if I do this. MNG-2961
484                 artifact.isSnapshot();
485 
486                 buf.append( scriptableFlag ).append( ":" ).append( pomFile ).append( ":" )
487                    .append( artifact.getDependencyConflictId() ).append( ":" ).append( artifact.getClassifier() )
488                    .append( ":" ).append( artifact.getBaseVersion() ).append( ":" ).append( artifact.getScope() )
489                    .append( "\n" );
490             }
491             getLog().info( "\n" + buf );
492         }
493     }
494 
495     private List<Artifact> filterDependencies( Set<Artifact> artifacts, String[] excludes )
496         throws MojoExecutionException
497     {
498         ArtifactFilter filter = new StrictPatternExcludesArtifactFilter( Arrays.asList( excludes ) );
499         List<Artifact> result = new ArrayList<Artifact>();
500 
501         for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
502         {
503             Artifact artifact = it.next();
504             if ( !filter.include( artifact ) )
505             {
506                 it.remove();
507                 result.add( artifact );
508             }
509         }
510 
511         return result;
512     }
513 }