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 java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.nio.charset.Charset;
28  import java.nio.file.Files;
29  import java.nio.file.Path;
30  import java.nio.file.Paths;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Date;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.LinkedHashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Properties;
40  import java.util.Set;
41  
42  import org.apache.maven.artifact.Artifact;
43  import org.apache.maven.artifact.DefaultArtifact;
44  import org.apache.maven.artifact.handler.ArtifactHandler;
45  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
46  import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
47  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
48  import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
49  import org.apache.maven.artifact.versioning.VersionRange;
50  import org.apache.maven.execution.MavenSession;
51  import org.apache.maven.plugin.AbstractMojo;
52  import org.apache.maven.plugin.MojoExecution;
53  import org.apache.maven.plugin.MojoExecutionException;
54  import org.apache.maven.plugins.annotations.Component;
55  import org.apache.maven.plugins.annotations.Parameter;
56  import org.apache.maven.project.MavenProject;
57  import org.apache.maven.repository.RepositorySystem;
58  import org.apache.maven.shared.incremental.IncrementalBuildHelper;
59  import org.apache.maven.shared.incremental.IncrementalBuildHelperRequest;
60  import org.apache.maven.shared.utils.ReaderFactory;
61  import org.apache.maven.shared.utils.StringUtils;
62  import org.apache.maven.shared.utils.io.FileUtils;
63  import org.apache.maven.shared.utils.logging.MessageBuilder;
64  import org.apache.maven.shared.utils.logging.MessageUtils;
65  import org.apache.maven.toolchain.Toolchain;
66  import org.apache.maven.toolchain.ToolchainManager;
67  import org.codehaus.plexus.compiler.Compiler;
68  import org.codehaus.plexus.compiler.CompilerConfiguration;
69  import org.codehaus.plexus.compiler.CompilerError;
70  import org.codehaus.plexus.compiler.CompilerException;
71  import org.codehaus.plexus.compiler.CompilerMessage;
72  import org.codehaus.plexus.compiler.CompilerNotImplementedException;
73  import org.codehaus.plexus.compiler.CompilerOutputStyle;
74  import org.codehaus.plexus.compiler.CompilerResult;
75  import org.codehaus.plexus.compiler.manager.CompilerManager;
76  import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
77  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
78  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
79  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
80  import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
81  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
82  import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
83  import org.codehaus.plexus.languages.java.version.JavaVersion;
84  
85  /**
86   * TODO: At least one step could be optimized, currently the plugin will do two
87   * scans of all the source code if the compiler has to have the entire set of
88   * sources. This is currently the case for at least the C# compiler and most
89   * likely all the other .NET compilers too.
90   *
91   * @author others
92   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
93   * @version $Id$
94   * @since 2.0
95   */
96  public abstract class AbstractCompilerMojo
97      extends AbstractMojo
98  {
99      protected static final String PS = System.getProperty( "path.separator" );
100 
101     static final String DEFAULT_SOURCE = "1.6";
102     
103     static final String DEFAULT_TARGET = "1.6";
104     
105     // Used to compare with older targets
106     static final String MODULE_INFO_TARGET = "1.9";
107     
108     // ----------------------------------------------------------------------
109     // Configurables
110     // ----------------------------------------------------------------------
111 
112     /**
113      * Indicates whether the build will continue even if there are compilation errors.
114      *
115      * @since 2.0.2
116      */
117     @Parameter( property = "maven.compiler.failOnError", defaultValue = "true" )
118     private boolean failOnError = true;
119     
120     /**
121      * Indicates whether the build will continue even if there are compilation warnings.
122      *
123      * @since 3.6
124      */
125     @Parameter( property = "maven.compiler.failOnWarning", defaultValue = "false" )
126     private boolean failOnWarning;  
127 
128     /**
129      * Set to <code>true</code> to include debugging information in the compiled class files.
130      */
131     @Parameter( property = "maven.compiler.debug", defaultValue = "true" )
132     private boolean debug = true;
133 
134     /**
135      * Set to <code>true</code> to generate metadata for reflection on method parameters.
136      * @since 3.6.2
137      */
138     @Parameter( property = "maven.compiler.parameters", defaultValue = "false" )
139     private boolean parameters;
140 
141     /**
142      * Set to <code>true</code> to show messages about what the compiler is doing.
143      */
144     @Parameter( property = "maven.compiler.verbose", defaultValue = "false" )
145     private boolean verbose;
146 
147     /**
148      * Sets whether to show source locations where deprecated APIs are used.
149      */
150     @Parameter( property = "maven.compiler.showDeprecation", defaultValue = "false" )
151     private boolean showDeprecation;
152 
153     /**
154      * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods.
155      * @deprecated This property is a no-op in {@code javac}.
156      */
157     @Deprecated
158     @Parameter( property = "maven.compiler.optimize", defaultValue = "false" )
159     private boolean optimize;
160 
161     /**
162      * Set to <code>true</code> to show compilation warnings.
163      */
164     @Parameter( property = "maven.compiler.showWarnings", defaultValue = "false" )
165     private boolean showWarnings;
166 
167     /**
168      * <p>The -source argument for the Java compiler.</p>
169      * 
170      * <b>NOTE: </b>Since 3.8.0 the default value has changed from 1.5 to 1.6
171      */
172     @Parameter( property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE )
173     protected String source;
174 
175     /**
176      * <p>The -target argument for the Java compiler.</p>
177      * 
178      * <b>NOTE: </b>Since 3.8.0 the default value has changed from 1.5 to 1.6
179      */
180     @Parameter( property = "maven.compiler.target", defaultValue = DEFAULT_TARGET )
181     protected String target;
182 
183     /**
184      * The -release argument for the Java compiler, supported since Java9
185      * 
186      * @since 3.6
187      */
188     @Parameter( property = "maven.compiler.release" )
189     protected String release;
190     
191     /**
192      * The -encoding argument for the Java compiler.
193      *
194      * @since 2.1
195      */
196     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
197     private String encoding;
198 
199     /**
200      * Sets the granularity in milliseconds of the last modification
201      * date for testing whether a source needs recompilation.
202      */
203     @Parameter( property = "lastModGranularityMs", defaultValue = "0" )
204     private int staleMillis;
205 
206     /**
207      * The compiler id of the compiler to use. See this
208      * <a href="non-javac-compilers.html">guide</a> for more information.
209      */
210     @Parameter( property = "maven.compiler.compilerId", defaultValue = "javac" )
211     private String compilerId;
212 
213     /**
214      * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>.
215      */
216     @Parameter( property = "maven.compiler.compilerVersion" )
217     private String compilerVersion;
218 
219     /**
220      * Allows running the compiler in a separate process.
221      * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable.
222      */
223     @Parameter( property = "maven.compiler.fork", defaultValue = "false" )
224     private boolean fork;
225 
226     /**
227      * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
228      * if {@link #fork} is set to <code>true</code>.
229      *
230      * @since 2.0.1
231      */
232     @Parameter( property = "maven.compiler.meminitial" )
233     private String meminitial;
234 
235     /**
236      * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
237      * if {@link #fork} is set to <code>true</code>.
238      *
239      * @since 2.0.1
240      */
241     @Parameter( property = "maven.compiler.maxmem" )
242     private String maxmem;
243 
244     /**
245      * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>.
246      */
247     @Parameter( property = "maven.compiler.executable" )
248     private String executable;
249 
250     /**
251      * <p>
252      * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
253      * If not set, both compilation and annotation processing are performed at the same time.
254      * </p>
255      * <p>Allowed values are:</p>
256      * <ul>
257      * <li><code>none</code> - no annotation processing is performed.</li>
258      * <li><code>only</code> - only annotation processing is done, no compilation.</li>
259      * </ul>
260      *
261      * @since 2.2
262      */
263     @Parameter
264     private String proc;
265 
266     /**
267      * <p>
268      * Names of annotation processors to run. Only applies to JDK 1.6+
269      * If not set, the default annotation processors discovery process applies.
270      * </p>
271      *
272      * @since 2.2
273      */
274     @Parameter
275     private String[] annotationProcessors;
276 
277     /**
278      * <p>
279      * Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation
280      * processors only in those classpath elements. If omitted, the default classpath is used to detect annotation
281      * processors. The detection itself depends on the configuration of {@code annotationProcessors}.
282      * </p>
283      * <p>
284      * Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier,
285      * type). Transitive dependencies are added automatically. Example:
286      * </p>
287      *
288      * <pre>
289      * &lt;configuration&gt;
290      *   &lt;annotationProcessorPaths&gt;
291      *     &lt;path&gt;
292      *       &lt;groupId&gt;org.sample&lt;/groupId&gt;
293      *       &lt;artifactId&gt;sample-annotation-processor&lt;/artifactId&gt;
294      *       &lt;version&gt;1.2.3&lt;/version&gt;
295      *     &lt;/path&gt;
296      *     &lt;!-- ... more ... --&gt;
297      *   &lt;/annotationProcessorPaths&gt;
298      * &lt;/configuration&gt;
299      * </pre>
300      *
301      * @since 3.5
302      */
303     @Parameter
304     private List<DependencyCoordinate> annotationProcessorPaths;
305 
306     /**
307      * <p>
308      * Sets the arguments to be passed to the compiler (prepending a dash) if {@link #fork} is set to <code>true</code>.
309      * </p>
310      * <p>
311      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
312      * </p>
313      * <p>
314      * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following:
315      * </p>
316      *
317      * <pre>
318      * &lt;compilerArguments&gt;
319      *   &lt;Xmaxerrs&gt;1000&lt;/Xmaxerrs&gt;
320      *   &lt;Xlint/&gt;
321      *   &lt;Xlint:-path/&gt;
322      *   &lt;Averbose&gt;true&lt;/Averbose&gt;
323      * &lt;/compilerArguments&gt;
324      * </pre>
325      *
326      * @since 2.0.1
327      * @deprecated use {@link #compilerArgs} instead.
328      */
329     @Parameter
330     @Deprecated
331     protected Map<String, String> compilerArguments;
332 
333     /**
334      * <p>
335      * Sets the arguments to be passed to the compiler if {@link #fork} is set to <code>true</code>.
336      * Example:
337      * <pre>
338      * &lt;compilerArgs&gt;
339      *   &lt;arg&gt;-Xmaxerrs=1000&lt;/arg&gt;
340      *   &lt;arg&gt;-Xlint&lt;/arg&gt;
341      *   &lt;arg&gt;-J-Duser.language=en_us&lt;/arg&gt;
342      * &lt;/compilerArgs&gt;
343      * </pre>
344      *
345      * @since 3.1
346      */
347     @Parameter
348     protected List<String> compilerArgs;
349 
350     /**
351      * <p>
352      * Sets the unformatted single argument string to be passed to the compiler if {@link #fork} is set to
353      * <code>true</code>. To pass multiple arguments such as <code>-Xmaxerrs 1000</code> (which are actually two
354      * arguments) you have to use {@link #compilerArguments}.
355      * </p>
356      * <p>
357      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
358      * </p>
359      */
360     @Parameter
361     protected String compilerArgument;
362 
363     /**
364      * Sets the name of the output file when compiling a set of
365      * sources to a single file.
366      * <p/>
367      * expression="${project.build.finalName}"
368      */
369     @Parameter
370     private String outputFileName;
371 
372     /**
373      * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a
374      * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>.
375      * If debug level is not specified, by default, nothing will be appended to <code>-g</code>.
376      * If debug is not turned on, this attribute will be ignored.
377      *
378      * @since 2.1
379      */
380     @Parameter( property = "maven.compiler.debuglevel" )
381     private String debuglevel;
382 
383     /**
384      *
385      */
386     @Component
387     private ToolchainManager toolchainManager;
388 
389     /**
390      * <p>
391      * Specify the requirements for this jdk toolchain.
392      * This overrules the toolchain selected by the maven-toolchain-plugin.
393      * </p>
394      * <strong>note:</strong> requires at least Maven 3.3.1
395      * 
396      * @since 3.6
397      */
398     @Parameter
399     private Map<String, String> jdkToolchain;
400 
401     // ----------------------------------------------------------------------
402     // Read-only parameters
403     // ----------------------------------------------------------------------
404 
405     /**
406      * The directory to run the compiler from if fork is true.
407      */
408     @Parameter( defaultValue = "${basedir}", required = true, readonly = true )
409     private File basedir;
410 
411     /**
412      * The target directory of the compiler if fork is true.
413      */
414     @Parameter( defaultValue = "${project.build.directory}", required = true, readonly = true )
415     private File buildDirectory;
416 
417     /**
418      * Plexus compiler manager.
419      */
420     @Component
421     private CompilerManager compilerManager;
422 
423     /**
424      * The current build session instance. This is used for toolchain manager API calls.
425      */
426     @Parameter( defaultValue = "${session}", readonly = true, required = true )
427     private MavenSession session;
428 
429     /**
430      * The current project instance. This is used for propagating generated-sources paths as compile/testCompile source
431      * roots.
432      */
433     @Parameter( defaultValue = "${project}", readonly = true, required = true )
434     private MavenProject project;
435 
436     /**
437      * Strategy to re use javacc class created:
438      * <ul>
439      * <li><code>reuseCreated</code> (default): will reuse already created but in case of multi-threaded builds, each
440      * thread will have its own instance</li>
441      * <li><code>reuseSame</code>: the same Javacc class will be used for each compilation even for multi-threaded build
442      * </li>
443      * <li><code>alwaysNew</code>: a new Javacc class will be created for each compilation</li>
444      * </ul>
445      * Note this parameter value depends on the os/jdk you are using, but the default value should work on most of env.
446      *
447      * @since 2.5
448      */
449     @Parameter( defaultValue = "${reuseCreated}", property = "maven.compiler.compilerReuseStrategy" )
450     private String compilerReuseStrategy = "reuseCreated";
451 
452     /**
453      * @since 2.5
454      */
455     @Parameter( defaultValue = "false", property = "maven.compiler.skipMultiThreadWarning" )
456     private boolean skipMultiThreadWarning;
457 
458     /**
459      * compiler can now use javax.tools if available in your current jdk, you can disable this feature
460      * using -Dmaven.compiler.forceJavacCompilerUse=true or in the plugin configuration
461      *
462      * @since 3.0
463      */
464     @Parameter( defaultValue = "false", property = "maven.compiler.forceJavacCompilerUse" )
465     private boolean forceJavacCompilerUse;
466 
467     /**
468      * @since 3.0 needed for storing the status for the incremental build support.
469      */
470     @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
471     private MojoExecution mojoExecution;
472 
473     /**
474      * file extensions to check timestamp for incremental build
475      * <b>default contains only <code>.class</code></b>
476      *
477      * @since 3.1
478      */
479     @Parameter
480     private List<String> fileExtensions;
481 
482     /**
483      * to enable/disable incrementation compilation feature
484      * @since 3.1
485      */
486     @Parameter( defaultValue = "true", property = "maven.compiler.useIncrementalCompilation" )
487     private boolean useIncrementalCompilation = true;
488 
489     /**
490      * Resolves the artifacts needed.
491      */
492     @Component
493     private RepositorySystem repositorySystem;
494 
495     /**
496      * Artifact handler manager.
497      */
498     @Component
499     private ArtifactHandlerManager artifactHandlerManager;
500 
501     /**
502      * Throws an exception on artifact resolution errors.
503      */
504     @Component
505     private ResolutionErrorHandler resolutionErrorHandler;
506 
507     protected abstract SourceInclusionScanner getSourceInclusionScanner( int staleMillis );
508 
509     protected abstract SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding );
510 
511     protected abstract List<String> getClasspathElements();
512 
513     protected abstract List<String> getModulepathElements();
514 
515     protected abstract Map<String, JavaModuleDescriptor> getPathElements();
516 
517     protected abstract List<String> getCompileSourceRoots();
518     
519     protected abstract void preparePaths( Set<File> sourceFiles );
520 
521     protected abstract File getOutputDirectory();
522 
523     protected abstract String getSource();
524 
525     protected abstract String getTarget();
526 
527     protected abstract String getRelease();
528 
529     protected abstract String getCompilerArgument();
530 
531     protected abstract Map<String, String> getCompilerArguments();
532 
533     protected abstract File getGeneratedSourcesDirectory();
534 
535     protected final MavenProject getProject()
536     {
537         return project;
538     }
539 
540     private boolean targetOrReleaseSet;
541 
542     @Override
543     public void execute()
544         throws MojoExecutionException, CompilationFailureException
545     {
546         // ----------------------------------------------------------------------
547         // Look up the compiler. This is done before other code than can
548         // cause the mojo to return before the lookup is done possibly resulting
549         // in misconfigured POMs still building.
550         // ----------------------------------------------------------------------
551 
552         Compiler compiler;
553 
554         getLog().debug( "Using compiler '" + compilerId + "'." );
555 
556         try
557         {
558             compiler = compilerManager.getCompiler( compilerId );
559         }
560         catch ( NoSuchCompilerException e )
561         {
562             throw new MojoExecutionException( "No such compiler '" + e.getCompilerId() + "'." );
563         }
564 
565         //-----------toolchains start here ----------------------------------
566         //use the compilerId as identifier for toolchains as well.
567         Toolchain tc = getToolchain();
568         if ( tc != null )
569         {
570             getLog().info( "Toolchain in maven-compiler-plugin: " + tc );
571             if ( executable != null )
572             {
573                 getLog().warn( "Toolchains are ignored, 'executable' parameter is set to " + executable );
574             }
575             else
576             {
577                 fork = true;
578                 //TODO somehow shaky dependency between compilerId and tool executable.
579                 executable = tc.findTool( compilerId );
580             }
581         }
582         // ----------------------------------------------------------------------
583         //
584         // ----------------------------------------------------------------------
585 
586         List<String> compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() );
587 
588         if ( compileSourceRoots.isEmpty() )
589         {
590             getLog().info( "No sources to compile" );
591 
592             return;
593         }
594 
595         // Verify that target or release is set
596         if ( !targetOrReleaseSet )
597         {
598             MessageBuilder mb = MessageUtils.buffer().a( "No explicit value set for target or release! " )
599                             .a( "To ensure the same result even after upgrading this plugin, please add " ).newline()
600                             .newline();
601 
602             writePlugin( mb );
603 
604             getLog().warn( mb.toString() );
605         }
606 
607         // ----------------------------------------------------------------------
608         // Create the compiler configuration
609         // ----------------------------------------------------------------------
610 
611         CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
612 
613         compilerConfiguration.setOutputLocation( getOutputDirectory().getAbsolutePath() );
614 
615         compilerConfiguration.setOptimize( optimize );
616 
617         compilerConfiguration.setDebug( debug );
618 
619         if ( debug && StringUtils.isNotEmpty( debuglevel ) )
620         {
621             String[] split = StringUtils.split( debuglevel, "," );
622             for ( String aSplit : split )
623             {
624                 if ( !( aSplit.equalsIgnoreCase( "none" ) || aSplit.equalsIgnoreCase( "lines" )
625                     || aSplit.equalsIgnoreCase( "vars" ) || aSplit.equalsIgnoreCase( "source" ) ) )
626                 {
627                     throw new IllegalArgumentException( "The specified debug level: '" + aSplit + "' is unsupported. "
628                         + "Legal values are 'none', 'lines', 'vars', and 'source'." );
629                 }
630             }
631             compilerConfiguration.setDebugLevel( debuglevel );
632         }
633 
634         compilerConfiguration.setParameters( parameters );
635 
636         compilerConfiguration.setVerbose( verbose );
637 
638         compilerConfiguration.setShowWarnings( showWarnings );
639 
640         compilerConfiguration.setFailOnWarning( failOnWarning );
641 
642         compilerConfiguration.setShowDeprecation( showDeprecation );
643 
644         compilerConfiguration.setSourceVersion( getSource() );
645 
646         compilerConfiguration.setTargetVersion( getTarget() );
647         
648         compilerConfiguration.setReleaseVersion( getRelease() );
649 
650         compilerConfiguration.setProc( proc );
651 
652         File generatedSourcesDirectory = getGeneratedSourcesDirectory();
653         compilerConfiguration.setGeneratedSourcesDirectory( generatedSourcesDirectory != null
654                         ? generatedSourcesDirectory.getAbsoluteFile() : null );
655 
656         if ( generatedSourcesDirectory != null )
657         {
658             String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath();
659 
660             compileSourceRoots.add( generatedSourcesPath );
661 
662             if ( isTestCompile() )
663             {
664                 getLog().debug( "Adding " + generatedSourcesPath + " to test-compile source roots:\n  "
665                                     + StringUtils.join( project.getTestCompileSourceRoots()
666                                                                .iterator(), "\n  " ) );
667 
668                 project.addTestCompileSourceRoot( generatedSourcesPath );
669 
670                 getLog().debug( "New test-compile source roots:\n  "
671                                     + StringUtils.join( project.getTestCompileSourceRoots()
672                                                                .iterator(), "\n  " ) );
673             }
674             else
675             {
676                 getLog().debug( "Adding " + generatedSourcesPath + " to compile source roots:\n  "
677                                     + StringUtils.join( project.getCompileSourceRoots()
678                                                                .iterator(), "\n  " ) );
679 
680                 project.addCompileSourceRoot( generatedSourcesPath );
681 
682                 getLog().debug( "New compile source roots:\n  " + StringUtils.join( project.getCompileSourceRoots()
683                                                                                            .iterator(), "\n  " ) );
684             }
685         }
686 
687         compilerConfiguration.setSourceLocations( compileSourceRoots );
688 
689         compilerConfiguration.setAnnotationProcessors( annotationProcessors );
690 
691         compilerConfiguration.setProcessorPathEntries( resolveProcessorPathEntries() );
692 
693         compilerConfiguration.setSourceEncoding( encoding );
694 
695         compilerConfiguration.setFork( fork );
696 
697         if ( fork )
698         {
699             if ( !StringUtils.isEmpty( meminitial ) )
700             {
701                 String value = getMemoryValue( meminitial );
702 
703                 if ( value != null )
704                 {
705                     compilerConfiguration.setMeminitial( value );
706                 }
707                 else
708                 {
709                     getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
710                 }
711             }
712 
713             if ( !StringUtils.isEmpty( maxmem ) )
714             {
715                 String value = getMemoryValue( maxmem );
716 
717                 if ( value != null )
718                 {
719                     compilerConfiguration.setMaxmem( value );
720                 }
721                 else
722                 {
723                     getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
724                 }
725             }
726         }
727 
728         compilerConfiguration.setExecutable( executable );
729 
730         compilerConfiguration.setWorkingDirectory( basedir );
731 
732         compilerConfiguration.setCompilerVersion( compilerVersion );
733 
734         compilerConfiguration.setBuildDirectory( buildDirectory );
735 
736         compilerConfiguration.setOutputFileName( outputFileName );
737 
738         if ( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals( this.compilerReuseStrategy ) )
739         {
740             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew );
741         }
742         else if ( CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy().equals(
743             this.compilerReuseStrategy ) )
744         {
745             if ( getRequestThreadCount() > 1 )
746             {
747                 if ( !skipMultiThreadWarning )
748                 {
749                     getLog().warn( "You are in a multi-thread build and compilerReuseStrategy is set to reuseSame."
750                                        + " This can cause issues in some environments (os/jdk)!"
751                                        + " Consider using reuseCreated strategy."
752                                        + System.getProperty( "line.separator" )
753                                        + "If your env is fine with reuseSame, you can skip this warning with the "
754                                        + "configuration field skipMultiThreadWarning "
755                                        + "or -Dmaven.compiler.skipMultiThreadWarning=true" );
756                 }
757             }
758             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseSame );
759         }
760         else
761         {
762 
763             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseCreated );
764         }
765 
766         getLog().debug( "CompilerReuseStrategy: " + compilerConfiguration.getCompilerReuseStrategy().getStrategy() );
767 
768         compilerConfiguration.setForceJavacCompilerUse( forceJavacCompilerUse );
769 
770         boolean canUpdateTarget;
771 
772         IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper( mojoExecution, session );
773 
774         final Set<File> sources;
775 
776         IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
777 
778         if ( useIncrementalCompilation )
779         {
780             getLog().debug( "useIncrementalCompilation enabled" );
781             try
782             {
783                 canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
784 
785                 sources = getCompileSources( compiler, compilerConfiguration );
786                 
787                 preparePaths( sources );
788 
789                 incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles( sources );
790 
791                 // CHECKSTYLE_OFF: LineLength
792                 if ( ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) && !canUpdateTarget )
793                     || isDependencyChanged()
794                     || isSourceChanged( compilerConfiguration, compiler )
795                     || incrementalBuildHelper.inputFileTreeChanged( incrementalBuildHelperRequest ) )
796                     // CHECKSTYLE_ON: LineLength
797                 {
798                     getLog().info( "Changes detected - recompiling the module!" );
799 
800                     compilerConfiguration.setSourceFiles( sources );
801                 }
802                 else
803                 {
804                     getLog().info( "Nothing to compile - all classes are up to date" );
805 
806                     return;
807                 }
808             }
809             catch ( CompilerException e )
810             {
811                 throw new MojoExecutionException( "Error while computing stale sources.", e );
812             }
813         }
814         else
815         {
816             getLog().debug( "useIncrementalCompilation disabled" );
817             
818             Set<File> staleSources;
819             try
820             {
821                 staleSources =
822                     computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
823 
824                 canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
825 
826                 if ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
827                     && !canUpdateTarget )
828                 {
829                     getLog().info( "RESCANNING!" );
830                     // TODO: This second scan for source files is sub-optimal
831                     String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
832 
833                     staleSources = computeStaleSources( compilerConfiguration, compiler,
834                                                              getSourceInclusionScanner( inputFileEnding ) );
835                 }
836                 
837             }
838             catch ( CompilerException e )
839             {
840                 throw new MojoExecutionException( "Error while computing stale sources.", e );
841             }
842 
843             if ( staleSources.isEmpty() )
844             {
845                 getLog().info( "Nothing to compile - all classes are up to date" );
846 
847                 return;
848             }
849 
850             compilerConfiguration.setSourceFiles( staleSources );
851             
852             try
853             {
854                 // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath
855                 sources = getCompileSources( compiler, compilerConfiguration );
856                 
857                 getLog().debug( "#sources: " + sources.size() );
858                 for ( File file : sources )
859                 {
860                     getLog().debug( file.getPath() );
861                 }
862 
863                 preparePaths( sources );
864             }
865             catch ( CompilerException e )
866             {
867                 throw new MojoExecutionException( "Error while computing stale sources.", e );
868             }
869         }
870        
871         // Dividing pathElements of classPath and modulePath is based on sourceFiles
872         compilerConfiguration.setClasspathEntries( getClasspathElements() );
873 
874         compilerConfiguration.setModulepathEntries( getModulepathElements() );
875         
876         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
877 
878         String effectiveCompilerArgument = getCompilerArgument();
879 
880         if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null )
881                         || ( compilerArgs != null ) )
882         {
883             if ( effectiveCompilerArguments != null )
884             {
885                 for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
886                 {
887                     String key = me.getKey();
888                     String value = me.getValue();
889                     if ( !key.startsWith( "-" ) )
890                     {
891                         key = "-" + key;
892                     }
893 
894                     if ( key.startsWith( "-A" ) && StringUtils.isNotEmpty( value ) )
895                     {
896                         compilerConfiguration.addCompilerCustomArgument( key + "=" + value, null );
897                     }
898                     else
899                     {
900                         compilerConfiguration.addCompilerCustomArgument( key, value );
901                     }
902                 }
903             }
904             if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
905             {
906                 compilerConfiguration.addCompilerCustomArgument( effectiveCompilerArgument, null );
907             }
908             if ( compilerArgs != null )
909             {
910                 for ( String arg : compilerArgs )
911                 {
912                     compilerConfiguration.addCompilerCustomArgument( arg, null );
913                 }
914             }
915         }
916         
917         // ----------------------------------------------------------------------
918         // Dump configuration
919         // ----------------------------------------------------------------------
920         if ( getLog().isDebugEnabled() )
921         {
922             getLog().debug( "Classpath:" );
923 
924             for ( String s : getClasspathElements() )
925             {
926                 getLog().debug( " " + s );
927             }
928 
929             if ( !getModulepathElements().isEmpty() )
930             {
931                 getLog().debug( "Modulepath:" );
932                 for ( String s : getModulepathElements() )
933                 {
934                     getLog().debug( " " + s );
935                 }
936             }
937 
938             getLog().debug( "Source roots:" );
939 
940             for ( String root : getCompileSourceRoots() )
941             {
942                 getLog().debug( " " + root );
943             }
944 
945             try
946             {
947                 if ( fork )
948                 {
949                     if ( compilerConfiguration.getExecutable() != null )
950                     {
951                         getLog().debug( "Excutable: " );
952                         getLog().debug( " " + compilerConfiguration.getExecutable() );
953                     }
954                 }
955 
956                 String[] cl = compiler.createCommandLine( compilerConfiguration );
957                 if ( getLog().isDebugEnabled() && cl != null && cl.length > 0 )
958                 {
959                     StringBuilder sb = new StringBuilder();
960                     sb.append( cl[0] );
961                     for ( int i = 1; i < cl.length; i++ )
962                     {
963                         sb.append( " " );
964                         sb.append( cl[i] );
965                     }
966                     getLog().debug( "Command line options:" );
967                     getLog().debug( sb );
968                 }
969             }
970             catch ( CompilerException ce )
971             {
972                 getLog().debug( ce );
973             }
974         }
975         
976 
977         List<String> jpmsLines = new ArrayList<String>();
978         
979         // See http://openjdk.java.net/jeps/261
980         final List<String> runtimeArgs = Arrays.asList( "--upgrade-module-path", 
981                                                   "--add-exports",
982                                                   "--add-reads", 
983                                                   "--add-modules", 
984                                                   "--limit-modules" );
985         
986         // Custom arguments are all added as keys to an ordered Map
987         Iterator<Map.Entry<String, String>> entryIter =
988             compilerConfiguration.getCustomCompilerArgumentsEntries().iterator();
989         while ( entryIter.hasNext() )
990         {
991             Map.Entry<String, String> entry = entryIter.next();
992             
993             if ( runtimeArgs.contains( entry.getKey() ) )
994             {
995                 jpmsLines.add( entry.getKey() );
996                 
997                 String value = entry.getValue();
998                 if ( value == null )
999                 {
1000                     entry = entryIter.next();
1001                     value = entry.getKey();
1002                 }
1003                 jpmsLines.add( value );
1004             }
1005             else if ( "--patch-module".equals( entry.getKey() ) )
1006             {
1007                 String value = entry.getValue();
1008                 if ( value == null )
1009                 {
1010                     entry = entryIter.next();
1011                     value = entry.getKey();
1012                 }
1013                 
1014                 String[] values = value.split( "=" );
1015 
1016                 StringBuilder patchModule = new StringBuilder( values[0] );
1017                 patchModule.append( '=' );
1018 
1019                 Set<String> patchModules = new LinkedHashSet<>();
1020                 Set<Path> sourceRoots = new HashSet<>( getCompileSourceRoots().size() );
1021                 for ( String sourceRoot : getCompileSourceRoots() )
1022                 {
1023                     sourceRoots.add( Paths.get( sourceRoot ) );
1024                 }
1025 
1026                 String[] files = values[1].split( PS );
1027 
1028                 for ( String file : files )
1029                 {
1030                     Path filePath = Paths.get( file );
1031                     if ( getOutputDirectory().toPath().equals( filePath ) )
1032                     {
1033                         patchModules.add( "_" ); // this jar
1034                     }
1035                     else if ( getOutputDirectory().toPath().startsWith( filePath ) )
1036                     {
1037                         // multirelease, can be ignored
1038                         continue;
1039                     }
1040                     else if ( sourceRoots.contains( filePath ) )
1041                     {
1042                         patchModules.add( "_" ); // this jar
1043                     }
1044                     else
1045                     {
1046                         JavaModuleDescriptor descriptor = getPathElements().get( file );
1047                         
1048                         if ( descriptor == null )
1049                         {
1050                             if ( Files.isDirectory( filePath ) )
1051                             {
1052                                 patchModules.add( file );
1053                             }
1054                             else
1055                             {
1056                                 getLog().warn( "Can't locate " + file );
1057                             }
1058                         }
1059                         else if ( !values[0].equals( descriptor.name() ) )
1060                         {
1061                             patchModules.add( descriptor.name() );
1062                         }
1063                     }
1064                 }
1065 
1066                 StringBuilder sb = new StringBuilder();
1067                 
1068                 if ( patchModules.size() > 0 )
1069                 {
1070                     for ( String mod : patchModules )
1071                     {
1072                         if ( sb.length() > 0 )
1073                         {
1074                             sb.append( ", " );
1075                         }
1076                         // use 'invalid' separator to ensure values are transformed
1077                         sb.append( mod );
1078                     }
1079 
1080                     jpmsLines.add( "--patch-module" );
1081                     jpmsLines.add( patchModule + sb.toString() );
1082                 }
1083 
1084             }
1085         }
1086         
1087         if ( !jpmsLines.isEmpty() ) 
1088         {
1089             Path jpmsArgs = Paths.get( getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args" );
1090             try
1091             {
1092                 Files.createDirectories( jpmsArgs.getParent() );
1093                 
1094                 Files.write( jpmsArgs, jpmsLines, Charset.defaultCharset() );
1095             }
1096             catch ( IOException e )
1097             {
1098                 getLog().warn( e.getMessage() );
1099             }
1100         }
1101 
1102 
1103         // ----------------------------------------------------------------------
1104         // Compile!
1105         // ----------------------------------------------------------------------
1106 
1107         if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
1108         {
1109             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
1110                                + ", i.e. build is platform dependent!" );
1111         }
1112 
1113         CompilerResult compilerResult;
1114 
1115 
1116         if ( useIncrementalCompilation )
1117         {
1118             incrementalBuildHelperRequest.outputDirectory( getOutputDirectory() );
1119 
1120             incrementalBuildHelper.beforeRebuildExecution( incrementalBuildHelperRequest );
1121 
1122             getLog().debug( "incrementalBuildHelper#beforeRebuildExecution" );
1123         }
1124 
1125         try
1126         {
1127             try
1128             {
1129                 compilerResult = compiler.performCompile( compilerConfiguration );
1130             }
1131             catch ( CompilerNotImplementedException cnie )
1132             {
1133                 List<CompilerError> messages = compiler.compile( compilerConfiguration );
1134                 compilerResult = convertToCompilerResult( messages );
1135             }
1136         }
1137         catch ( Exception e )
1138         {
1139             // TODO: don't catch Exception
1140             throw new MojoExecutionException( "Fatal error compiling", e );
1141         }
1142 
1143         if ( useIncrementalCompilation )
1144         {
1145             if ( incrementalBuildHelperRequest.getOutputDirectory().exists() )
1146             {
1147                 getLog().debug( "incrementalBuildHelper#afterRebuildExecution" );
1148                 // now scan the same directory again and create a diff
1149                 incrementalBuildHelper.afterRebuildExecution( incrementalBuildHelperRequest );
1150             }
1151             else
1152             {
1153                 getLog().debug(
1154                     "skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist" );
1155             }
1156         }
1157 
1158         List<CompilerMessage> warnings = new ArrayList<CompilerMessage>();
1159         List<CompilerMessage> errors = new ArrayList<CompilerMessage>();
1160         List<CompilerMessage> others = new ArrayList<CompilerMessage>();
1161         for ( CompilerMessage message : compilerResult.getCompilerMessages() )
1162         {
1163             if ( message.getKind() == CompilerMessage.Kind.ERROR )
1164             {
1165                 errors.add( message );
1166             }
1167             else if ( message.getKind() == CompilerMessage.Kind.WARNING
1168                 || message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING )
1169             {
1170                 warnings.add( message );
1171             }
1172             else
1173             {
1174                 others.add( message );
1175             }
1176         }
1177 
1178         if ( failOnError && !compilerResult.isSuccess() )
1179         {
1180             for ( CompilerMessage message : others )
1181             {
1182                 assert message.getKind() != CompilerMessage.Kind.ERROR
1183                     && message.getKind() != CompilerMessage.Kind.WARNING
1184                     && message.getKind() != CompilerMessage.Kind.MANDATORY_WARNING;
1185                 getLog().info( message.toString() );
1186             }
1187             if ( !warnings.isEmpty() )
1188             {
1189                 getLog().info( "-------------------------------------------------------------" );
1190                 getLog().warn( "COMPILATION WARNING : " );
1191                 getLog().info( "-------------------------------------------------------------" );
1192                 for ( CompilerMessage warning : warnings )
1193                 {
1194                     getLog().warn( warning.toString() );
1195                 }
1196                 getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
1197                 getLog().info( "-------------------------------------------------------------" );
1198             }
1199 
1200             if ( !errors.isEmpty() )
1201             {
1202                 getLog().info( "-------------------------------------------------------------" );
1203                 getLog().error( "COMPILATION ERROR : " );
1204                 getLog().info( "-------------------------------------------------------------" );
1205                 for ( CompilerMessage error : errors )
1206                 {
1207                     getLog().error( error.toString() );
1208                 }
1209                 getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
1210                 getLog().info( "-------------------------------------------------------------" );
1211             }
1212 
1213             if ( !errors.isEmpty() )
1214             {
1215                 throw new CompilationFailureException( errors );
1216             }
1217             else
1218             {
1219                 throw new CompilationFailureException( warnings );
1220             }
1221         }
1222         else
1223         {
1224             for ( CompilerMessage message : compilerResult.getCompilerMessages() )
1225             {
1226                 switch ( message.getKind() )
1227                 {
1228                     case NOTE:
1229                     case OTHER:
1230                         getLog().info( message.toString() );
1231                         break;
1232 
1233                     case ERROR:
1234                         getLog().error( message.toString() );
1235                         break;
1236 
1237                     case MANDATORY_WARNING:
1238                     case WARNING:
1239                     default:
1240                         getLog().warn( message.toString() );
1241                         break;
1242                 }
1243             }
1244         }
1245     }
1246 
1247     protected boolean isTestCompile()
1248     {
1249         return false;
1250     }
1251 
1252     protected CompilerResult convertToCompilerResult( List<CompilerError> compilerErrors )
1253     {
1254         if ( compilerErrors == null )
1255         {
1256             return new CompilerResult();
1257         }
1258         List<CompilerMessage> messages = new ArrayList<CompilerMessage>( compilerErrors.size() );
1259         boolean success = true;
1260         for ( CompilerError compilerError : compilerErrors )
1261         {
1262             messages.add(
1263                 new CompilerMessage( compilerError.getFile(), compilerError.getKind(), compilerError.getStartLine(),
1264                                      compilerError.getStartColumn(), compilerError.getEndLine(),
1265                                      compilerError.getEndColumn(), compilerError.getMessage() ) );
1266             if ( compilerError.isError() )
1267             {
1268                 success = false;
1269             }
1270         }
1271 
1272         return new CompilerResult( success, messages );
1273     }
1274 
1275     /**
1276      * @return all source files for the compiler
1277      */
1278     private Set<File> getCompileSources( Compiler compiler, CompilerConfiguration compilerConfiguration )
1279         throws MojoExecutionException, CompilerException
1280     {
1281         String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
1282         if ( StringUtils.isEmpty( inputFileEnding ) )
1283         {
1284             // see MCOMPILER-199 GroovyEclipseCompiler doesn't set inputFileEnding
1285             // so we can presume it's all files from the source directory
1286             inputFileEnding = ".*";
1287         }
1288         SourceInclusionScanner scanner = getSourceInclusionScanner( inputFileEnding );
1289 
1290         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
1291 
1292         scanner.addSourceMapping( mapping );
1293 
1294         Set<File> compileSources = new HashSet<File>();
1295 
1296         for ( String sourceRoot : getCompileSourceRoots() )
1297         {
1298             File rootFile = new File( sourceRoot );
1299 
1300             if ( !rootFile.isDirectory()
1301                 || rootFile.getAbsoluteFile().equals( compilerConfiguration.getGeneratedSourcesDirectory() ) )
1302             {
1303                 continue;
1304             }
1305 
1306             try
1307             {
1308                 compileSources.addAll( scanner.getIncludedSources( rootFile, null ) );
1309             }
1310             catch ( InclusionScanException e )
1311             {
1312                 throw new MojoExecutionException(
1313                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
1314             }
1315         }
1316 
1317         return compileSources;
1318     }
1319 
1320     /**
1321      * @param compilerConfiguration
1322      * @param compiler
1323      * @return <code>true</code> if at least a single source file is newer than it's class file
1324      */
1325     private boolean isSourceChanged( CompilerConfiguration compilerConfiguration, Compiler compiler )
1326         throws CompilerException, MojoExecutionException
1327     {
1328         Set<File> staleSources =
1329             computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
1330 
1331         if ( getLog().isDebugEnabled() )
1332         {
1333             for ( File f : staleSources )
1334             {
1335                 getLog().debug( "Stale source detected: " + f.getAbsolutePath() );
1336             }
1337         }
1338         return staleSources != null && staleSources.size() > 0;
1339     }
1340 
1341 
1342     /**
1343      * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependent
1344      *
1345      * @return number of thread for this build or 1 if not multi-thread build
1346      */
1347     protected int getRequestThreadCount()
1348     {
1349         try
1350         {
1351             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
1352             Object mavenExecutionRequest = getRequestMethod.invoke( this.session );
1353             Method getThreadCountMethod = mavenExecutionRequest.getClass().getMethod( "getThreadCount" );
1354             String threadCount = (String) getThreadCountMethod.invoke( mavenExecutionRequest );
1355             return Integer.valueOf( threadCount );
1356         }
1357         catch ( Exception e )
1358         {
1359             getLog().debug( "unable to get threadCount for the current build: " + e.getMessage() );
1360         }
1361         return 1;
1362     }
1363 
1364     protected Date getBuildStartTime()
1365     {
1366         Date buildStartTime = null;
1367         try
1368         {
1369             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
1370             Object mavenExecutionRequest = getRequestMethod.invoke( session );
1371             Method getStartTimeMethod = mavenExecutionRequest.getClass().getMethod( "getStartTime" );
1372             buildStartTime = (Date) getStartTimeMethod.invoke( mavenExecutionRequest );
1373         }
1374         catch ( Exception e )
1375         {
1376             getLog().debug( "unable to get start time for the current build: " + e.getMessage() );
1377         }
1378 
1379         if ( buildStartTime == null )
1380         {
1381             return new Date();
1382         }
1383 
1384         return buildStartTime;
1385     }
1386 
1387 
1388     private String getMemoryValue( String setting )
1389     {
1390         String value = null;
1391 
1392         // Allow '128' or '128m'
1393         if ( isDigits( setting ) )
1394         {
1395             value = setting + "m";
1396         }
1397         else if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) )
1398             && ( setting.toLowerCase().endsWith( "m" ) ) )
1399         {
1400             value = setting;
1401         }
1402         return value;
1403     }
1404 
1405     //TODO remove the part with ToolchainManager lookup once we depend on
1406     //3.0.9 (have it as prerequisite). Define as regular component field then.
1407     protected final Toolchain getToolchain()
1408     {
1409         Toolchain tc = null;
1410         
1411         if ( jdkToolchain != null )
1412         {
1413             // Maven 3.3.1 has plugin execution scoped Toolchain Support
1414             try
1415             {
1416                 Method getToolchainsMethod =
1417                     toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
1418                                                            Map.class );
1419 
1420                 @SuppressWarnings( "unchecked" )
1421                 List<Toolchain> tcs =
1422                     (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
1423                                                                   jdkToolchain );
1424 
1425                 if ( tcs != null && tcs.size() > 0 )
1426                 {
1427                     tc = tcs.get( 0 );
1428                 }
1429             }
1430             catch ( NoSuchMethodException e )
1431             {
1432                 // ignore
1433             }
1434             catch ( SecurityException e )
1435             {
1436                 // ignore
1437             }
1438             catch ( IllegalAccessException e )
1439             {
1440                 // ignore
1441             }
1442             catch ( IllegalArgumentException e )
1443             {
1444                 // ignore
1445             }
1446             catch ( InvocationTargetException e )
1447             {
1448                 // ignore
1449             }
1450         }
1451         
1452         if ( tc == null )
1453         {
1454             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
1455         }
1456         
1457         return tc;
1458     }
1459 
1460     private boolean isDigits( String string )
1461     {
1462         for ( int i = 0; i < string.length(); i++ )
1463         {
1464             if ( !Character.isDigit( string.charAt( i ) ) )
1465             {
1466                 return false;
1467             }
1468         }
1469         return true;
1470     }
1471 
1472     private Set<File> computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler,
1473                                            SourceInclusionScanner scanner )
1474         throws MojoExecutionException, CompilerException
1475     {
1476         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
1477 
1478         File outputDirectory;
1479         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1480         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
1481         {
1482             outputDirectory = buildDirectory;
1483         }
1484         else
1485         {
1486             outputDirectory = getOutputDirectory();
1487         }
1488 
1489         scanner.addSourceMapping( mapping );
1490 
1491         Set<File> staleSources = new HashSet<File>();
1492 
1493         for ( String sourceRoot : getCompileSourceRoots() )
1494         {
1495             File rootFile = new File( sourceRoot );
1496 
1497             if ( !rootFile.isDirectory() )
1498             {
1499                 continue;
1500             }
1501 
1502             try
1503             {
1504                 staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) );
1505             }
1506             catch ( InclusionScanException e )
1507             {
1508                 throw new MojoExecutionException(
1509                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
1510             }
1511         }
1512 
1513         return staleSources;
1514     }
1515 
1516     private SourceMapping getSourceMapping( CompilerConfiguration compilerConfiguration, Compiler compiler )
1517         throws CompilerException, MojoExecutionException
1518     {
1519         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1520 
1521         SourceMapping mapping;
1522         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
1523         {
1524             mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ),
1525                                          compiler.getOutputFileEnding( compilerConfiguration ) );
1526         }
1527         else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
1528         {
1529             mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ),
1530                                                      compiler.getOutputFile( compilerConfiguration ) );
1531 
1532         }
1533         else
1534         {
1535             throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." );
1536         }
1537         return mapping;
1538     }
1539 
1540     /**
1541      * @todo also in ant plugin. This should be resolved at some point so that it does not need to
1542      * be calculated continuously - or should the plugins accept empty source roots as is?
1543      */
1544     private static List<String> removeEmptyCompileSourceRoots( List<String> compileSourceRootsList )
1545     {
1546         List<String> newCompileSourceRootsList = new ArrayList<String>();
1547         if ( compileSourceRootsList != null )
1548         {
1549             // copy as I may be modifying it
1550             for ( String srcDir : compileSourceRootsList )
1551             {
1552                 if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() )
1553                 {
1554                     newCompileSourceRootsList.add( srcDir );
1555                 }
1556             }
1557         }
1558         return newCompileSourceRootsList;
1559     }
1560 
1561     /**
1562      * We just compare the timestamps of all local dependency files (inter-module dependency classpath) and the own
1563      * generated classes and if we got a file which is &gt;= the buid-started timestamp, then we catched a file which
1564      * got changed during this build.
1565      *
1566      * @return <code>true</code> if at least one single dependency has changed.
1567      */
1568     protected boolean isDependencyChanged()
1569     {
1570         if ( session == null )
1571         {
1572             // we just cannot determine it, so don't do anything beside logging
1573             getLog().info( "Cannot determine build start date, skipping incremental build detection." );
1574             return false;
1575         }
1576 
1577         if ( fileExtensions == null || fileExtensions.isEmpty() )
1578         {
1579             fileExtensions = new ArrayList<String>();
1580             fileExtensions.add( ".class" );
1581         }
1582 
1583         Date buildStartTime = getBuildStartTime();
1584 
1585         List<String> pathElements = new ArrayList<String>();
1586         pathElements.addAll( getClasspathElements() );
1587         pathElements.addAll( getModulepathElements() );
1588         
1589         for ( String pathElement : pathElements )
1590         {
1591             // ProjectArtifacts are artifacts which are available in the local project
1592             // that's the only ones we are interested in now.
1593             File artifactPath = new File( pathElement );
1594             if ( artifactPath.isDirectory() )
1595             {
1596                 if ( hasNewFile( artifactPath, buildStartTime ) )
1597                 {
1598                     getLog().debug( "New dependency detected: " + artifactPath.getAbsolutePath() );
1599                     return true;
1600                 }
1601             }
1602         }
1603 
1604         // obviously there was no new file detected.
1605         return false;
1606     }
1607 
1608     /**
1609      * @param classPathEntry entry to check
1610      * @param buildStartTime time build start
1611      * @return if any changes occurred
1612      */
1613     private boolean hasNewFile( File classPathEntry, Date buildStartTime )
1614     {
1615         if ( !classPathEntry.exists() )
1616         {
1617             return false;
1618         }
1619 
1620         if ( classPathEntry.isFile() )
1621         {
1622             return classPathEntry.lastModified() >= buildStartTime.getTime()
1623                 && fileExtensions.contains( FileUtils.getExtension( classPathEntry.getName() ) );
1624         }
1625 
1626         File[] children = classPathEntry.listFiles();
1627 
1628         for ( File child : children )
1629         {
1630             if ( hasNewFile( child, buildStartTime ) )
1631             {
1632                 return true;
1633             }
1634         }
1635 
1636         return false;
1637     }
1638 
1639     private List<String> resolveProcessorPathEntries()
1640         throws MojoExecutionException
1641     {
1642         if ( annotationProcessorPaths == null || annotationProcessorPaths.isEmpty() )
1643         {
1644             return null;
1645         }
1646 
1647         try
1648         {
1649             Set<Artifact> requiredArtifacts = new LinkedHashSet<Artifact>();
1650 
1651             for ( DependencyCoordinate coord : annotationProcessorPaths )
1652             {
1653                 ArtifactHandler handler = artifactHandlerManager.getArtifactHandler( coord.getType() );
1654 
1655                 Artifact artifact = new DefaultArtifact(
1656                      coord.getGroupId(),
1657                      coord.getArtifactId(),
1658                      VersionRange.createFromVersionSpec( coord.getVersion() ),
1659                      Artifact.SCOPE_RUNTIME,
1660                      coord.getType(),
1661                      coord.getClassifier(),
1662                      handler,
1663                      false );
1664 
1665                 requiredArtifacts.add( artifact );
1666             }
1667 
1668             ArtifactResolutionRequest request = new ArtifactResolutionRequest()
1669                             .setArtifact( requiredArtifacts.iterator().next() )
1670                             .setResolveRoot( true )
1671                             .setResolveTransitively( true )
1672                             .setArtifactDependencies( requiredArtifacts )
1673                             .setLocalRepository( session.getLocalRepository() )
1674                             .setRemoteRepositories( project.getRemoteArtifactRepositories() );
1675 
1676             ArtifactResolutionResult resolutionResult = repositorySystem.resolve( request );
1677 
1678             resolutionErrorHandler.throwErrors( request, resolutionResult );
1679 
1680             List<String> elements = new ArrayList<String>( resolutionResult.getArtifacts().size() );
1681 
1682             for ( Object resolved : resolutionResult.getArtifacts() )
1683             {
1684                 elements.add( ( (Artifact) resolved ).getFile().getAbsolutePath() );
1685             }
1686 
1687             return elements;
1688         }
1689         catch ( Exception e )
1690         {
1691             throw new MojoExecutionException( "Resolution of annotationProcessorPath dependencies failed: "
1692                 + e.getLocalizedMessage(), e );
1693         }
1694     }
1695 
1696     private void writePlugin( MessageBuilder mb )
1697     {
1698         mb.a( "    <plugin>" ).newline();
1699         mb.a( "      <groupId>org.apache.maven.plugins</groupId>" ).newline();
1700         mb.a( "      <artifactId>maven-compiler-plugin</artifactId>" ).newline();
1701         
1702         String version = getMavenCompilerPluginVersion();
1703         if ( version != null )
1704         {
1705             mb.a( "      <version>" ).a( version ).a( "</version>" ).newline();
1706         }
1707         writeConfig( mb );
1708         
1709         mb.a( "    </plugin>" ).newline();
1710     }
1711 
1712     private void writeConfig( MessageBuilder mb )
1713     {
1714         mb.a( "      <configuration>" ).newline();
1715 
1716         if ( release != null )
1717         {
1718             mb.a( "        <release>" ).a( release ).a( "</release>" ).newline();
1719         }
1720         else if ( JavaVersion.JAVA_VERSION.isAtLeast( "9" ) )
1721         {
1722             String rls = target.replaceAll( ".\\.", "" );
1723             // when using Java9+, motivate to use release instead of source/target
1724             mb.a( "        <release>" ).a( rls ).a( "</release>" ).newline();
1725         }
1726         else
1727         {
1728             mb.a( "        <source>" ).a( source ).a( "</source>" ).newline();
1729             mb.a( "        <target>" ).a( target ).a( "</target>" ).newline();
1730         }
1731         mb.a( "      </configuration>" ).newline();
1732     }
1733 
1734     private String getMavenCompilerPluginVersion()
1735     {
1736         Properties pomProperties = new Properties();
1737 
1738         try ( InputStream is = AbstractCompilerMojo.class
1739             .getResourceAsStream( "/META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties" ) )
1740         {
1741             if ( is != null )
1742             {
1743                 pomProperties.load( is );
1744             }
1745         }
1746         catch ( IOException e )
1747         {
1748             // noop
1749         }
1750 
1751         return pomProperties.getProperty( "version" );
1752     }
1753 
1754     public void setTarget( String target )
1755     {
1756         this.target = target;
1757         targetOrReleaseSet = true;
1758     }
1759 
1760     public void setRelease( String release )
1761     {
1762         this.release = release;
1763         targetOrReleaseSet = true;
1764     }
1765 }