1 package org.apache.maven.plugins.dependency.analyze;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.StringWriter;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Iterator;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Set;
30
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.maven.artifact.Artifact;
33 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
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.plugins.annotations.Parameter;
38 import org.apache.maven.project.MavenProject;
39 import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
40 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
41 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer;
42 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException;
43 import org.codehaus.plexus.PlexusConstants;
44 import org.codehaus.plexus.PlexusContainer;
45 import org.codehaus.plexus.context.Context;
46 import org.codehaus.plexus.context.ContextException;
47 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
48 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
49
50
51
52
53
54
55
56
57 public abstract class AbstractAnalyzeMojo
58 extends AbstractMojo
59 implements Contextualizable
60 {
61
62
63
64
65
66
67 private Context context;
68
69
70
71
72 @Parameter( defaultValue = "${project}", readonly = true, required = true )
73 private MavenProject project;
74
75
76
77
78
79
80
81
82
83 @Parameter( property = "analyzer", defaultValue = "default" )
84 private String analyzer;
85
86
87
88
89 @Parameter( property = "failOnWarning", defaultValue = "false" )
90 private boolean failOnWarning;
91
92
93
94
95 @Parameter( property = "verbose", defaultValue = "false" )
96 private boolean verbose;
97
98
99
100
101 @Parameter( property = "ignoreNonCompile", defaultValue = "false" )
102 private boolean ignoreNonCompile;
103
104
105
106
107 @Parameter( property = "ignoreUnusedRuntime", defaultValue = "false" )
108 private boolean ignoreUnusedRuntime;
109
110
111
112
113
114
115 @Parameter( property = "outputXML", defaultValue = "false" )
116 private boolean outputXML;
117
118
119
120
121
122
123 @Parameter( property = "scriptableOutput", defaultValue = "false" )
124 private boolean scriptableOutput;
125
126
127
128
129
130
131 @Parameter( property = "scriptableFlag", defaultValue = "$$$%%%" )
132 private String scriptableFlag;
133
134
135
136
137
138
139 @Parameter( defaultValue = "${basedir}", readonly = true )
140 private File baseDir;
141
142
143
144
145
146
147 @Parameter( defaultValue = "${project.build.directory}", readonly = true )
148 private File outputDirectory;
149
150
151
152
153
154
155
156 @Parameter
157 private String[] usedDependencies;
158
159
160
161
162
163
164 @Parameter( property = "mdep.analyze.skip", defaultValue = "false" )
165 private boolean skip;
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184 @Parameter
185 private String[] ignoredDependencies = new String[0];
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 @Parameter
204 private String[] ignoredUsedUndeclaredDependencies = new String[0];
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222 @Parameter
223 private String[] ignoredUnusedDeclaredDependencies = new String[0];
224
225
226
227
228
229
230 @Override
231 public void execute()
232 throws MojoExecutionException, MojoFailureException
233 {
234 if ( isSkip() )
235 {
236 getLog().info( "Skipping plugin execution" );
237 return;
238 }
239
240 if ( "pom".equals( project.getPackaging() ) )
241 {
242 getLog().info( "Skipping pom project" );
243 return;
244 }
245
246 if ( outputDirectory == null || !outputDirectory.exists() )
247 {
248 getLog().info( "Skipping project with no build directory" );
249 return;
250 }
251
252 boolean warning = checkDependencies();
253
254 if ( warning && failOnWarning )
255 {
256 throw new MojoExecutionException( "Dependency problems found" );
257 }
258 }
259
260
261
262
263
264 protected ProjectDependencyAnalyzer createProjectDependencyAnalyzer()
265 throws MojoExecutionException
266 {
267
268 final String role = ProjectDependencyAnalyzer.ROLE;
269 final String roleHint = analyzer;
270
271 try
272 {
273 final PlexusContainer container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
274
275 return (ProjectDependencyAnalyzer) container.lookup( role, roleHint );
276 }
277 catch ( Exception exception )
278 {
279 throw new MojoExecutionException( "Failed to instantiate ProjectDependencyAnalyser with role " + role
280 + " / role-hint " + roleHint, exception );
281 }
282 }
283
284 @Override
285 public void contextualize( Context theContext )
286 throws ContextException
287 {
288 this.context = theContext;
289 }
290
291
292
293
294 protected final boolean isSkip()
295 {
296 return skip;
297 }
298
299
300
301 private boolean checkDependencies()
302 throws MojoExecutionException
303 {
304 ProjectDependencyAnalysis analysis;
305 try
306 {
307 analysis = createProjectDependencyAnalyzer().analyze( project );
308
309 if ( usedDependencies != null )
310 {
311 analysis = analysis.forceDeclaredDependenciesUsage( usedDependencies );
312 }
313 }
314 catch ( ProjectDependencyAnalyzerException exception )
315 {
316 throw new MojoExecutionException( "Cannot analyze dependencies", exception );
317 }
318
319 if ( ignoreNonCompile )
320 {
321 analysis = analysis.ignoreNonCompile();
322 }
323
324 Set<Artifact> usedDeclared = new LinkedHashSet<>( analysis.getUsedDeclaredArtifacts() );
325 Set<Artifact> usedUndeclared = new LinkedHashSet<>( analysis.getUsedUndeclaredArtifacts() );
326 Set<Artifact> unusedDeclared = new LinkedHashSet<>( analysis.getUnusedDeclaredArtifacts() );
327 Set<Artifact> testArtifactsWithNonTestScope = new LinkedHashSet<>(
328 analysis.getTestArtifactsWithNonTestScope() );
329
330 Set<Artifact> ignoredUsedUndeclared = new LinkedHashSet<>();
331 Set<Artifact> ignoredUnusedDeclared = new LinkedHashSet<>();
332
333 if ( ignoreUnusedRuntime )
334 {
335 filterArtifactsByScope( unusedDeclared, Artifact.SCOPE_RUNTIME );
336 }
337
338 ignoredUsedUndeclared.addAll( filterDependencies( usedUndeclared, ignoredDependencies ) );
339 ignoredUsedUndeclared.addAll( filterDependencies( usedUndeclared, ignoredUsedUndeclaredDependencies ) );
340
341 ignoredUnusedDeclared.addAll( filterDependencies( unusedDeclared, ignoredDependencies ) );
342 ignoredUnusedDeclared.addAll( filterDependencies( unusedDeclared, ignoredUnusedDeclaredDependencies ) );
343
344 boolean reported = false;
345 boolean warning = false;
346
347 if ( verbose && !usedDeclared.isEmpty() )
348 {
349 getLog().info( "Used declared dependencies found:" );
350
351 logArtifacts( analysis.getUsedDeclaredArtifacts(), false );
352 reported = true;
353 }
354
355 if ( !usedUndeclared.isEmpty() )
356 {
357 getLog().warn( "Used undeclared dependencies found:" );
358
359 logArtifacts( usedUndeclared, true );
360 reported = true;
361 warning = true;
362 }
363
364 if ( !unusedDeclared.isEmpty() )
365 {
366 getLog().warn( "Unused declared dependencies found:" );
367
368 logArtifacts( unusedDeclared, true );
369 reported = true;
370 warning = true;
371 }
372
373 if ( !testArtifactsWithNonTestScope.isEmpty() )
374 {
375 getLog().warn( "Non-test scoped test only dependencies found:" );
376
377 logArtifacts( testArtifactsWithNonTestScope, true );
378 reported = true;
379 warning = true;
380 }
381
382 if ( verbose && !ignoredUsedUndeclared.isEmpty() )
383 {
384 getLog().info( "Ignored used undeclared dependencies:" );
385
386 logArtifacts( ignoredUsedUndeclared, false );
387 reported = true;
388 }
389
390 if ( verbose && !ignoredUnusedDeclared.isEmpty() )
391 {
392 getLog().info( "Ignored unused declared dependencies:" );
393
394 logArtifacts( ignoredUnusedDeclared, false );
395 reported = true;
396 }
397
398 if ( outputXML )
399 {
400 writeDependencyXML( usedUndeclared );
401 }
402
403 if ( scriptableOutput )
404 {
405 writeScriptableOutput( usedUndeclared );
406 }
407
408 if ( !reported )
409 {
410 getLog().info( "No dependency problems found" );
411 }
412
413 return warning;
414 }
415
416 private void filterArtifactsByScope( Set<Artifact> artifacts, String scope )
417 {
418 for ( Iterator<Artifact> iterator = artifacts.iterator(); iterator.hasNext(); )
419 {
420 Artifact artifact = iterator.next();
421 if ( artifact.getScope().equals( scope ) )
422 {
423 iterator.remove();
424 }
425 }
426 }
427
428 private void logArtifacts( Set<Artifact> artifacts, boolean warn )
429 {
430 if ( artifacts.isEmpty() )
431 {
432 getLog().info( " None" );
433 }
434 else
435 {
436 for ( Artifact artifact : artifacts )
437 {
438
439 artifact.isSnapshot();
440
441 if ( warn )
442 {
443 getLog().warn( " " + artifact );
444 }
445 else
446 {
447 getLog().info( " " + artifact );
448 }
449
450 }
451 }
452 }
453
454 private void writeDependencyXML( Set<Artifact> artifacts )
455 {
456 if ( !artifacts.isEmpty() )
457 {
458 getLog().info( "Add the following to your pom to correct the missing dependencies: " );
459
460 StringWriter out = new StringWriter();
461 PrettyPrintXMLWriter writer = new PrettyPrintXMLWriter( out );
462
463 for ( Artifact artifact : artifacts )
464 {
465
466 artifact.isSnapshot();
467
468 writer.startElement( "dependency" );
469 writer.startElement( "groupId" );
470 writer.writeText( artifact.getGroupId() );
471 writer.endElement();
472 writer.startElement( "artifactId" );
473 writer.writeText( artifact.getArtifactId() );
474 writer.endElement();
475 writer.startElement( "version" );
476 writer.writeText( artifact.getBaseVersion() );
477 if ( !StringUtils.isBlank( artifact.getClassifier() ) )
478 {
479 writer.startElement( "classifier" );
480 writer.writeText( artifact.getClassifier() );
481 writer.endElement();
482 }
483 writer.endElement();
484
485 if ( !Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) )
486 {
487 writer.startElement( "scope" );
488 writer.writeText( artifact.getScope() );
489 writer.endElement();
490 }
491 writer.endElement();
492 }
493
494 getLog().info( System.lineSeparator() + out.getBuffer() );
495 }
496 }
497
498 private void writeScriptableOutput( Set<Artifact> artifacts )
499 {
500 if ( !artifacts.isEmpty() )
501 {
502 getLog().info( "Missing dependencies: " );
503 String pomFile = baseDir.getAbsolutePath() + File.separatorChar + "pom.xml";
504 StringBuilder buf = new StringBuilder();
505
506 for ( Artifact artifact : artifacts )
507 {
508
509 artifact.isSnapshot();
510
511
512 buf.append( scriptableFlag )
513 .append( ":" )
514 .append( pomFile )
515 .append( ":" )
516 .append( artifact.getDependencyConflictId() )
517 .append( ":" )
518 .append( artifact.getClassifier() )
519 .append( ":" )
520 .append( artifact.getBaseVersion() )
521 .append( ":" )
522 .append( artifact.getScope() )
523 .append( System.lineSeparator() );
524
525 }
526 getLog().info( System.lineSeparator() + buf );
527 }
528 }
529
530 private List<Artifact> filterDependencies( Set<Artifact> artifacts, String[] excludes )
531 throws MojoExecutionException
532 {
533 ArtifactFilter filter = new StrictPatternExcludesArtifactFilter( Arrays.asList( excludes ) );
534 List<Artifact> result = new ArrayList<>();
535
536 for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); )
537 {
538 Artifact artifact = it.next();
539 if ( !filter.include( artifact ) )
540 {
541 it.remove();
542 result.add( artifact );
543 }
544 }
545
546 return result;
547 }
548 }