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