View Javadoc

1   package org.apache.maven.plugin.compiler;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.execution.MavenSession;
23  import org.apache.maven.plugin.AbstractMojo;
24  import org.apache.maven.plugin.MojoExecution;
25  import org.apache.maven.plugin.MojoExecutionException;
26  import org.apache.maven.plugins.annotations.Component;
27  import org.apache.maven.plugins.annotations.Parameter;
28  import org.apache.maven.shared.incremental.IncrementalBuildHelper;
29  import org.apache.maven.shared.utils.ReaderFactory;
30  import org.apache.maven.shared.utils.StringUtils;
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.CompilerMessage;
38  import org.codehaus.plexus.compiler.CompilerNotImplementedException;
39  import org.codehaus.plexus.compiler.CompilerOutputStyle;
40  import org.codehaus.plexus.compiler.CompilerResult;
41  import org.codehaus.plexus.compiler.manager.CompilerManager;
42  import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
43  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
44  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
45  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
46  import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
47  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
48  
49  import java.io.File;
50  import java.lang.reflect.Method;
51  import java.util.ArrayList;
52  import java.util.Date;
53  import java.util.HashSet;
54  import java.util.LinkedHashMap;
55  import java.util.List;
56  import java.util.Map;
57  import java.util.Set;
58  
59  /**
60   * TODO: At least one step could be optimized, currently the plugin will do two
61   * scans of all the source code if the compiler has to have the entire set of
62   * sources. This is currently the case for at least the C# compiler and most
63   * likely all the other .NET compilers too.
64   *
65   * @author others
66   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
67   * @version $Id: AbstractCompilerMojo.java 1410034 2012-11-15 21:40:58Z olamy $
68   * @since 2.0
69   */
70  public abstract class AbstractCompilerMojo
71      extends AbstractMojo
72  {
73      // ----------------------------------------------------------------------
74      // Configurables
75      // ----------------------------------------------------------------------
76  
77      /**
78       * Indicates whether the build will continue even if there are compilation errors.
79       *
80       * @since 2.0.2
81       */
82      @Parameter ( property = "maven.compiler.failOnError", defaultValue = "true" )
83      private boolean failOnError = true;
84  
85      /**
86       * Set to <code>true</code> to include debugging information in the compiled class files.
87       */
88      @Parameter ( property = "maven.compiler.debug", defaultValue = "true" )
89      private boolean debug = true;
90  
91      /**
92       * Set to <code>true</code> to show messages about what the compiler is doing.
93       */
94      @Parameter ( property = "maven.compiler.verbose", defaultValue = "false" )
95      private boolean verbose;
96  
97      /**
98       * Sets whether to show source locations where deprecated APIs are used.
99       */
100     @Parameter ( property = "maven.compiler.showDeprecation", defaultValue = "false" )
101     private boolean showDeprecation;
102 
103     /**
104      * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods.
105      */
106     @Parameter ( property = "maven.compiler.optimize", defaultValue = "false" )
107     private boolean optimize;
108 
109     /**
110      * Set to <code>true</code> to show compilation warnings.
111      */
112     @Parameter ( property = "maven.compiler.showWarnings", defaultValue = "false" )
113     private boolean showWarnings;
114 
115     /**
116      * The -source argument for the Java compiler.
117      */
118     @Parameter ( property = "maven.compiler.source", defaultValue = "1.5" )
119     protected String source;
120 
121     /**
122      * The -target argument for the Java compiler.
123      */
124     @Parameter ( property = "maven.compiler.target", defaultValue = "1.5" )
125     protected String target;
126 
127     /**
128      * The -encoding argument for the Java compiler.
129      *
130      * @since 2.1
131      */
132     @Parameter ( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
133     private String encoding;
134 
135     /**
136      * Sets the granularity in milliseconds of the last modification
137      * date for testing whether a source needs recompilation.
138      */
139     @Parameter ( property = "lastModGranularityMs", defaultValue = "0" )
140     private int staleMillis;
141 
142     /**
143      * The compiler id of the compiler to use. See this
144      * <a href="non-javac-compilers.html">guide</a> for more information.
145      */
146     @Parameter ( property = "maven.compiler.compilerId", defaultValue = "javac" )
147     private String compilerId;
148 
149     /**
150      * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>.
151      */
152     @Parameter ( property = "maven.compiler.compilerVersion" )
153     private String compilerVersion;
154 
155     /**
156      * Allows running the compiler in a separate process.
157      * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable.
158      */
159     @Parameter ( property = "maven.compiler.fork", defaultValue = "false" )
160     private boolean fork;
161 
162     /**
163      * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
164      * if {@link #fork} is set to <code>true</code>.
165      *
166      * @since 2.0.1
167      */
168     @Parameter ( property = "maven.compiler.meminitial" )
169     private String meminitial;
170 
171     /**
172      * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
173      * if {@link #fork} is set to <code>true</code>.
174      *
175      * @since 2.0.1
176      */
177     @Parameter ( property = "maven.compiler.maxmem" )
178     private String maxmem;
179 
180     /**
181      * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>.
182      */
183     @Parameter ( property = "maven.compiler.executable" )
184     private String executable;
185 
186     /**
187      * <p>
188      * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
189      * If not set, both compilation and annotation processing are performed at the same time.
190      * </p>
191      * <p>Allowed values are:</p>
192      * <ul>
193      * <li><code>none</code> - no annotation processing is performed.</li>
194      * <li><code>only</code> - only annotation processing is done, no compilation.</li>
195      * </ul>
196      *
197      * @since 2.2
198      */
199     @Parameter
200     private String proc;
201 
202     /**
203      * <p>
204      * Names of annotation processors to run. Only applies to JDK 1.6+
205      * If not set, the default annotation processors discovery process applies.
206      * </p>
207      *
208      * @since 2.2
209      */
210     @Parameter
211     private String[] annotationProcessors;
212 
213     /**
214      * <p>
215      * Sets the arguments to be passed to the compiler (prepending a dash) if {@link #fork} is set to <code>true</code>.
216      * </p>
217      * <p>
218      * This is because the list of valid arguments passed to a Java compiler
219      * varies based on the compiler version.
220      * </p>
221      * <p>
222      * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following:
223      * </p>
224      * <pre>
225      * &lt;compilerArguments&gt;
226      *   &lt;Xmaxerrs&gt;1000&lt;/Xmaxerrs&gt;
227      *   &lt;Xlint/&gt;
228      *   &lt;Xlint:-path/&gt;
229      *   &lt;Averbose&gt;true&lt;/Averbose&gt;
230      * &lt;/compilerArguments&gt;
231      * </pre>
232      *
233      * @since 2.0.1
234      */
235     @Parameter
236     protected Map<String, String> compilerArguments;
237 
238     /**
239      * <p>
240      * Sets the unformatted single argument string to be passed to the compiler if {@link #fork} is set to <code>true</code>.
241      * To pass multiple arguments such as <code>-Xmaxerrs 1000</code> (which are actually two arguments) you have to use {@link #compilerArguments}.
242      * </p>
243      * <p>
244      * This is because the list of valid arguments passed to a Java compiler
245      * varies based on the compiler version.
246      * </p>
247      */
248     @Parameter
249     protected String compilerArgument;
250 
251     /**
252      * Sets the name of the output file when compiling a set of
253      * sources to a single file.
254      * <p/>
255      * expression="${project.build.finalName}"
256      */
257     @Parameter
258     private String outputFileName;
259 
260     /**
261      * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a
262      * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>.
263      * If debug level is not specified, by default, nothing will be appended to <code>-g</code>.
264      * If debug is not turned on, this attribute will be ignored.
265      *
266      * @since 2.1
267      */
268     @Parameter ( property = "maven.compiler.debuglevel" )
269     private String debuglevel;
270 
271     /**
272      *
273      */
274     @Component
275     private ToolchainManager toolchainManager;
276 
277     // ----------------------------------------------------------------------
278     // Read-only parameters
279     // ----------------------------------------------------------------------
280 
281     /**
282      * The directory to run the compiler from if fork is true.
283      */
284     @Parameter ( defaultValue = "${basedir}", required = true, readonly = true )
285     private File basedir;
286 
287     /**
288      * The target directory of the compiler if fork is true.
289      */
290     @Parameter ( defaultValue = "${project.build.directory}", required = true, readonly = true )
291     private File buildDirectory;
292 
293     /**
294      * Plexus compiler manager.
295      */
296     @Component
297     private CompilerManager compilerManager;
298 
299     /**
300      * The current build session instance. This is used for toolchain manager API calls.
301      */
302     @Component
303     private MavenSession session;
304 
305     /**
306      * Strategy to re use javacc class created:
307      * <ul>
308      * <li><code>reuseCreated</code> (default): will reuse already created but in case of multi-threaded builds,
309      * each thread will have its own instance</li>
310      * <li><code>reuseSame</code>: the same Javacc class will be used for each compilation even for multi-threaded build</li>
311      * <li><code>alwaysNew</code>: a new Javacc class will be created for each compilation</li>
312      * </ul>
313      * Note this parameter value depends on the os/jdk you are using, but the default value should work on most of env.
314      *
315      * @since 2.5
316      */
317     @Parameter ( defaultValue = "${reuseCreated}", property = "maven.compiler.compilerReuseStrategy" )
318     private String compilerReuseStrategy = "reuseCreated";
319 
320     /**
321      * @since 2.5
322      */
323     @Parameter ( defaultValue = "false", property = "maven.compiler.skipMultiThreadWarning" )
324     private boolean skipMultiThreadWarning;
325 
326     /**
327      * compiler can now use javax.tools if available in your current jdk, you can disable this feature
328      * using -Dmaven.compiler.forceJavacCompilerUse=true or in the plugin configuration
329      *
330      * @since 2.6
331      */
332     @Parameter ( defaultValue = "false", property = "maven.compiler.forceJavacCompilerUse" )
333     private boolean forceJavacCompilerUse;
334 
335     /**
336      * @since 2.6 needed for storing the status for the incremental build support.
337      */
338     @Parameter ( property = "mojoExecution" )
339     private MojoExecution mojoExecution;
340 
341     /**
342      * We need this to determine the start timestamp of the build.
343      *
344      * @since 2.6
345      */
346     @Component
347     protected MavenSession mavenSession;
348 
349     protected abstract SourceInclusionScanner getSourceInclusionScanner( int staleMillis );
350 
351     protected abstract SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding );
352 
353     protected abstract List<String> getClasspathElements();
354 
355     protected abstract List<String> getCompileSourceRoots();
356 
357     protected abstract File getOutputDirectory();
358 
359     protected abstract String getSource();
360 
361     protected abstract String getTarget();
362 
363     protected abstract String getCompilerArgument();
364 
365     protected abstract Map<String, String> getCompilerArguments();
366 
367     protected abstract File getGeneratedSourcesDirectory();
368 
369     public void execute()
370         throws MojoExecutionException, CompilationFailureException
371     {
372         // ----------------------------------------------------------------------
373         // Look up the compiler. This is done before other code than can
374         // cause the mojo to return before the lookup is done possibly resulting
375         // in misconfigured POMs still building.
376         // ----------------------------------------------------------------------
377 
378         Compiler compiler;
379 
380         getLog().debug( "Using compiler '" + compilerId + "'." );
381 
382         try
383         {
384             compiler = compilerManager.getCompiler( compilerId );
385         }
386         catch ( NoSuchCompilerException e )
387         {
388             throw new MojoExecutionException( "No such compiler '" + e.getCompilerId() + "'." );
389         }
390 
391         //-----------toolchains start here ----------------------------------
392         //use the compilerId as identifier for toolchains as well.
393         Toolchain tc = getToolchain();
394         if ( tc != null )
395         {
396             getLog().info( "Toolchain in compiler-plugin: " + tc );
397             if ( executable != null )
398             {
399                 getLog().warn( "Toolchains are ignored, 'executable' parameter is set to " + executable );
400             }
401             else
402             {
403                 fork = true;
404                 //TODO somehow shaky dependency between compilerId and tool executable.
405                 executable = tc.findTool( compilerId );
406             }
407         }
408         // ----------------------------------------------------------------------
409         //
410         // ----------------------------------------------------------------------
411 
412         List<String> compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() );
413 
414         if ( compileSourceRoots.isEmpty() )
415         {
416             getLog().info( "No sources to compile" );
417 
418             return;
419         }
420 
421         if ( getLog().isDebugEnabled() )
422         {
423             getLog().debug( "Source directories: " + compileSourceRoots.toString().replace( ',', '\n' ) );
424             getLog().debug( "Classpath: " + getClasspathElements().toString().replace( ',', '\n' ) );
425             getLog().debug( "Output directory: " + getOutputDirectory() );
426         }
427 
428         // ----------------------------------------------------------------------
429         // Create the compiler configuration
430         // ----------------------------------------------------------------------
431 
432         CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
433 
434         compilerConfiguration.setOutputLocation( getOutputDirectory().getAbsolutePath() );
435 
436         compilerConfiguration.setClasspathEntries( getClasspathElements() );
437 
438         compilerConfiguration.setSourceLocations( compileSourceRoots );
439 
440         compilerConfiguration.setOptimize( optimize );
441 
442         compilerConfiguration.setDebug( debug );
443 
444         if ( debug && StringUtils.isNotEmpty( debuglevel ) )
445         {
446             String[] split = StringUtils.split( debuglevel, "," );
447             for ( int i = 0; i < split.length; i++ )
448             {
449                 if ( !( split[i].equalsIgnoreCase( "none" ) || split[i].equalsIgnoreCase( "lines" )
450                     || split[i].equalsIgnoreCase( "vars" ) || split[i].equalsIgnoreCase( "source" ) ) )
451                 {
452                     throw new IllegalArgumentException( "The specified debug level: '" + split[i] + "' is unsupported. "
453                                                             + "Legal values are 'none', 'lines', 'vars', and 'source'." );
454                 }
455             }
456             compilerConfiguration.setDebugLevel( debuglevel );
457         }
458 
459         compilerConfiguration.setVerbose( verbose );
460 
461         compilerConfiguration.setShowWarnings( showWarnings );
462 
463         compilerConfiguration.setShowDeprecation( showDeprecation );
464 
465         compilerConfiguration.setSourceVersion( getSource() );
466 
467         compilerConfiguration.setTargetVersion( getTarget() );
468 
469         compilerConfiguration.setProc( proc );
470 
471         compilerConfiguration.setGeneratedSourcesDirectory( getGeneratedSourcesDirectory() );
472 
473         compilerConfiguration.setAnnotationProcessors( annotationProcessors );
474 
475         compilerConfiguration.setSourceEncoding( encoding );
476 
477         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
478 
479         String effectiveCompilerArgument = getCompilerArgument();
480 
481         if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null ) )
482         {
483             LinkedHashMap<String, String> cplrArgsCopy = new LinkedHashMap<String, String>();
484             if ( effectiveCompilerArguments != null )
485             {
486                 for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
487                 {
488                     String key = me.getKey();
489                     String value = me.getValue();
490                     if ( !key.startsWith( "-" ) )
491                     {
492                         key = "-" + key;
493                     }
494 
495                     if ( key.startsWith( "-A" ) && StringUtils.isNotEmpty( value ) )
496                     {
497                         cplrArgsCopy.put( key + "=" + value, null );
498                     }
499                     else
500                     {
501                         cplrArgsCopy.put( key, value );
502                     }
503                 }
504             }
505             if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
506             {
507                 cplrArgsCopy.put( effectiveCompilerArgument, null );
508             }
509             compilerConfiguration.setCustomCompilerArguments( cplrArgsCopy );
510         }
511 
512         compilerConfiguration.setFork( fork );
513 
514         if ( fork )
515         {
516             if ( !StringUtils.isEmpty( meminitial ) )
517             {
518                 String value = getMemoryValue( meminitial );
519 
520                 if ( value != null )
521                 {
522                     compilerConfiguration.setMeminitial( value );
523                 }
524                 else
525                 {
526                     getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
527                 }
528             }
529 
530             if ( !StringUtils.isEmpty( maxmem ) )
531             {
532                 String value = getMemoryValue( maxmem );
533 
534                 if ( value != null )
535                 {
536                     compilerConfiguration.setMaxmem( value );
537                 }
538                 else
539                 {
540                     getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
541                 }
542             }
543         }
544 
545         compilerConfiguration.setExecutable( executable );
546 
547         compilerConfiguration.setWorkingDirectory( basedir );
548 
549         compilerConfiguration.setCompilerVersion( compilerVersion );
550 
551         compilerConfiguration.setBuildDirectory( buildDirectory );
552 
553         compilerConfiguration.setOutputFileName( outputFileName );
554 
555         if ( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals( this.compilerReuseStrategy ) )
556         {
557             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew );
558         }
559         else if ( CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy().equals(
560             this.compilerReuseStrategy ) )
561         {
562             if ( getRequestThreadCount() > 1 )
563             {
564                 if ( !skipMultiThreadWarning )
565                 {
566                     StringBuilder sb = new StringBuilder(
567                         "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." );
568                     sb.append( System.getProperty( "line.separator" ) );
569                     sb.append(
570                         "If your env is fine with reuseSame, you can skip this warning with the configuration field skipMultiThreadWarning or -Dmaven.compiler.skipMultiThreadWarning=true" );
571                     getLog().warn( sb.toString() );
572                 }
573             }
574             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseSame );
575         }
576         else
577         {
578 
579             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseCreated );
580         }
581 
582         getLog().debug( "CompilerReuseStrategy: " + compilerConfiguration.getCompilerReuseStrategy().getStrategy() );
583 
584         compilerConfiguration.setForceJavacCompilerUse( forceJavacCompilerUse );
585 
586         boolean canUpdateTarget;
587 
588         IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper( mojoExecution, mavenSession );
589 
590         try
591         {
592             canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
593             Set<File> sources = getCompileSources( compiler, compilerConfiguration );
594 
595             if ( ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
596                 && !canUpdateTarget ) || isDependencyChanged() || isSourceChanged( compilerConfiguration, compiler )
597                 || incrementalBuildHelper.inputFileTreeChanged( sources ) )
598             {
599                 getLog().info( "Changes detected - recompiling the module!" );
600 
601                 compilerConfiguration.setSourceFiles( sources );
602             }
603             else
604             {
605                 getLog().info( "Nothing to compile - all classes are up to date" );
606 
607                 return;
608             }
609         }
610         catch ( CompilerException e )
611         {
612             throw new MojoExecutionException( "Error while computing stale sources.", e );
613         }
614 
615         // ----------------------------------------------------------------------
616         // Dump configuration
617         // ----------------------------------------------------------------------
618 
619         if ( getLog().isDebugEnabled() )
620         {
621             getLog().debug( "Classpath:" );
622 
623             for ( String s : getClasspathElements() )
624             {
625                 getLog().debug( " " + s );
626             }
627 
628             getLog().debug( "Source roots:" );
629 
630             for ( String root : getCompileSourceRoots() )
631             {
632                 getLog().debug( " " + root );
633             }
634 
635             try
636             {
637                 if ( fork )
638                 {
639                     if ( compilerConfiguration.getExecutable() != null )
640                     {
641                         getLog().debug( "Excutable: " );
642                         getLog().debug( " " + compilerConfiguration.getExecutable() );
643                     }
644                 }
645 
646                 String[] cl = compiler.createCommandLine( compilerConfiguration );
647                 if ( cl != null && cl.length > 0 )
648                 {
649                     StringBuilder sb = new StringBuilder();
650                     sb.append( cl[0] );
651                     for ( int i = 1; i < cl.length; i++ )
652                     {
653                         sb.append( " " );
654                         sb.append( cl[i] );
655                     }
656                     getLog().debug( "Command line options:" );
657                     getLog().debug( sb );
658                 }
659             }
660             catch ( CompilerException ce )
661             {
662                 getLog().debug( ce );
663             }
664         }
665 
666         // ----------------------------------------------------------------------
667         // Compile!
668         // ----------------------------------------------------------------------
669 
670         if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
671         {
672             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
673                                + ", i.e. build is platform dependent!" );
674         }
675 
676         CompilerResult compilerResult;
677 
678         incrementalBuildHelper.beforeRebuildExecution( getOutputDirectory() );
679 
680         try
681         {
682             try
683             {
684                 compilerResult = compiler.performCompile( compilerConfiguration );
685             }
686             catch ( CompilerNotImplementedException cnie )
687             {
688                 List<CompilerError> messages = compiler.compile( compilerConfiguration );
689                 compilerResult = convertToCompilerResult( messages );
690             }
691         }
692         catch ( Exception e )
693         {
694             // TODO: don't catch Exception
695             throw new MojoExecutionException( "Fatal error compiling", e );
696         }
697 
698         // now scan the same directory again and create a diff
699         incrementalBuildHelper.afterRebuildExecution();
700 
701         List<CompilerMessage> warnings = new ArrayList<CompilerMessage>();
702         List<CompilerMessage> errors = new ArrayList<CompilerMessage>();
703         for ( CompilerMessage message : compilerResult.getCompilerMessages() )
704         {
705             if ( message.isError() )
706             {
707                 errors.add( message );
708             }
709             else
710             {
711                 warnings.add( message );
712             }
713         }
714 
715         if ( failOnError && !compilerResult.isSuccess() )
716         {
717             if ( !warnings.isEmpty() )
718             {
719                 getLog().info( "-------------------------------------------------------------" );
720                 getLog().warn( "COMPILATION WARNING : " );
721                 getLog().info( "-------------------------------------------------------------" );
722                 for ( CompilerMessage warning : warnings )
723                 {
724                     getLog().warn( warning.toString() );
725                 }
726                 getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
727                 getLog().info( "-------------------------------------------------------------" );
728             }
729 
730             if ( !errors.isEmpty() )
731             {
732                 getLog().info( "-------------------------------------------------------------" );
733                 getLog().error( "COMPILATION ERROR : " );
734                 getLog().info( "-------------------------------------------------------------" );
735                 for ( CompilerMessage error : errors )
736                 {
737                     getLog().error( error.toString() );
738                 }
739                 getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
740                 getLog().info( "-------------------------------------------------------------" );
741             }
742 
743             if ( !errors.isEmpty() )
744             {
745                 throw new CompilationFailureException( errors );
746             }
747             else
748             {
749                 throw new CompilationFailureException( warnings );
750             }
751         }
752         else
753         {
754             for ( CompilerMessage message : compilerResult.getCompilerMessages() )
755             {
756                 getLog().warn( message.toString() );
757             }
758         }
759     }
760 
761     protected CompilerResult convertToCompilerResult( List<CompilerError> compilerErrors )
762     {
763         if ( compilerErrors == null )
764         {
765             return new CompilerResult();
766         }
767         List<CompilerMessage> messages = new ArrayList<CompilerMessage>( compilerErrors.size() );
768         boolean success = true;
769         for ( CompilerError compilerError : compilerErrors )
770         {
771             messages.add(
772                 new CompilerMessage( compilerError.getFile(), compilerError.getKind(), compilerError.getStartLine(),
773                                      compilerError.getStartColumn(), compilerError.getEndLine(),
774                                      compilerError.getEndColumn(), compilerError.getMessage() ) );
775             if ( compilerError.isError() )
776             {
777                 success = false;
778             }
779         }
780 
781         return new CompilerResult( success, messages );
782     }
783 
784     /**
785      * @return all source files for the compiler
786      */
787     private Set<File> getCompileSources( Compiler compiler, CompilerConfiguration compilerConfiguration )
788         throws MojoExecutionException, CompilerException
789     {
790         String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
791         SourceInclusionScanner scanner = getSourceInclusionScanner( inputFileEnding );
792 
793         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
794 
795         scanner.addSourceMapping( mapping );
796 
797         Set<File> compileSources = new HashSet<File>();
798 
799         for ( String sourceRoot : getCompileSourceRoots() )
800         {
801             File rootFile = new File( sourceRoot );
802 
803             if ( !rootFile.isDirectory() )
804             {
805                 continue;
806             }
807 
808             try
809             {
810                 compileSources.addAll( scanner.getIncludedSources( rootFile, null ) );
811             }
812             catch ( InclusionScanException e )
813             {
814                 throw new MojoExecutionException(
815                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
816             }
817         }
818 
819         return compileSources;
820     }
821 
822     /**
823      * @param compilerConfiguration
824      * @param compiler
825      * @return <code>true</code> if at least a single source file is newer than it's class file
826      */
827     private boolean isSourceChanged( CompilerConfiguration compilerConfiguration, Compiler compiler )
828         throws CompilerException, MojoExecutionException
829     {
830         Set<File> staleSources =
831             computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
832 
833         return staleSources != null && staleSources.size() > 0;
834     }
835 
836 
837     /**
838      * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependant
839      *
840      * @return number of thread for this build or 1 if not multi-thread build
841      */
842     protected int getRequestThreadCount()
843     {
844         try
845         {
846             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
847             Object mavenExecutionRequest = getRequestMethod.invoke( this.session );
848             Method getThreadCountMethod = mavenExecutionRequest.getClass().getMethod( "getThreadCount" );
849             String threadCount = (String) getThreadCountMethod.invoke( mavenExecutionRequest );
850             return Integer.valueOf( threadCount );
851         }
852         catch ( Exception e )
853         {
854             getLog().debug( "unable to get threadCount for the current build: " + e.getMessage() );
855         }
856         return 1;
857     }
858 
859     protected Date getBuildStartTime()
860     {
861         try
862         {
863             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
864             Object mavenExecutionRequest = getRequestMethod.invoke( session );
865             Method getStartTimeMethod = mavenExecutionRequest.getClass().getMethod( "getStartTime" );
866             Date buildStartTime = (Date) getStartTimeMethod.invoke( mavenExecutionRequest );
867             return buildStartTime;
868         }
869         catch ( Exception e )
870         {
871             getLog().debug( "unable to get start time for the current build: " + e.getMessage() );
872         }
873 
874         return new Date();
875     }
876 
877 
878     private String getMemoryValue( String setting )
879     {
880         String value = null;
881 
882         // Allow '128' or '128m'
883         if ( isDigits( setting ) )
884         {
885             value = setting + "m";
886         }
887         else
888         {
889             if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) ) && ( setting.toLowerCase().endsWith(
890                 "m" ) ) )
891             {
892                 value = setting;
893             }
894         }
895         return value;
896     }
897 
898     //TODO remove the part with ToolchainManager lookup once we depend on
899     //3.0.9 (have it as prerequisite). Define as regular component field then.
900     private Toolchain getToolchain()
901     {
902         Toolchain tc = null;
903         if ( toolchainManager != null )
904         {
905             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
906         }
907         return tc;
908     }
909 
910     private boolean isDigits( String string )
911     {
912         for ( int i = 0; i < string.length(); i++ )
913         {
914             if ( !Character.isDigit( string.charAt( i ) ) )
915             {
916                 return false;
917             }
918         }
919         return true;
920     }
921 
922     private Set<File> computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler,
923                                            SourceInclusionScanner scanner )
924         throws MojoExecutionException, CompilerException
925     {
926         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
927 
928         File outputDirectory;
929         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
930         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
931         {
932             outputDirectory = buildDirectory;
933         }
934         else
935         {
936             outputDirectory = getOutputDirectory();
937         }
938 
939         scanner.addSourceMapping( mapping );
940 
941         Set<File> staleSources = new HashSet<File>();
942 
943         for ( String sourceRoot : getCompileSourceRoots() )
944         {
945             File rootFile = new File( sourceRoot );
946 
947             if ( !rootFile.isDirectory() )
948             {
949                 continue;
950             }
951 
952             try
953             {
954                 staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) );
955             }
956             catch ( InclusionScanException e )
957             {
958                 throw new MojoExecutionException(
959                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
960             }
961         }
962 
963         return staleSources;
964     }
965 
966     private SourceMapping getSourceMapping( CompilerConfiguration compilerConfiguration, Compiler compiler )
967         throws CompilerException, MojoExecutionException
968     {
969         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
970 
971         SourceMapping mapping;
972         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
973         {
974             mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ),
975                                          compiler.getOutputFileEnding( compilerConfiguration ) );
976         }
977         else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
978         {
979             mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ),
980                                                      compiler.getOutputFile( compilerConfiguration ) );
981 
982         }
983         else
984         {
985             throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." );
986         }
987         return mapping;
988     }
989 
990     /**
991      * @todo also in ant plugin. This should be resolved at some point so that it does not need to
992      * be calculated continuously - or should the plugins accept empty source roots as is?
993      */
994     private static List<String> removeEmptyCompileSourceRoots( List<String> compileSourceRootsList )
995     {
996         List<String> newCompileSourceRootsList = new ArrayList<String>();
997         if ( compileSourceRootsList != null )
998         {
999             // copy as I may be modifying it
1000             for ( String srcDir : compileSourceRootsList )
1001             {
1002                 if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() )
1003                 {
1004                     newCompileSourceRootsList.add( srcDir );
1005                 }
1006             }
1007         }
1008         return newCompileSourceRootsList;
1009     }
1010 
1011     /**
1012      * We just compare the timestamps of all local dependency files (inter-module dependency classpath)
1013      * and the own generated classes
1014      * and if we got a file which is >= the buid-started timestamp, then we catched a file which got
1015      * changed during this build.
1016      *
1017      * @return <code>true</code> if at least one single dependency has changed.
1018      */
1019     protected boolean isDependencyChanged()
1020     {
1021         if ( mavenSession == null )
1022         {
1023             // we just cannot determine it, so don't do anything beside logging
1024             getLog().info( "Cannot determine build start date, skipping incremental build detection." );
1025             return false;
1026         }
1027 
1028         Date buildStartTime = getBuildStartTime();
1029 
1030         for ( String classPathElement : getClasspathElements() )
1031         {
1032             // ProjectArtifacts are artifacts which are available in the local project
1033             // that's the only ones we are interested in now.
1034             File artifactPath = new File( classPathElement );
1035             if ( artifactPath.isDirectory() )
1036             {
1037                 if ( hasNewFile( artifactPath, buildStartTime ) )
1038                 {
1039                     return true;
1040                 }
1041             }
1042         }
1043 
1044         // obviously there was no new file detected.
1045         return false;
1046     }
1047 
1048     private boolean hasNewFile( File classPathEntry, Date buildStartTime )
1049     {
1050         if ( !classPathEntry.exists() )
1051         {
1052             return false;
1053         }
1054 
1055         if ( classPathEntry.isFile() )
1056         {
1057             return classPathEntry.lastModified() >= buildStartTime.getTime();
1058         }
1059 
1060         File[] children = classPathEntry.listFiles();
1061 
1062         for ( File child : children )
1063         {
1064             if ( hasNewFile( child, buildStartTime ) )
1065             {
1066                 return true;
1067             }
1068         }
1069 
1070         return false;
1071     }
1072 }