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