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