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