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.commons.lang.StringUtils;
23  import org.apache.maven.artifact.Artifact;
24  import org.apache.maven.plugin.AbstractMojo;
25  import org.apache.maven.plugin.MojoExecutionException;
26  import org.apache.maven.plugin.MojoFailureException;
27  import org.apache.maven.plugins.annotations.Component;
28  import org.apache.maven.plugins.annotations.Parameter;
29  import org.apache.maven.project.MavenProject;
30  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
31  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer;
32  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException;
33  import org.codehaus.plexus.PlexusConstants;
34  import org.codehaus.plexus.PlexusContainer;
35  import org.codehaus.plexus.context.Context;
36  import org.codehaus.plexus.context.ContextException;
37  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
38  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
39  
40  import java.io.File;
41  import java.io.StringWriter;
42  import java.util.HashSet;
43  import java.util.Iterator;
44  import java.util.Set;
45  
46  /**
47   * Analyzes the dependencies of this project and determines which are: used and declared; used and undeclared; unused
48   * and declared.
49   *
50   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
51   * @version $Id: AbstractAnalyzeMojo.java 1357251 2012-07-04 13:28:33Z olamy $
52   * @since 2.0-alpha-5
53   */
54  public abstract class AbstractAnalyzeMojo
55      extends AbstractMojo
56      implements Contextualizable
57  {
58      // fields -----------------------------------------------------------------
59  
60      /**
61       * The plexus context to look-up the right {@link ProjectDependencyAnalyzer} implementation depending on the mojo
62       * configuration.
63       */
64      private Context context;
65  
66      /**
67       * The Maven project to analyze.
68       */
69      @Component
70      private MavenProject project;
71  
72      /**
73       * Project dependency analyzer to use (plexus component role-hint).
74       *
75       * @since 2.2
76       */
77      @Parameter( property = "analyzer", defaultValue = "default" )
78      private String analyzer;
79  
80      /**
81       * Whether to fail the build if a dependency warning is found.
82       */
83      @Parameter( property = "failOnWarning", defaultValue = "false" )
84      private boolean failOnWarning;
85  
86      /**
87       * Output used dependencies
88       */
89      @Parameter( property = "verbose", defaultValue = "false" )
90      private boolean verbose;
91  
92      /**
93       * Ignore Runtime,Provide,Test,System scopes for unused dependency analysis
94       */
95      @Parameter( property = "ignoreNonCompile", defaultValue = "false" )
96      private boolean ignoreNonCompile;
97  
98      /**
99       * Output the xml for the missing dependencies
100      *
101      * @since 2.0-alpha-5
102      */
103     @Parameter( property = "outputXML", defaultValue = "false" )
104     private boolean outputXML;
105 
106     /**
107      * Output scriptable values
108      *
109      * @since 2.0-alpha-5
110      */
111     @Parameter( property = "scriptableOutput", defaultValue = "false" )
112     private boolean scriptableOutput;
113 
114     /**
115      * Flag to use for scriptable output
116      *
117      * @since 2.0-alpha-5
118      */
119     @Parameter( property = "scriptableFlag", defaultValue = "$$$%%%" )
120     private String scriptableFlag;
121 
122     /**
123      * Flag to use for scriptable output
124      *
125      * @since 2.0-alpha-5
126      */
127     @Parameter( defaultValue = "${basedir}", readonly = true )
128     private File baseDir;
129 
130     /**
131      * Target folder
132      *
133      * @since 2.0-alpha-5
134      */
135     @Parameter( defaultValue = "${project.build.directory}", readonly = true )
136     private File outputDirectory;
137 
138     // Mojo methods -----------------------------------------------------------
139 
140     /*
141      * @see org.apache.maven.plugin.Mojo#execute()
142      */
143     public void execute()
144         throws MojoExecutionException, MojoFailureException
145     {
146         if ( "pom".equals( project.getPackaging() ) )
147         {
148             getLog().info( "Skipping pom project" );
149             return;
150         }
151 
152         if ( outputDirectory == null || !outputDirectory.exists() )
153         {
154             getLog().info( "Skipping project with no build directory" );
155             return;
156         }
157 
158         boolean warning = checkDependencies();
159 
160         if ( warning && failOnWarning )
161         {
162             throw new MojoExecutionException( "Dependency problems found" );
163         }
164     }
165 
166     protected ProjectDependencyAnalyzer createProjectDependencyAnalyzer()
167         throws MojoExecutionException
168     {
169 
170         final String role = ProjectDependencyAnalyzer.ROLE;
171         final String roleHint = analyzer;
172 
173         try
174         {
175             final PlexusContainer container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
176 
177             return (ProjectDependencyAnalyzer) container.lookup( role, roleHint );
178         }
179         catch ( Exception exception )
180         {
181             throw new MojoExecutionException(
182                 "Failed to instantiate ProjectDependencyAnalyser with role " + role + " / role-hint " + roleHint,
183                 exception );
184         }
185     }
186 
187     public void contextualize( Context context )
188         throws ContextException
189     {
190         this.context = context;
191     }
192 
193     // private methods --------------------------------------------------------
194 
195     private boolean checkDependencies()
196         throws MojoExecutionException
197     {
198         ProjectDependencyAnalysis analysis;
199         try
200         {
201             analysis = createProjectDependencyAnalyzer().analyze( project );
202         }
203         catch ( ProjectDependencyAnalyzerException exception )
204         {
205             throw new MojoExecutionException( "Cannot analyze dependencies", exception );
206         }
207 
208         @SuppressWarnings( "unchecked" ) Set<Artifact> usedDeclared = analysis.getUsedDeclaredArtifacts();
209         @SuppressWarnings( "unchecked" ) Set<Artifact> usedUndeclared = analysis.getUsedUndeclaredArtifacts();
210         @SuppressWarnings( "unchecked" ) Set<Artifact> unusedDeclared = analysis.getUnusedDeclaredArtifacts();
211 
212         if ( ignoreNonCompile )
213         {
214             Set<Artifact> filteredUnusedDeclared = new HashSet<Artifact>( unusedDeclared );
215             Iterator<Artifact> iter = filteredUnusedDeclared.iterator();
216             while ( iter.hasNext() )
217             {
218                 Artifact artifact = iter.next();
219                 if ( !artifact.getScope().equals( Artifact.SCOPE_COMPILE ) )
220                 {
221                     iter.remove();
222                 }
223             }
224             unusedDeclared = filteredUnusedDeclared;
225         }
226 
227         if ( ( !verbose || usedDeclared.isEmpty() ) && usedUndeclared.isEmpty() && unusedDeclared.isEmpty() )
228         {
229             getLog().info( "No dependency problems found" );
230             return false;
231         }
232 
233         if ( verbose && !usedDeclared.isEmpty() )
234         {
235             getLog().info( "Used declared dependencies found:" );
236 
237             logArtifacts( analysis.getUsedDeclaredArtifacts(), false );
238         }
239 
240         if ( !usedUndeclared.isEmpty() )
241         {
242             getLog().warn( "Used undeclared dependencies found:" );
243 
244             logArtifacts( usedUndeclared, true );
245         }
246 
247         if ( !unusedDeclared.isEmpty() )
248         {
249             getLog().warn( "Unused declared dependencies found:" );
250 
251             logArtifacts( unusedDeclared, true );
252         }
253 
254         if ( outputXML )
255         {
256             writeDependencyXML( usedUndeclared );
257         }
258 
259         if ( scriptableOutput )
260         {
261             writeScriptableOutput( usedUndeclared );
262         }
263 
264         return !usedUndeclared.isEmpty() || !unusedDeclared.isEmpty();
265     }
266 
267     private void logArtifacts( Set<Artifact> artifacts, boolean warn )
268     {
269         if ( artifacts.isEmpty() )
270         {
271             getLog().info( "   None" );
272         }
273         else
274         {
275             for ( Artifact artifact : artifacts )
276             {
277                 // called because artifact will set the version to -SNAPSHOT only if I do this. MNG-2961
278                 artifact.isSnapshot();
279 
280                 if ( warn )
281                 {
282                     getLog().warn( "   " + artifact );
283                 }
284                 else
285                 {
286                     getLog().info( "   " + artifact );
287                 }
288 
289             }
290         }
291     }
292 
293     private void writeDependencyXML( Set<Artifact> artifacts )
294     {
295         if ( !artifacts.isEmpty() )
296         {
297             getLog().info( "Add the following to your pom to correct the missing dependencies: " );
298 
299             StringWriter out = new StringWriter();
300             PrettyPrintXMLWriter writer = new PrettyPrintXMLWriter( out );
301 
302             for ( Artifact artifact : artifacts )
303             {
304                 // called because artifact will set the version to -SNAPSHOT only if I do this. MNG-2961
305                 artifact.isSnapshot();
306 
307                 writer.startElement( "dependency" );
308                 writer.startElement( "groupId" );
309                 writer.writeText( artifact.getGroupId() );
310                 writer.endElement();
311                 writer.startElement( "artifactId" );
312                 writer.writeText( artifact.getArtifactId() );
313                 writer.endElement();
314                 writer.startElement( "version" );
315                 writer.writeText( artifact.getBaseVersion() );
316                 if ( !StringUtils.isBlank( artifact.getClassifier() ) )
317                 {
318                     writer.startElement( "classifier" );
319                     writer.writeText( artifact.getClassifier() );
320                     writer.endElement();
321                 }
322                 writer.endElement();
323 
324                 if ( !Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) )
325                 {
326                     writer.startElement( "scope" );
327                     writer.writeText( artifact.getScope() );
328                     writer.endElement();
329                 }
330                 writer.endElement();
331             }
332 
333             getLog().info( "\n" + out.getBuffer() );
334         }
335     }
336 
337     private void writeScriptableOutput( Set<Artifact> artifacts )
338     {
339         if ( !artifacts.isEmpty() )
340         {
341             getLog().info( "Missing dependencies: " );
342             String pomFile = baseDir.getAbsolutePath() + File.separatorChar + "pom.xml";
343             StringBuffer buf = new StringBuffer();
344 
345             for ( Artifact artifact : artifacts )
346             {
347                 // called because artifact will set the version to -SNAPSHOT only if I do this. MNG-2961
348                 artifact.isSnapshot();
349 
350                 buf.append( scriptableFlag + ":" + pomFile + ":" + artifact.getDependencyConflictId() + ":"
351                                 + artifact.getClassifier() + ":" + artifact.getBaseVersion() + ":" + artifact.getScope()
352                                 + "\n" );
353             }
354             getLog().info( "\n" + buf );
355         }
356     }
357 }