1 package org.codehaus.mojo.animal_sniffer.enforcer;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import org.apache.maven.artifact.Artifact;
29 import org.apache.maven.artifact.factory.ArtifactFactory;
30 import org.apache.maven.artifact.repository.ArtifactRepository;
31 import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
32 import org.apache.maven.artifact.resolver.ArtifactResolver;
33 import org.apache.maven.enforcer.rule.api.EnforcerRule;
34 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
35 import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
36 import org.apache.maven.model.Dependency;
37 import org.apache.maven.project.MavenProject;
38 import org.apache.maven.shared.artifact.filter.PatternExcludesArtifactFilter;
39 import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
40 import org.codehaus.mojo.animal_sniffer.ClassFileVisitor;
41 import org.codehaus.mojo.animal_sniffer.ClassListBuilder;
42 import org.codehaus.mojo.animal_sniffer.SignatureChecker;
43 import org.codehaus.mojo.animal_sniffer.logging.Logger;
44 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
45 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
46 import org.codehaus.plexus.util.StringUtils;
47
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.List;
56 import java.util.Random;
57 import java.util.Set;
58
59
60
61
62
63
64
65 public class CheckSignatureRule
66 implements EnforcerRule
67 {
68
69
70
71
72
73
74 protected Signature signature;
75
76
77
78
79
80
81 protected String[] ignores;
82
83
84
85
86
87
88
89
90
91
92
93 protected String[] annotations;
94
95
96
97
98
99
100 protected boolean ignoreDependencies = true;
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 private String[] includeDependencies = null;
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 private String[] excludeDependencies = null;
133
134
135
136
137
138
139
140 private boolean checkTestClasses = false;
141
142 public void execute( EnforcerRuleHelper helper )
143 throws EnforcerRuleException
144 {
145 try
146 {
147 File outputDirectory = new File( (String) helper.evaluate( "${project.build.outputDirectory}" ) );
148
149 File testOutputDirectory = new File( (String) helper.evaluate( "${project.build.testOutputDirectory}" ) );
150
151 ArtifactResolver resolver = (ArtifactResolver) helper.getComponent( ArtifactResolver.class );
152
153 MavenProject project = (MavenProject) helper.evaluate( "${project}" );
154
155 ArtifactRepository localRepository = (ArtifactRepository) helper.evaluate( "${localRepository}" );
156
157 ArtifactFactory artifactFactory = (ArtifactFactory) helper.getComponent( ArtifactFactory.class );
158
159 if ( StringUtils.isEmpty( signature.getVersion() ) )
160 {
161 helper.getLog().debug( "Resolving signature " + signature.getGroupId() + ":" + signature.getArtifactId()
162 + " version from dependencies" );
163 String source = "dependencies";
164 Dependency match = findMatchingDependency( signature, project.getDependencies() );
165 if ( match == null )
166 {
167 helper.getLog().debug( "Resolving signature " + signature.getGroupId() + ":" + signature.getArtifactId()
168 + " version from dependencyManagement" );
169 source = "dependencyManagement";
170 match = findMatchingDependency( signature, project.getDependencyManagement().getDependencies() );
171 }
172 if ( match != null )
173 {
174 helper.getLog().info( "Resolved signature " + signature.getGroupId() + ":" + signature.getArtifactId()
175 + " version as " + match.getVersion() + " from " + source);
176 signature.setVersion( match.getVersion() );
177 }
178 }
179
180 helper.getLog().info( "Checking unresolved references to " + signature );
181
182 org.apache.maven.artifact.Artifact a = signature.createArtifact( artifactFactory );
183
184 resolver.resolve( a, project.getRemoteArtifactRepositories(), localRepository );
185
186
187 MavenLogger logger = new MavenLogger( helper.getLog() );
188
189 final Set<String> ignoredPackages = buildPackageList( outputDirectory, testOutputDirectory, project, logger );
190
191 if ( ignores != null )
192 {
193 for ( String ignore : ignores )
194 {
195 if ( ignore == null )
196 {
197 continue;
198 }
199 ignoredPackages.add( ignore.replace( '.', '/' ) );
200 }
201 }
202
203 final SignatureChecker signatureChecker =
204 new SignatureChecker( new FileInputStream( a.getFile() ), ignoredPackages, logger );
205 signatureChecker.setCheckJars( false );
206
207
208 signatureChecker.setSourcePath( buildSourcePathList( project ) );
209
210 if ( annotations != null )
211 {
212 signatureChecker.setAnnotationTypes( Arrays.asList( annotations ) );
213 }
214
215 if ( checkTestClasses )
216 {
217 signatureChecker.process( new File[] { outputDirectory, testOutputDirectory } );
218 }
219 else
220 {
221 signatureChecker.process( outputDirectory );
222 }
223
224 if ( signatureChecker.isSignatureBroken() )
225 {
226 throw new EnforcerRuleException(
227 "Signature errors found. Verify them and ignore them with the proper annotation if needed." );
228 }
229 }
230 catch ( IOException e )
231 {
232 throw new EnforcerRuleException( "Failed to check signatures", e );
233 }
234 catch ( AbstractArtifactResolutionException e )
235 {
236 throw new EnforcerRuleException( "Failed to obtain signature: " + signature, e );
237 }
238 catch ( ComponentLookupException e )
239 {
240 throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e );
241 }
242 catch ( ExpressionEvaluationException e )
243 {
244 throw new EnforcerRuleException( "Unable to lookup an expression " + e.getLocalizedMessage(), e );
245 }
246 }
247
248 private static Dependency findMatchingDependency( Signature signature, List<Dependency> dependencies )
249 {
250 Dependency match = null;
251 for ( Dependency d : dependencies )
252 {
253 if ( StringUtils.isEmpty( d.getVersion() ) )
254 {
255 continue;
256 }
257 if ( StringUtils.equals( d.getGroupId(), signature.getGroupId() ) && StringUtils.equals( d.getArtifactId(),
258 signature.getArtifactId() ) )
259 {
260 if ( "signature".equals( d.getType() ) )
261 {
262
263 match = d;
264 break;
265 }
266 if ( "pom".equals( d.getType() ) )
267 {
268 if ( match == null || "jar".equals( match.getType() ) )
269 {
270 match = d;
271 }
272 }
273 if ( "jar".equals( d.getType() ) )
274 {
275 if ( match == null )
276 {
277 match = d;
278 }
279 }
280 }
281 }
282 return match;
283 }
284
285
286
287
288
289
290
291 private Set<String> buildPackageList( File outputDirectory, File testOutputDirectory, MavenProject project, Logger logger )
292 throws IOException
293 {
294 ClassListBuilder plb = new ClassListBuilder( logger );
295 apply( plb, outputDirectory, testOutputDirectory, project, logger );
296 return plb.getPackages();
297 }
298
299 private void apply( ClassFileVisitor v, File outputDirectory, File testOutputDirectory, MavenProject project, Logger logger )
300 throws IOException
301 {
302 v.process( outputDirectory );
303 if ( checkTestClasses )
304 {
305 v.process( testOutputDirectory );
306 }
307 if ( ignoreDependencies )
308 {
309 PatternIncludesArtifactFilter includesFilter = includeDependencies == null
310 ? null
311 : new PatternIncludesArtifactFilter( Arrays.asList( includeDependencies ) );
312 PatternExcludesArtifactFilter excludesFilter = excludeDependencies == null
313 ? null
314 : new PatternExcludesArtifactFilter( Arrays.asList( excludeDependencies ) );
315
316 Set<String> classpathScopes = new HashSet<>(
317 Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_SYSTEM ) );
318 if ( checkTestClasses )
319 {
320 classpathScopes.addAll( Arrays.asList( Artifact.SCOPE_TEST, Artifact.SCOPE_RUNTIME ) );
321 }
322
323 logger.debug( "Building list of classes from dependencies" );
324 for ( Object o : project.getArtifacts() )
325 {
326
327 Artifact artifact = (Artifact) o;
328
329 if ( !artifact.getArtifactHandler().isAddedToClasspath() )
330 {
331 logger.debug(
332 "Skipping artifact " + artifactId( artifact ) + " as it is not added to the classpath." );
333 continue;
334 }
335
336 if ( !classpathScopes.contains( artifact.getScope() ) )
337 {
338 logger.debug( "Skipping artifact " + artifactId( artifact ) + " as it is not on the " + (
339 checkTestClasses
340 ? "test"
341 : "compile" ) + " classpath." );
342 continue;
343 }
344
345 if ( includesFilter != null && !includesFilter.include( artifact ) )
346 {
347 logger.debug( "Skipping classes in artifact " + artifactId( artifact )
348 + " as it does not match include rules." );
349 continue;
350 }
351
352 if ( excludesFilter != null && !excludesFilter.include( artifact ) )
353 {
354 logger.debug( "Skipping classes in artifact " + artifactId( artifact )
355 + " as it does matches exclude rules." );
356 continue;
357 }
358
359 if ( artifact.getFile() == null )
360 {
361 logger.warn( "Skipping classes in artifact " + artifactId( artifact )
362 + " as there are unresolved dependencies." );
363 continue;
364 }
365
366 logger.debug( "Adding classes in artifact " + artifactId( artifact ) + " to the ignores" );
367 v.process( artifact.getFile() );
368 }
369 }
370 }
371
372 public boolean isCacheable()
373 {
374 return false;
375 }
376
377 public boolean isResultValid( EnforcerRule enforcerRule )
378 {
379 return false;
380 }
381
382 public String getCacheId()
383 {
384 return getClass().getName() + new Random().nextLong();
385 }
386
387 private static String artifactId( Artifact artifact )
388 {
389 return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + (
390 artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" ) + ":" + artifact.getBaseVersion();
391
392 }
393
394 @SuppressWarnings("unchecked")
395 private List<File> buildSourcePathList( MavenProject project )
396 {
397 List<String> compileSourceRoots = new ArrayList<>( project.getCompileSourceRoots() );
398 if ( checkTestClasses )
399 {
400 compileSourceRoots.addAll( project.getTestCompileSourceRoots() );
401 }
402 List<File> sourcePathList = new ArrayList<>( compileSourceRoots.size() );
403 for ( String compileSourceRoot : compileSourceRoots)
404 {
405 sourcePathList.add( new File( compileSourceRoot ) );
406 }
407 return sourcePathList;
408 }
409 }