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