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