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