1 package org.apache.maven.plugins.jdeps;
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.IOException;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.LinkedHashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Properties;
35 import java.util.Set;
36 import java.util.StringTokenizer;
37
38 import org.apache.commons.lang3.SystemUtils;
39 import org.apache.maven.artifact.Artifact;
40 import org.apache.maven.artifact.ArtifactUtils;
41 import org.apache.maven.artifact.DependencyResolutionRequiredException;
42 import org.apache.maven.execution.MavenSession;
43 import org.apache.maven.plugin.AbstractMojo;
44 import org.apache.maven.plugin.MojoExecutionException;
45 import org.apache.maven.plugin.MojoFailureException;
46 import org.apache.maven.plugins.jdeps.consumers.JDepsConsumer;
47 import org.apache.maven.plugins.annotations.Component;
48 import org.apache.maven.plugins.annotations.Parameter;
49 import org.apache.maven.project.MavenProject;
50 import org.apache.maven.toolchain.Toolchain;
51 import org.apache.maven.toolchain.ToolchainManager;
52 import org.codehaus.plexus.util.MatchPatterns;
53 import org.codehaus.plexus.util.StringUtils;
54 import org.codehaus.plexus.util.cli.CommandLineException;
55 import org.codehaus.plexus.util.cli.CommandLineUtils;
56 import org.codehaus.plexus.util.cli.Commandline;
57
58
59
60
61
62
63
64 public abstract class AbstractJDepsMojo
65 extends AbstractMojo
66 {
67
68 @Parameter( defaultValue = "${project}", readonly = true, required = true )
69 private MavenProject project;
70
71 @Parameter( defaultValue = "${session}", readonly = true, required = true )
72 private MavenSession session;
73
74 @Parameter( defaultValue = "${project.build.directory}", readonly = true, required = true )
75 private File outputDirectory;
76
77
78
79
80 @Parameter( defaultValue = "true", property = "jdeps.failOnWarning" )
81 private boolean failOnWarning;
82
83
84
85
86
87
88 @Parameter( property = "jdeps.multiRelease" )
89 private String multiRelease;
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 @Parameter
106 private List<String> dependenciesToAnalyzeIncludes;
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 @Parameter
122 private List<String> dependenciesToAnalyzeExcludes;
123
124
125
126
127 @Parameter( property = "jdeps.dotOutput" )
128 private File dotOutput;
129
130
131
132
133
134
135
136
137
138
139
140 @Parameter( property = "jdeps.verbose" )
141 private String verbose;
142
143
144
145
146
147
148
149 @Parameter
150 private List<String> packages;
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168 @Parameter( property = "jdeps.include" )
169 private String include;
170
171
172
173
174
175 @Parameter( defaultValue = "false", property = "jdeps.apionly" )
176 private boolean apiOnly;
177
178
179
180
181 @Parameter( defaultValue = "false", property = "jdeps.profile" )
182 private boolean profile;
183
184
185
186
187
188 @Parameter( defaultValue = "false", property = "jdeps.recursive" )
189 private boolean recursive;
190
191
192
193
194
195
196 @Parameter( property = "jdeps.module" )
197 private String module;
198
199 @Component
200 private ToolchainManager toolchainManager;
201
202 protected MavenProject getProject()
203 {
204 return project;
205 }
206
207 public void execute()
208 throws MojoExecutionException, MojoFailureException
209 {
210 if ( !new File( getClassesDirectory() ).exists() )
211 {
212 getLog().debug( "No classes to analyze" );
213 return;
214 }
215
216 String jExecutable;
217 try
218 {
219 jExecutable = getJDepsExecutable();
220 }
221 catch ( IOException e )
222 {
223 throw new MojoFailureException( "Unable to find jdeps command: " + e.getMessage(), e );
224 }
225
226
227
228 Commandline cmd = new Commandline();
229 cmd.setExecutable( jExecutable );
230
231 Set<Path> dependenciesToAnalyze = getDependenciesToAnalyze();
232 addJDepsOptions( cmd, dependenciesToAnalyze );
233 addJDepsClasses( cmd, dependenciesToAnalyze );
234
235 JDepsConsumer consumer = new JDepsConsumer();
236 executeJDepsCommandLine( cmd, outputDirectory, consumer );
237
238
239 if ( consumer.getOffendingPackages().size() > 0 )
240 {
241 final String ls = System.getProperty( "line.separator" );
242
243 StringBuilder msg = new StringBuilder();
244 msg.append( "Found offending packages:" ).append( ls );
245 for ( Map.Entry<String, String> offendingPackage : consumer.getOffendingPackages().entrySet() )
246 {
247 msg.append( ' ' ).append( offendingPackage.getKey() )
248 .append( " -> " ).append( offendingPackage.getValue() ).append( ls );
249 }
250
251 if ( isFailOnWarning() )
252 {
253 throw new MojoExecutionException( msg.toString() );
254 }
255 }
256 }
257
258 protected void addJDepsOptions( Commandline cmd, Set<Path> dependenciesToAnalyze )
259 throws MojoFailureException
260 {
261 if ( dotOutput != null )
262 {
263 cmd.createArg().setValue( "-dotoutput" );
264 cmd.createArg().setFile( dotOutput );
265 }
266
267
268
269
270
271
272 if ( verbose != null )
273 {
274 if ( "class".equals( verbose ) )
275 {
276 cmd.createArg().setValue( "-verbose:class" );
277 }
278 else if ( "package".equals( verbose ) )
279 {
280 cmd.createArg().setValue( "-verbose:package" );
281 }
282 else
283 {
284 cmd.createArg().setValue( "-v" );
285 }
286 }
287
288 try
289 {
290 Collection<Path> cp = new ArrayList<>();
291
292 for ( Path path : getClassPath() )
293 {
294 if ( !dependenciesToAnalyze.contains( path ) )
295 {
296 cp.add( path );
297 }
298 }
299
300 if ( !cp.isEmpty() )
301 {
302 cmd.createArg().setValue( "-cp" );
303
304 cmd.createArg().setValue( StringUtils.join( cp.iterator(), File.pathSeparator ) );
305 }
306
307 }
308 catch ( DependencyResolutionRequiredException e )
309 {
310 throw new MojoFailureException( e.getMessage(), e );
311 }
312
313 if ( packages != null )
314 {
315 for ( String pkgName : packages )
316 {
317 cmd.createArg().setValue( "-p" );
318 cmd.createArg().setValue( pkgName );
319 }
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337 if ( include != null )
338 {
339 cmd.createArg().setValue( "-include" );
340 cmd.createArg().setValue( include );
341 }
342
343 if ( profile )
344 {
345 cmd.createArg().setValue( "-P" );
346 }
347
348 if ( module != null )
349 {
350 cmd.createArg().setValue( "-m" );
351 cmd.createArg().setValue( module );
352 }
353
354 if ( multiRelease != null )
355 {
356 cmd.createArg().setValue( "--multi-release" );
357 cmd.createArg().setValue( multiRelease );
358 }
359
360 if ( apiOnly )
361 {
362 cmd.createArg().setValue( "-apionly" );
363 }
364
365 if ( recursive )
366 {
367 cmd.createArg().setValue( "-R" );
368 }
369
370
371 }
372
373 protected Set<Path> getDependenciesToAnalyze()
374 {
375 Set<Path> jdepsClasses = new LinkedHashSet<>();
376
377 jdepsClasses.add( Paths.get( getClassesDirectory() ) );
378
379 if ( dependenciesToAnalyzeIncludes != null )
380 {
381 MatchPatterns includes = MatchPatterns.from( dependenciesToAnalyzeIncludes );
382
383 MatchPatterns excludes;
384 if ( dependenciesToAnalyzeExcludes != null )
385 {
386 excludes = MatchPatterns.from( dependenciesToAnalyzeExcludes );
387 }
388 else
389 {
390 excludes = MatchPatterns.from( Collections.<String>emptyList() );
391 }
392
393 for ( Artifact artifact : project.getArtifacts() )
394 {
395 String versionlessKey = ArtifactUtils.versionlessKey( artifact );
396
397 if ( includes.matchesPatternStart( versionlessKey, true )
398 && !excludes.matchesPatternStart( versionlessKey, true ) )
399 {
400 jdepsClasses.add( artifact.getFile().toPath() );
401 }
402 }
403 }
404
405 return jdepsClasses;
406 }
407
408 protected void addJDepsClasses( Commandline cmd, Set<Path> dependenciesToAnalyze )
409 {
410
411 for ( Path dependencyToAnalyze : dependenciesToAnalyze )
412 {
413 cmd.createArg().setFile( dependencyToAnalyze.toFile() );
414 }
415 }
416
417 private String getJDepsExecutable() throws IOException
418 {
419 Toolchain tc = getToolchain();
420
421 String jdepsExecutable = null;
422 if ( tc != null )
423 {
424 jdepsExecutable = tc.findTool( "jdeps" );
425 }
426
427 String jdepsCommand = "jdeps" + ( SystemUtils.IS_OS_WINDOWS ? ".exe" : "" );
428
429 File jdepsExe;
430
431 if ( StringUtils.isNotEmpty( jdepsExecutable ) )
432 {
433 jdepsExe = new File( jdepsExecutable );
434
435 if ( jdepsExe.isDirectory() )
436 {
437 jdepsExe = new File( jdepsExe, jdepsCommand );
438 }
439
440 if ( SystemUtils.IS_OS_WINDOWS && jdepsExe.getName().indexOf( '.' ) < 0 )
441 {
442 jdepsExe = new File( jdepsExe.getPath() + ".exe" );
443 }
444
445 if ( !jdepsExe.isFile() )
446 {
447 throw new IOException( "The jdeps executable '" + jdepsExe
448 + "' doesn't exist or is not a file." );
449 }
450 return jdepsExe.getAbsolutePath();
451 }
452
453 jdepsExe = new File( SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "sh", jdepsCommand );
454
455
456
457
458 if ( !jdepsExe.exists() || !jdepsExe.isFile() )
459 {
460 Properties env = CommandLineUtils.getSystemEnvVars();
461 String javaHome = env.getProperty( "JAVA_HOME" );
462 if ( StringUtils.isEmpty( javaHome ) )
463 {
464 throw new IOException( "The environment variable JAVA_HOME is not correctly set." );
465 }
466 if ( ( !new File( javaHome ).getCanonicalFile().exists() )
467 || ( new File( javaHome ).getCanonicalFile().isFile() ) )
468 {
469 throw new IOException( "The environment variable JAVA_HOME=" + javaHome
470 + " doesn't exist or is not a valid directory." );
471 }
472
473 jdepsExe = new File( javaHome + File.separator + "bin", jdepsCommand );
474 }
475
476 if ( !jdepsExe.getCanonicalFile().exists() || !jdepsExe.getCanonicalFile().isFile() )
477 {
478 throw new IOException( "The jdeps executable '" + jdepsExe
479 + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable." );
480 }
481
482 return jdepsExe.getAbsolutePath();
483 }
484
485 private void executeJDepsCommandLine( Commandline cmd, File jOutputDirectory,
486 CommandLineUtils.StringStreamConsumer consumer )
487 throws MojoExecutionException
488 {
489 if ( getLog().isDebugEnabled() )
490 {
491
492 getLog().debug( "Executing: " + CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" ) );
493 }
494
495
496 CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer()
497 {
498 @Override
499 public void consumeLine( String line )
500 {
501 if ( !line.startsWith( "Picked up JAVA_TOOL_OPTIONS:" ) )
502 {
503 super.consumeLine( line );
504 }
505 }
506 };
507 CommandLineUtils.StringStreamConsumer out;
508 if ( consumer != null )
509 {
510 out = consumer;
511 }
512 else
513 {
514 out = new CommandLineUtils.StringStreamConsumer();
515 }
516
517 try
518 {
519 int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
520
521 String output = ( StringUtils.isEmpty( out.getOutput() ) ? null : '\n' + out.getOutput().trim() );
522
523 if ( exitCode != 0 )
524 {
525 if ( StringUtils.isNotEmpty( output ) )
526 {
527 getLog().info( output );
528 }
529
530 StringBuilder msg = new StringBuilder( "\nExit code: " );
531 msg.append( exitCode );
532 if ( StringUtils.isNotEmpty( err.getOutput() ) )
533 {
534 msg.append( " - " ).append( err.getOutput() );
535 }
536 msg.append( '\n' );
537 msg.append( "Command line was: " ).append( cmd ).append( '\n' ).append( '\n' );
538
539 throw new MojoExecutionException( msg.toString() );
540 }
541
542 if ( StringUtils.isNotEmpty( output ) )
543 {
544 getLog().info( output );
545 }
546 }
547 catch ( CommandLineException e )
548 {
549 throw new MojoExecutionException( "Unable to execute jdeps command: " + e.getMessage(), e );
550 }
551
552
553
554
555
556 if ( StringUtils.isNotEmpty( err.getOutput() ) && getLog().isWarnEnabled() )
557 {
558 getLog().warn( "JDeps Warnings" );
559
560 StringTokenizer token = new StringTokenizer( err.getOutput(), "\n" );
561 while ( token.hasMoreTokens() )
562 {
563 String current = token.nextToken().trim();
564
565 getLog().warn( current );
566 }
567 }
568 }
569
570 private Toolchain getToolchain()
571 {
572 Toolchain tc = null;
573 if ( toolchainManager != null )
574 {
575 tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
576
577 if ( tc == null )
578 {
579
580 try
581 {
582 Method getToolchainsMethod =
583 toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
584 Map.class );
585
586 @SuppressWarnings( "unchecked" )
587 List<Toolchain> tcs =
588 (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
589 Collections.singletonMap( "version", "[1.8,)" ) );
590
591 if ( tcs != null && tcs.size() > 0 )
592 {
593
594 tc = tcs.get( tcs.size() - 1 );
595 }
596 }
597 catch ( NoSuchMethodException e )
598 {
599
600 }
601 catch ( SecurityException e )
602 {
603
604 }
605 catch ( IllegalAccessException e )
606 {
607
608 }
609 catch ( IllegalArgumentException e )
610 {
611
612 }
613 catch ( InvocationTargetException e )
614 {
615
616 }
617 }
618 }
619
620 return tc;
621 }
622
623 protected boolean isFailOnWarning()
624 {
625 return failOnWarning;
626 }
627
628 protected abstract String getClassesDirectory();
629
630 protected abstract Collection<Path> getClassPath() throws DependencyResolutionRequiredException;
631 }