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