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