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