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.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.model.Dependency;
31  import org.apache.maven.model.DependencyManagement;
32  import org.apache.maven.model.Exclusion;
33  import org.apache.maven.plugin.AbstractMojo;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.MojoFailureException;
36  import org.apache.maven.plugins.annotations.Mojo;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.plugins.annotations.ResolutionScope;
39  import org.apache.maven.project.MavenProject;
40  import org.codehaus.plexus.util.StringUtils;
41  
42  /**
43   * This mojo looks at the dependencies after final resolution and looks for
44   * mismatches in your dependencyManagement section. In versions of maven prior
45   * to 2.0.6, it was possible to inherit versions that didn't match your
46   * dependencyManagement. See <a
47   * href="http://jira.codehaus.org/browse/MNG-1577">MNG-1577</a> for more info.
48   * This mojo is also useful for just detecting projects that override the
49   * dependencyManagement directly. Set ignoreDirect to false to detect these
50   * otherwise normal conditions.
51   *
52   * @author <a href="mailto:brianefox@gmail.com">Brian Fox</a>
53   * @version $Id: AnalyzeDepMgt.html 922735 2014-09-18 19:32:48Z khmarbaise $
54   * @since 2.0-alpha-3
55   */
56  @Mojo( name = "analyze-dep-mgt", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
57  public class AnalyzeDepMgt
58      extends AbstractMojo
59  {
60      // fields -----------------------------------------------------------------
61  
62      /**
63       *
64       */
65      @Parameter( defaultValue = "${project}", readonly = true, required = true )
66      private MavenProject project;
67  
68      /**
69       * Fail the build if a problem is detected.
70       */
71      @Parameter( property = "mdep.analyze.failBuild", defaultValue = "false" )
72      private boolean failBuild = false;
73  
74      /**
75       * Ignore Direct Dependency Overrides of dependencyManagement section.
76       */
77      @Parameter( property = "mdep.analyze.ignore.direct", defaultValue = "true" )
78      private boolean ignoreDirect = true;
79  
80      /**
81       * Skip plugin execution completely.
82       *
83       * @since 2.7
84       */
85      @Parameter( property = "mdep.analyze.skip", defaultValue = "false" )
86      private boolean skip;
87  
88      // Mojo methods -----------------------------------------------------------
89  
90      /*
91       * @see org.apache.maven.plugin.Mojo#execute()
92       */
93      public void execute()
94          throws MojoExecutionException, MojoFailureException
95      {
96          if ( isSkip() )
97          {
98              getLog().info( "Skipping plugin execution" );
99              return;
100         }
101 
102         boolean result = checkDependencyManagement();
103         if ( result )
104         {
105             if ( this.failBuild )
106 
107             {
108                 throw new MojoExecutionException( "Found Dependency errors." );
109             }
110             else
111             {
112                 getLog().warn( "Potential problems found in Dependency Management " );
113             }
114         }
115     }
116 
117     /**
118      * Does the work of checking the DependencyManagement Section.
119      *
120      * @return true if errors are found.
121      * @throws MojoExecutionException
122      */
123     private boolean checkDependencyManagement()
124         throws MojoExecutionException
125     {
126         boolean foundError = false;
127 
128         getLog().info( "Found Resolved Dependency / DependencyManagement mismatches:" );
129 
130         List<Dependency> depMgtDependencies = null;
131 
132         DependencyManagement depMgt = project.getDependencyManagement();
133         if ( depMgt != null )
134         {
135             depMgtDependencies = depMgt.getDependencies();
136         }
137 
138         if ( depMgtDependencies != null && !depMgtDependencies.isEmpty() )
139         {
140             // put all the dependencies from depMgt into a map for quick lookup
141             Map<String, Dependency> depMgtMap = new HashMap<String, Dependency>();
142             Map<String, Exclusion> exclusions = new HashMap<String, Exclusion>();
143             for ( Dependency depMgtDependency : depMgtDependencies )
144             {
145                 depMgtMap.put( depMgtDependency.getManagementKey(), depMgtDependency );
146 
147                 // now put all the exclusions into a map for quick lookup
148                 exclusions.putAll( addExclusions( depMgtDependency.getExclusions() ) );
149             }
150 
151             // get dependencies for the project (including transitive)
152             @SuppressWarnings( "unchecked" ) Set<Artifact> allDependencyArtifacts =
153                 new HashSet<Artifact>( project.getArtifacts() );
154 
155             // don't warn if a dependency that is directly listed overrides
156             // depMgt. That's ok.
157             if ( this.ignoreDirect )
158             {
159                 getLog().info( "\tIgnoring Direct Dependencies." );
160                 @SuppressWarnings( "unchecked" ) Set<Artifact> directDependencies = project.getDependencyArtifacts();
161                 allDependencyArtifacts.removeAll( directDependencies );
162             }
163 
164             // log exclusion errors
165             List<Artifact> exclusionErrors = getExclusionErrors( exclusions, allDependencyArtifacts );
166             for ( Artifact exclusion : exclusionErrors )
167             {
168                 getLog().info( StringUtils.stripEnd( getArtifactManagementKey( exclusion ), ":" )
169                                    + " was excluded in DepMgt, but version " + exclusion.getVersion()
170                                    + " has been found in the dependency tree." );
171                 foundError = true;
172             }
173 
174             // find and log version mismatches
175             Map<Artifact, Dependency> mismatch = getMismatch( depMgtMap, allDependencyArtifacts );
176             for ( Map.Entry<Artifact, Dependency> entry : mismatch.entrySet() )
177             {
178                 logMismatch( entry.getKey(), entry.getValue() );
179                 foundError = true;
180             }
181             if ( !foundError )
182             {
183                 getLog().info( "   None" );
184             }
185         }
186         else
187         {
188             getLog().info( "   Nothing in DepMgt." );
189         }
190 
191         return foundError;
192     }
193 
194     /**
195      * Returns a map of the exclusions using the Dependency ManagementKey as the
196      * keyset.
197      *
198      * @param exclusionList to be added to the map.
199      * @return a map of the exclusions using the Dependency ManagementKey as the
200      *         keyset.
201      */
202     public Map<String, Exclusion> addExclusions( List<Exclusion> exclusionList )
203     {
204         Map<String, Exclusion> exclusions = new HashMap<String, Exclusion>();
205         if ( exclusionList != null )
206         {
207             for ( Exclusion exclusion : exclusionList )
208             {
209                 exclusions.put( getExclusionKey( exclusion ), exclusion );
210             }
211         }
212         return exclusions;
213     }
214 
215     /**
216      * Returns a List of the artifacts that should have been excluded, but were
217      * found in the dependency tree.
218      *
219      * @param exclusions             a map of the DependencyManagement exclusions, with the
220      *                               ManagementKey as the key and Dependency as the value.
221      * @param allDependencyArtifacts resolved artifacts to be compared.
222      * @return list of artifacts that should have been excluded.
223      */
224     public List<Artifact> getExclusionErrors( Map<String, Exclusion> exclusions, Set<Artifact> allDependencyArtifacts )
225     {
226         List<Artifact> list = new ArrayList<Artifact>();
227 
228         for ( Artifact artifact : allDependencyArtifacts )
229         {
230             if ( exclusions.containsKey( getExclusionKey( artifact ) ) )
231             {
232                 list.add( artifact );
233             }
234         }
235 
236         return list;
237     }
238 
239     public String getExclusionKey( Artifact artifact )
240     {
241         return artifact.getGroupId() + ":" + artifact.getArtifactId();
242     }
243 
244     public String getExclusionKey( Exclusion ex )
245     {
246         return ex.getGroupId() + ":" + ex.getArtifactId();
247     }
248 
249     /**
250      * Calculate the mismatches between the DependencyManagement and resolved
251      * artifacts
252      *
253      * @param depMgtMap              contains the Dependency.GetManagementKey as the keyset for
254      *                               quick lookup.
255      * @param allDependencyArtifacts contains the set of all artifacts to compare.
256      * @return a map containing the resolved artifact as the key and the listed
257      *         dependency as the value.
258      */
259     public Map<Artifact, Dependency> getMismatch( Map<String, Dependency> depMgtMap,
260                                                   Set<Artifact> allDependencyArtifacts )
261     {
262         Map<Artifact, Dependency> mismatchMap = new HashMap<Artifact, Dependency>();
263 
264         for ( Artifact dependencyArtifact : allDependencyArtifacts )
265         {
266             Dependency depFromDepMgt = depMgtMap.get( getArtifactManagementKey( dependencyArtifact ) );
267             if ( depFromDepMgt != null )
268             {
269                 //workaround for MNG-2961
270                 dependencyArtifact.isSnapshot();
271 
272                 if ( !depFromDepMgt.getVersion().equals( dependencyArtifact.getBaseVersion() ) )
273                 {
274                     mismatchMap.put( dependencyArtifact, depFromDepMgt );
275                 }
276             }
277         }
278         return mismatchMap;
279     }
280 
281     /**
282      * This function displays the log to the screen showing the versions and
283      * information about the artifacts that don't match.
284      *
285      * @param dependencyArtifact   the artifact that was resolved.
286      * @param dependencyFromDepMgt the dependency listed in the DependencyManagement section.
287      * @throws MojoExecutionException
288      */
289     public void logMismatch( Artifact dependencyArtifact, Dependency dependencyFromDepMgt )
290         throws MojoExecutionException
291     {
292         if ( dependencyArtifact == null || dependencyFromDepMgt == null )
293         {
294             throw new MojoExecutionException(
295                 "Invalid params: Artifact:" + dependencyArtifact + " Dependency:" + dependencyFromDepMgt );
296         }
297 
298         getLog().info( "\tDependency: " + StringUtils.stripEnd( dependencyFromDepMgt.getManagementKey(), ":" ) );
299         getLog().info( "\t\tDepMgt  : " + dependencyFromDepMgt.getVersion() );
300         getLog().info( "\t\tResolved: " + dependencyArtifact.getBaseVersion() );
301     }
302 
303     /**
304      * This function returns a string comparable with Dependency.GetManagementKey.
305      *
306      * @param artifact to gen the key for
307      * @return a string in the form: groupId:ArtifactId:Type[:Classifier]
308      */
309     public String getArtifactManagementKey( Artifact artifact )
310     {
311         return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + ( (
312             artifact.getClassifier() != null ) ? ":" + artifact.getClassifier() : "" );
313     }
314 
315     /**
316      * @return the failBuild
317      */
318     public boolean isFailBuild()
319     {
320         return this.failBuild;
321     }
322 
323     /**
324      * @param theFailBuild the failBuild to set
325      */
326     public void setFailBuild( boolean theFailBuild )
327     {
328         this.failBuild = theFailBuild;
329     }
330 
331     /**
332      * @return the project
333      */
334     public MavenProject getProject()
335     {
336         return this.project;
337     }
338 
339     /**
340      * @param theProject the project to set
341      */
342     public void setProject( MavenProject theProject )
343     {
344         this.project = theProject;
345     }
346 
347     /**
348      * @return the ignoreDirect
349      */
350     public boolean isIgnoreDirect()
351     {
352         return this.ignoreDirect;
353     }
354 
355     /**
356      * @param theIgnoreDirect the ignoreDirect to set
357      */
358     public void setIgnoreDirect( boolean theIgnoreDirect )
359     {
360         this.ignoreDirect = theIgnoreDirect;
361     }
362 
363     public boolean isSkip()
364     {
365         return skip;
366     }
367 
368     public void setSkip( boolean skip )
369     {
370         this.skip = skip;
371     }
372 }