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 }