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 1400739 2012-10-21 23:05:22Z hboutemy $
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      // Mojo methods -----------------------------------------------------------
82  
83      /*
84       * @see org.apache.maven.plugin.Mojo#execute()
85       */
86      public void execute()
87          throws MojoExecutionException, MojoFailureException
88      {
89          boolean result = checkDependencyManagement();
90          if ( result )
91          {
92              if ( this.failBuild )
93  
94              {
95                  throw new MojoExecutionException( "Found Dependency errors." );
96              }
97              else
98              {
99                  getLog().warn( "Potential problems found in Dependency Management " );
100             }
101         }
102     }
103 
104     /**
105      * Does the work of checking the DependencyManagement Section.
106      *
107      * @return true if errors are found.
108      * @throws MojoExecutionException
109      */
110     private boolean checkDependencyManagement()
111         throws MojoExecutionException
112     {
113         boolean foundError = false;
114 
115         getLog().info( "Found Resolved Dependency / DependencyManagement mismatches:" );
116 
117         List<Dependency> depMgtDependencies = null;
118 
119         DependencyManagement depMgt = project.getDependencyManagement();
120         if ( depMgt != null )
121         {
122             depMgtDependencies = depMgt.getDependencies();
123         }
124 
125         if ( depMgtDependencies != null && !depMgtDependencies.isEmpty() )
126         {
127             // put all the dependencies from depMgt into a map for quick lookup
128             Map<String, Dependency> depMgtMap = new HashMap<String, Dependency>();
129             Map<String, Exclusion> exclusions = new HashMap<String, Exclusion>();
130             for ( Dependency depMgtDependency : depMgtDependencies )
131             {
132                 depMgtMap.put( depMgtDependency.getManagementKey(), depMgtDependency );
133 
134                 // now put all the exclusions into a map for quick lookup
135                 exclusions.putAll( addExclusions( depMgtDependency.getExclusions() ) );
136             }
137 
138             // get dependencies for the project (including transitive)
139             @SuppressWarnings( "unchecked" ) Set<Artifact> allDependencyArtifacts =
140                 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                 @SuppressWarnings( "unchecked" ) 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( StringUtils.stripEnd( getArtifactManagementKey( exclusion ), ":" )
156                                    + " was excluded in DepMgt, but version " + exclusion.getVersion()
157                                    + " has been found in the dependency tree." );
158                 foundError = true;
159             }
160 
161             // find and log version mismatches
162             Map<Artifact, Dependency> mismatch = getMismatch( depMgtMap, allDependencyArtifacts );
163             for ( Map.Entry<Artifact, Dependency> entry : mismatch.entrySet() )
164             {
165                 logMismatch( entry.getKey(), entry.getValue() );
166                 foundError = true;
167             }
168             if ( !foundError )
169             {
170                 getLog().info( "   None" );
171             }
172         }
173         else
174         {
175             getLog().info( "   Nothing in DepMgt." );
176         }
177 
178         return foundError;
179     }
180 
181     /**
182      * Returns a map of the exclusions using the Dependency ManagementKey as the
183      * keyset.
184      *
185      * @param exclusionList to be added to the map.
186      * @return a map of the exclusions using the Dependency ManagementKey as the
187      *         keyset.
188      */
189     public Map<String, Exclusion> addExclusions( List<Exclusion> exclusionList )
190     {
191         Map<String, Exclusion> exclusions = new HashMap<String, Exclusion>();
192         if ( exclusionList != null )
193         {
194             for ( Exclusion exclusion : exclusionList )
195             {
196                 exclusions.put( getExclusionKey( exclusion ), exclusion );
197             }
198         }
199         return exclusions;
200     }
201 
202     /**
203      * Returns a List of the artifacts that should have been excluded, but were
204      * found in the dependency tree.
205      *
206      * @param exclusions             a map of the DependencyManagement exclusions, with the
207      *                               ManagementKey as the key and Dependency as the value.
208      * @param allDependencyArtifacts resolved artifacts to be compared.
209      * @return list of artifacts that should have been excluded.
210      */
211     public List<Artifact> getExclusionErrors( Map<String, Exclusion> exclusions, Set<Artifact> allDependencyArtifacts )
212     {
213         List<Artifact> list = new ArrayList<Artifact>();
214 
215         for ( Artifact artifact : allDependencyArtifacts )
216         {
217             if ( exclusions.containsKey( getExclusionKey( artifact ) ) )
218             {
219                 list.add( artifact );
220             }
221         }
222 
223         return list;
224     }
225 
226     public String getExclusionKey( Artifact artifact )
227     {
228         return artifact.getGroupId() + ":" + artifact.getArtifactId();
229     }
230 
231     public String getExclusionKey( Exclusion ex )
232     {
233         return ex.getGroupId() + ":" + ex.getArtifactId();
234     }
235 
236     /**
237      * Calculate the mismatches between the DependencyManagement and resolved
238      * artifacts
239      *
240      * @param depMgtMap              contains the Dependency.GetManagementKey as the keyset for
241      *                               quick lookup.
242      * @param allDependencyArtifacts contains the set of all artifacts to compare.
243      * @return a map containing the resolved artifact as the key and the listed
244      *         dependency as the value.
245      */
246     public Map<Artifact, Dependency> getMismatch( Map<String, Dependency> depMgtMap,
247                                                   Set<Artifact> allDependencyArtifacts )
248     {
249         Map<Artifact, Dependency> mismatchMap = new HashMap<Artifact, Dependency>();
250 
251         for ( Artifact dependencyArtifact : allDependencyArtifacts )
252         {
253             Dependency depFromDepMgt = depMgtMap.get( getArtifactManagementKey( dependencyArtifact ) );
254             if ( depFromDepMgt != null )
255             {
256                 //workaround for MNG-2961
257                 dependencyArtifact.isSnapshot();
258 
259                 if ( !depFromDepMgt.getVersion().equals( dependencyArtifact.getBaseVersion() ) )
260                 {
261                     mismatchMap.put( dependencyArtifact, depFromDepMgt );
262                 }
263             }
264         }
265         return mismatchMap;
266     }
267 
268     /**
269      * This function displays the log to the screen showing the versions and
270      * information about the artifacts that don't match.
271      *
272      * @param dependencyArtifact   the artifact that was resolved.
273      * @param dependencyFromDepMgt the dependency listed in the DependencyManagement section.
274      * @throws MojoExecutionException
275      */
276     public void logMismatch( Artifact dependencyArtifact, Dependency dependencyFromDepMgt )
277         throws MojoExecutionException
278     {
279         if ( dependencyArtifact == null || dependencyFromDepMgt == null )
280         {
281             throw new MojoExecutionException(
282                 "Invalid params: Artifact:" + dependencyArtifact + " Dependency:" + dependencyFromDepMgt );
283         }
284 
285         getLog().info( "\tDependency: " + StringUtils.stripEnd( dependencyFromDepMgt.getManagementKey(), ":" ) );
286         getLog().info( "\t\tDepMgt  : " + dependencyFromDepMgt.getVersion() );
287         getLog().info( "\t\tResolved: " + dependencyArtifact.getBaseVersion() );
288     }
289 
290     /**
291      * This function returns a string comparable with Dependency.GetManagementKey.
292      *
293      * @param artifact to gen the key for
294      * @return a string in the form: groupId:ArtifactId:Type[:Classifier]
295      */
296     public String getArtifactManagementKey( Artifact artifact )
297     {
298         return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + ( (
299             artifact.getClassifier() != null ) ? ":" + artifact.getClassifier() : "" );
300     }
301 
302     /**
303      * @return the failBuild
304      */
305     public boolean isFailBuild()
306     {
307         return this.failBuild;
308     }
309 
310     /**
311      * @param theFailBuild the failBuild to set
312      */
313     public void setFailBuild( boolean theFailBuild )
314     {
315         this.failBuild = theFailBuild;
316     }
317 
318     /**
319      * @return the project
320      */
321     public MavenProject getProject()
322     {
323         return this.project;
324     }
325 
326     /**
327      * @param theProject the project to set
328      */
329     public void setProject( MavenProject theProject )
330     {
331         this.project = theProject;
332     }
333 
334     /**
335      * @return the ignoreDirect
336      */
337     public boolean isIgnoreDirect()
338     {
339         return this.ignoreDirect;
340     }
341 
342     /**
343      * @param theIgnoreDirect the ignoreDirect to set
344      */
345     public void setIgnoreDirect( boolean theIgnoreDirect )
346     {
347         this.ignoreDirect = theIgnoreDirect;
348     }
349 }