1 package org.apache.maven.plugin;
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.util.ArrayList;
24 import java.util.HashSet;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29
30 import org.apache.maven.execution.MavenSession;
31 import org.apache.maven.toolchain.Toolchain;
32 import org.apache.maven.toolchain.ToolchainManager;
33 import org.codehaus.plexus.compiler.Compiler;
34 import org.codehaus.plexus.compiler.CompilerConfiguration;
35 import org.codehaus.plexus.compiler.CompilerError;
36 import org.codehaus.plexus.compiler.CompilerException;
37 import org.codehaus.plexus.compiler.CompilerOutputStyle;
38 import org.codehaus.plexus.compiler.manager.CompilerManager;
39 import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
40 import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
41 import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
42 import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
43 import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
44 import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
45 import org.codehaus.plexus.util.ReaderFactory;
46 import org.codehaus.plexus.util.StringUtils;
47
48
49
50
51
52
53
54
55
56
57
58
59 public abstract class AbstractCompilerMojo
60 extends AbstractMojo
61 {
62
63
64
65
66
67
68
69
70
71
72 private boolean failOnError = true;
73
74
75
76
77
78
79 private boolean debug = true;
80
81
82
83
84
85
86 private boolean verbose;
87
88
89
90
91
92
93 private boolean showDeprecation;
94
95
96
97
98
99
100 private boolean optimize;
101
102
103
104
105
106
107 private boolean showWarnings;
108
109
110
111
112
113
114 protected String source;
115
116
117
118
119
120
121 protected String target;
122
123
124
125
126
127
128 private String encoding;
129
130
131
132
133
134
135
136 private int staleMillis;
137
138
139
140
141
142
143
144 private String compilerId;
145
146
147
148
149
150
151 private String compilerVersion;
152
153
154
155
156
157
158
159 private boolean fork;
160
161
162
163
164
165
166
167
168 private String meminitial;
169
170
171
172
173
174
175
176
177 private String maxmem;
178
179
180
181
182
183
184 private String executable;
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200 private String proc;
201
202
203
204
205
206
207
208
209
210
211 private String[] annotationProcessors;
212
213
214
215
216
217
218
219
220
221
222
223
224
225 protected Map<String, String> compilerArguments;
226
227
228
229
230
231
232
233
234
235
236
237
238 protected String compilerArgument;
239
240
241
242
243
244
245
246 private String outputFileName;
247
248
249
250
251
252
253
254
255 private String debuglevel;
256
257
258 private ToolchainManager toolchainManager;
259
260
261
262
263
264
265
266
267
268
269
270
271 private File basedir;
272
273
274
275
276
277
278
279
280 private File buildDirectory;
281
282
283
284
285
286
287 private CompilerManager compilerManager;
288
289
290
291
292
293
294
295
296
297 private MavenSession session;
298
299 protected abstract SourceInclusionScanner getSourceInclusionScanner( int staleMillis );
300
301 protected abstract SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding );
302
303 protected abstract List<String> getClasspathElements();
304
305 protected abstract List<String> getCompileSourceRoots();
306
307 protected abstract File getOutputDirectory();
308
309 protected abstract String getSource();
310
311 protected abstract String getTarget();
312
313 protected abstract String getCompilerArgument();
314
315 protected abstract Map<String, String> getCompilerArguments();
316
317 protected abstract File getGeneratedSourcesDirectory();
318
319 @SuppressWarnings( "unchecked" )
320 public void execute()
321 throws MojoExecutionException, CompilationFailureException
322 {
323
324
325
326
327
328
329 Compiler compiler;
330
331 getLog().debug( "Using compiler '" + compilerId + "'." );
332
333 try
334 {
335 compiler = compilerManager.getCompiler( compilerId );
336 }
337 catch ( NoSuchCompilerException e )
338 {
339 throw new MojoExecutionException( "No such compiler '" + e.getCompilerId() + "'." );
340 }
341
342
343
344 Toolchain tc = getToolchain();
345 if ( tc != null )
346 {
347 getLog().info( "Toolchain in compiler-plugin: " + tc );
348 if ( executable != null )
349 {
350 getLog().warn( "Toolchains are ignored, 'executable' parameter is set to " + executable );
351 }
352 else
353 {
354 fork = true;
355
356 executable = tc.findTool( compilerId );
357 }
358 }
359
360
361
362
363 List<String> compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() );
364
365 if ( compileSourceRoots.isEmpty() )
366 {
367 getLog().info( "No sources to compile" );
368
369 return;
370 }
371
372 if ( getLog().isDebugEnabled() )
373 {
374 getLog().debug( "Source directories: " + compileSourceRoots.toString().replace( ',', '\n' ) );
375 getLog().debug( "Classpath: " + getClasspathElements().toString().replace( ',', '\n' ) );
376 getLog().debug( "Output directory: " + getOutputDirectory() );
377 }
378
379
380
381
382
383 CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
384
385 compilerConfiguration.setOutputLocation( getOutputDirectory().getAbsolutePath() );
386
387 compilerConfiguration.setClasspathEntries( getClasspathElements() );
388
389 compilerConfiguration.setSourceLocations( compileSourceRoots );
390
391 compilerConfiguration.setOptimize( optimize );
392
393 compilerConfiguration.setDebug( debug );
394
395 if ( debug && StringUtils.isNotEmpty( debuglevel ) )
396 {
397 String[] split = StringUtils.split( debuglevel, "," );
398 for ( int i = 0; i < split.length; i++ )
399 {
400 if ( !( split[i].equalsIgnoreCase( "none" ) || split[i].equalsIgnoreCase( "lines" )
401 || split[i].equalsIgnoreCase( "vars" ) || split[i].equalsIgnoreCase( "source" ) ) )
402 {
403 throw new IllegalArgumentException( "The specified debug level: '" + split[i]
404 + "' is unsupported. " + "Legal values are 'none', 'lines', 'vars', and 'source'." );
405 }
406 }
407 compilerConfiguration.setDebugLevel( debuglevel );
408 }
409
410 compilerConfiguration.setVerbose( verbose );
411
412 compilerConfiguration.setShowWarnings( showWarnings );
413
414 compilerConfiguration.setShowDeprecation( showDeprecation );
415
416 compilerConfiguration.setSourceVersion( getSource() );
417
418 compilerConfiguration.setTargetVersion( getTarget() );
419
420 compilerConfiguration.setProc( proc );
421
422 compilerConfiguration.setGeneratedSourcesDirectory( getGeneratedSourcesDirectory() );
423
424 compilerConfiguration.setAnnotationProcessors( annotationProcessors );
425
426 compilerConfiguration.setSourceEncoding( encoding );
427
428 Map<String, String> effectiveCompilerArguments = getCompilerArguments();
429
430 String effectiveCompilerArgument = getCompilerArgument();
431
432 if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null ) )
433 {
434 LinkedHashMap<String, String> cplrArgsCopy = new LinkedHashMap<String, String>();
435 if ( effectiveCompilerArguments != null )
436 {
437 for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
438 {
439 String key = (String) me.getKey();
440 String value = (String) me.getValue();
441 if ( !key.startsWith( "-" ) )
442 {
443 key = "-" + key;
444 }
445 cplrArgsCopy.put( key, value );
446 }
447 }
448 if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
449 {
450 cplrArgsCopy.put( effectiveCompilerArgument, null );
451 }
452 compilerConfiguration.setCustomCompilerArguments( cplrArgsCopy );
453 }
454
455 compilerConfiguration.setFork( fork );
456
457 if ( fork )
458 {
459 if ( !StringUtils.isEmpty( meminitial ) )
460 {
461 String value = getMemoryValue( meminitial );
462
463 if ( value != null )
464 {
465 compilerConfiguration.setMeminitial( value );
466 }
467 else
468 {
469 getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
470 }
471 }
472
473 if ( !StringUtils.isEmpty( maxmem ) )
474 {
475 String value = getMemoryValue( maxmem );
476
477 if ( value != null )
478 {
479 compilerConfiguration.setMaxmem( value );
480 }
481 else
482 {
483 getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
484 }
485 }
486 }
487
488 compilerConfiguration.setExecutable( executable );
489
490 compilerConfiguration.setWorkingDirectory( basedir );
491
492 compilerConfiguration.setCompilerVersion( compilerVersion );
493
494 compilerConfiguration.setBuildDirectory( buildDirectory );
495
496 compilerConfiguration.setOutputFileName( outputFileName );
497
498
499 Set<File> staleSources;
500
501 boolean canUpdateTarget;
502
503 try
504 {
505 staleSources =
506 computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
507
508 canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
509
510 if ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) &&
511 !canUpdateTarget )
512 {
513 getLog().info( "RESCANNING!" );
514
515 String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
516
517 Set<File> sources = computeStaleSources( compilerConfiguration, compiler,
518 getSourceInclusionScanner( inputFileEnding ) );
519
520 compilerConfiguration.setSourceFiles( sources );
521 }
522 else
523 {
524 compilerConfiguration.setSourceFiles( staleSources );
525 }
526 }
527 catch ( CompilerException e )
528 {
529 throw new MojoExecutionException( "Error while computing stale sources.", e );
530 }
531
532 if ( staleSources.isEmpty() )
533 {
534 getLog().info( "Nothing to compile - all classes are up to date" );
535
536 return;
537 }
538
539
540
541
542
543 if ( getLog().isDebugEnabled() )
544 {
545 getLog().debug( "Classpath:" );
546
547 for ( String s : getClasspathElements() )
548 {
549 getLog().debug( " " + s );
550 }
551
552 getLog().debug( "Source roots:" );
553
554 for ( String root : getCompileSourceRoots() )
555 {
556 getLog().debug( " " + root );
557 }
558
559 try
560 {
561 if ( fork )
562 {
563 if ( compilerConfiguration.getExecutable() != null )
564 {
565 getLog().debug( "Excutable: " );
566 getLog().debug( " " + compilerConfiguration.getExecutable() );
567 }
568 }
569
570 String[] cl = compiler.createCommandLine( compilerConfiguration );
571 if ( cl != null && cl.length > 0 )
572 {
573 StringBuffer sb = new StringBuffer();
574 sb.append( cl[0] );
575 for ( int i = 1; i < cl.length; i++ )
576 {
577 sb.append( " " );
578 sb.append( cl[i] );
579 }
580 getLog().debug( "Command line options:" );
581 getLog().debug( sb );
582 }
583 }
584 catch ( CompilerException ce )
585 {
586 getLog().debug( ce );
587 }
588 }
589
590
591
592
593
594 if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
595 {
596 getLog().warn(
597 "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
598 + ", i.e. build is platform dependent!" );
599 }
600
601 List<CompilerError> messages;
602
603 try
604 {
605 messages = compiler.compile( compilerConfiguration );
606 }
607 catch ( Exception e )
608 {
609
610 throw new MojoExecutionException( "Fatal error compiling", e );
611 }
612
613 List<CompilerError> warnings = new ArrayList<CompilerError>();
614 List<CompilerError> errors = new ArrayList<CompilerError>();
615 if ( messages != null )
616 {
617 for ( CompilerError message : messages )
618 {
619 if ( message.isError() )
620 {
621 errors.add( message );
622 }
623 else
624 {
625 warnings.add( message );
626 }
627 }
628 }
629
630 if ( failOnError && !errors.isEmpty() )
631 {
632 if ( !warnings.isEmpty() )
633 {
634 getLog().info( "-------------------------------------------------------------" );
635 getLog().warn( "COMPILATION WARNING : " );
636 getLog().info( "-------------------------------------------------------------" );
637 for ( CompilerError warning : warnings )
638 {
639 getLog().warn( warning.toString() );
640 }
641 getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
642 getLog().info( "-------------------------------------------------------------" );
643 }
644
645 getLog().info( "-------------------------------------------------------------" );
646 getLog().error( "COMPILATION ERROR : " );
647 getLog().info( "-------------------------------------------------------------" );
648
649 for ( CompilerError error : errors )
650 {
651 getLog().error( error.toString() );
652 }
653 getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
654 getLog().info( "-------------------------------------------------------------" );
655
656 throw new CompilationFailureException( errors );
657 }
658 else
659 {
660 for ( CompilerError message : messages )
661 {
662 getLog().warn( message.toString() );
663 }
664 }
665 }
666
667 private String getMemoryValue( String setting )
668 {
669 String value = null;
670
671
672 if ( isDigits( setting ) )
673 {
674 value = setting + "m";
675 }
676 else
677 {
678 if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) ) &&
679 ( setting.toLowerCase().endsWith( "m" ) ) )
680 {
681 value = setting;
682 }
683 }
684 return value;
685 }
686
687
688
689 private Toolchain getToolchain()
690 {
691 Toolchain tc = null;
692 if ( toolchainManager != null )
693 {
694 tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
695 }
696 return tc;
697 }
698
699 private boolean isDigits( String string )
700 {
701 for ( int i = 0; i < string.length(); i++ )
702 {
703 if ( !Character.isDigit( string.charAt( i ) ) )
704 {
705 return false;
706 }
707 }
708 return true;
709 }
710
711 @SuppressWarnings( "unchecked" )
712 private Set<File> computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler,
713 SourceInclusionScanner scanner )
714 throws MojoExecutionException, CompilerException
715 {
716 CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
717
718 SourceMapping mapping;
719
720 File outputDirectory;
721
722 if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
723 {
724 mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ), compiler
725 .getOutputFileEnding( compilerConfiguration ) );
726
727 outputDirectory = getOutputDirectory();
728 }
729 else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
730 {
731 mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ), compiler
732 .getOutputFile( compilerConfiguration ) );
733
734 outputDirectory = buildDirectory;
735 }
736 else
737 {
738 throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." );
739 }
740
741 scanner.addSourceMapping( mapping );
742
743 Set<File> staleSources = new HashSet<File>();
744
745 for ( String sourceRoot : getCompileSourceRoots() )
746 {
747 File rootFile = new File( sourceRoot );
748
749 if ( !rootFile.isDirectory() )
750 {
751 continue;
752 }
753
754 try
755 {
756 staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) );
757 }
758 catch ( InclusionScanException e )
759 {
760 throw new MojoExecutionException(
761 "Error scanning source root: \'" + sourceRoot + "\' " + "for stale files to recompile.", e );
762 }
763 }
764
765 return staleSources;
766 }
767
768
769
770
771
772 private static List<String> removeEmptyCompileSourceRoots( List<String> compileSourceRootsList )
773 {
774 List<String> newCompileSourceRootsList = new ArrayList<String>();
775 if ( compileSourceRootsList != null )
776 {
777
778 for ( String srcDir : compileSourceRootsList )
779 {
780 if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() )
781 {
782 newCompileSourceRootsList.add( srcDir );
783 }
784 }
785 }
786 return newCompileSourceRootsList;
787 }
788 }