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