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   * @since 2.0
94   */
95  public abstract class AbstractCompilerMojo
96      extends AbstractMojo
97  {
98      protected static final String PS = System.getProperty( "path.separator" );
99  
100     static final String DEFAULT_SOURCE = "1.6";
101     
102     static final String DEFAULT_TARGET = "1.6";
103     
104     // Used to compare with older targets
105     static final String MODULE_INFO_TARGET = "1.9";
106     
107     // ----------------------------------------------------------------------
108     // Configurables
109     // ----------------------------------------------------------------------
110 
111     /**
112      * Indicates whether the build will continue even if there are compilation errors.
113      *
114      * @since 2.0.2
115      */
116     @Parameter( property = "maven.compiler.failOnError", defaultValue = "true" )
117     private boolean failOnError = true;
118     
119     /**
120      * Indicates whether the build will continue even if there are compilation warnings.
121      *
122      * @since 3.6
123      */
124     @Parameter( property = "maven.compiler.failOnWarning", defaultValue = "false" )
125     private boolean failOnWarning;  
126 
127     /**
128      * Set to <code>true</code> to include debugging information in the compiled class files.
129      */
130     @Parameter( property = "maven.compiler.debug", defaultValue = "true" )
131     private boolean debug = true;
132 
133     /**
134      * Set to <code>true</code> to generate metadata for reflection on method parameters.
135      * @since 3.6.2
136      */
137     @Parameter( property = "maven.compiler.parameters", defaultValue = "false" )
138     private boolean parameters;
139 
140     /**
141      * Set to <code>true</code> to show messages about what the compiler is doing.
142      */
143     @Parameter( property = "maven.compiler.verbose", defaultValue = "false" )
144     private boolean verbose;
145 
146     /**
147      * Sets whether to show source locations where deprecated APIs are used.
148      */
149     @Parameter( property = "maven.compiler.showDeprecation", defaultValue = "false" )
150     private boolean showDeprecation;
151 
152     /**
153      * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods.
154      * @deprecated This property is a no-op in {@code javac}.
155      */
156     @Deprecated
157     @Parameter( property = "maven.compiler.optimize", defaultValue = "false" )
158     private boolean optimize;
159 
160     /**
161      * Set to <code>true</code> to show compilation warnings.
162      */
163     @Parameter( property = "maven.compiler.showWarnings", defaultValue = "false" )
164     private boolean showWarnings;
165 
166     /**
167      * <p>The -source argument for the Java compiler.</p>
168      * 
169      * <b>NOTE: </b>Since 3.8.0 the default value has changed from 1.5 to 1.6
170      */
171     @Parameter( property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE )
172     protected String source;
173 
174     /**
175      * <p>The -target argument for the Java compiler.</p>
176      * 
177      * <b>NOTE: </b>Since 3.8.0 the default value has changed from 1.5 to 1.6
178      */
179     @Parameter( property = "maven.compiler.target", defaultValue = DEFAULT_TARGET )
180     protected String target;
181 
182     /**
183      * The -release argument for the Java compiler, supported since Java9
184      * 
185      * @since 3.6
186      */
187     @Parameter( property = "maven.compiler.release" )
188     protected String release;
189     
190     /**
191      * The -encoding argument for the Java compiler.
192      *
193      * @since 2.1
194      */
195     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
196     private String encoding;
197 
198     /**
199      * Sets the granularity in milliseconds of the last modification
200      * date for testing whether a source needs recompilation.
201      */
202     @Parameter( property = "lastModGranularityMs", defaultValue = "0" )
203     private int staleMillis;
204 
205     /**
206      * The compiler id of the compiler to use. See this
207      * <a href="non-javac-compilers.html">guide</a> for more information.
208      */
209     @Parameter( property = "maven.compiler.compilerId", defaultValue = "javac" )
210     private String compilerId;
211 
212     /**
213      * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>.
214      */
215     @Parameter( property = "maven.compiler.compilerVersion" )
216     private String compilerVersion;
217 
218     /**
219      * Allows running the compiler in a separate process.
220      * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable.
221      */
222     @Parameter( property = "maven.compiler.fork", defaultValue = "false" )
223     private boolean fork;
224 
225     /**
226      * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
227      * if {@link #fork} is set to <code>true</code>.
228      *
229      * @since 2.0.1
230      */
231     @Parameter( property = "maven.compiler.meminitial" )
232     private String meminitial;
233 
234     /**
235      * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
236      * if {@link #fork} is set to <code>true</code>.
237      *
238      * @since 2.0.1
239      */
240     @Parameter( property = "maven.compiler.maxmem" )
241     private String maxmem;
242 
243     /**
244      * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>.
245      */
246     @Parameter( property = "maven.compiler.executable" )
247     private String executable;
248 
249     /**
250      * <p>
251      * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
252      * If not set, both compilation and annotation processing are performed at the same time.
253      * </p>
254      * <p>Allowed values are:</p>
255      * <ul>
256      * <li><code>none</code> - no annotation processing is performed.</li>
257      * <li><code>only</code> - only annotation processing is done, no compilation.</li>
258      * </ul>
259      *
260      * @since 2.2
261      */
262     @Parameter
263     private String proc;
264 
265     /**
266      * <p>
267      * Names of annotation processors to run. Only applies to JDK 1.6+
268      * If not set, the default annotation processors discovery process applies.
269      * </p>
270      *
271      * @since 2.2
272      */
273     @Parameter
274     private String[] annotationProcessors;
275 
276     /**
277      * <p>
278      * Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation
279      * processors only in those classpath elements. If omitted, the default classpath is used to detect annotation
280      * processors. The detection itself depends on the configuration of {@code annotationProcessors}.
281      * </p>
282      * <p>
283      * Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier,
284      * type). Transitive dependencies are added automatically. Example:
285      * </p>
286      *
287      * <pre>
288      * &lt;configuration&gt;
289      *   &lt;annotationProcessorPaths&gt;
290      *     &lt;path&gt;
291      *       &lt;groupId&gt;org.sample&lt;/groupId&gt;
292      *       &lt;artifactId&gt;sample-annotation-processor&lt;/artifactId&gt;
293      *       &lt;version&gt;1.2.3&lt;/version&gt;
294      *     &lt;/path&gt;
295      *     &lt;!-- ... more ... --&gt;
296      *   &lt;/annotationProcessorPaths&gt;
297      * &lt;/configuration&gt;
298      * </pre>
299      *
300      * @since 3.5
301      */
302     @Parameter
303     private List<DependencyCoordinate> annotationProcessorPaths;
304 
305     /**
306      * <p>
307      * Sets the arguments to be passed to the compiler (prepending a dash) if {@link #fork} is set to <code>true</code>.
308      * </p>
309      * <p>
310      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
311      * </p>
312      * <p>
313      * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following:
314      * </p>
315      *
316      * <pre>
317      * &lt;compilerArguments&gt;
318      *   &lt;Xmaxerrs&gt;1000&lt;/Xmaxerrs&gt;
319      *   &lt;Xlint/&gt;
320      *   &lt;Xlint:-path/&gt;
321      *   &lt;Averbose&gt;true&lt;/Averbose&gt;
322      * &lt;/compilerArguments&gt;
323      * </pre>
324      *
325      * @since 2.0.1
326      * @deprecated use {@link #compilerArgs} instead.
327      */
328     @Parameter
329     @Deprecated
330     protected Map<String, String> compilerArguments;
331 
332     /**
333      * <p>
334      * Sets the arguments to be passed to the compiler if {@link #fork} is set to <code>true</code>.
335      * Example:
336      * <pre>
337      * &lt;compilerArgs&gt;
338      *   &lt;arg&gt;-Xmaxerrs&lt;/arg&gt;
339      *   &lt;arg&gt;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             if ( !generatedSourcesDirectory.exists() )
659             {
660                 generatedSourcesDirectory.mkdirs();
661             }
662 
663             String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath();
664 
665             compileSourceRoots.add( generatedSourcesPath );
666 
667             if ( isTestCompile() )
668             {
669                 getLog().debug( "Adding " + generatedSourcesPath + " to test-compile source roots:\n  "
670                                     + StringUtils.join( project.getTestCompileSourceRoots()
671                                                                .iterator(), "\n  " ) );
672 
673                 project.addTestCompileSourceRoot( generatedSourcesPath );
674 
675                 getLog().debug( "New test-compile source roots:\n  "
676                                     + StringUtils.join( project.getTestCompileSourceRoots()
677                                                                .iterator(), "\n  " ) );
678             }
679             else
680             {
681                 getLog().debug( "Adding " + generatedSourcesPath + " to compile source roots:\n  "
682                                     + StringUtils.join( project.getCompileSourceRoots()
683                                                                .iterator(), "\n  " ) );
684 
685                 project.addCompileSourceRoot( generatedSourcesPath );
686 
687                 getLog().debug( "New compile source roots:\n  " + StringUtils.join( project.getCompileSourceRoots()
688                                                                                            .iterator(), "\n  " ) );
689             }
690         }
691 
692         compilerConfiguration.setSourceLocations( compileSourceRoots );
693 
694         compilerConfiguration.setAnnotationProcessors( annotationProcessors );
695 
696         compilerConfiguration.setProcessorPathEntries( resolveProcessorPathEntries() );
697 
698         compilerConfiguration.setSourceEncoding( encoding );
699 
700         compilerConfiguration.setFork( fork );
701 
702         if ( fork )
703         {
704             if ( !StringUtils.isEmpty( meminitial ) )
705             {
706                 String value = getMemoryValue( meminitial );
707 
708                 if ( value != null )
709                 {
710                     compilerConfiguration.setMeminitial( value );
711                 }
712                 else
713                 {
714                     getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
715                 }
716             }
717 
718             if ( !StringUtils.isEmpty( maxmem ) )
719             {
720                 String value = getMemoryValue( maxmem );
721 
722                 if ( value != null )
723                 {
724                     compilerConfiguration.setMaxmem( value );
725                 }
726                 else
727                 {
728                     getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
729                 }
730             }
731         }
732 
733         compilerConfiguration.setExecutable( executable );
734 
735         compilerConfiguration.setWorkingDirectory( basedir );
736 
737         compilerConfiguration.setCompilerVersion( compilerVersion );
738 
739         compilerConfiguration.setBuildDirectory( buildDirectory );
740 
741         compilerConfiguration.setOutputFileName( outputFileName );
742 
743         if ( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals( this.compilerReuseStrategy ) )
744         {
745             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew );
746         }
747         else if ( CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy().equals(
748             this.compilerReuseStrategy ) )
749         {
750             if ( getRequestThreadCount() > 1 )
751             {
752                 if ( !skipMultiThreadWarning )
753                 {
754                     getLog().warn( "You are in a multi-thread build and compilerReuseStrategy is set to reuseSame."
755                                        + " This can cause issues in some environments (os/jdk)!"
756                                        + " Consider using reuseCreated strategy."
757                                        + System.getProperty( "line.separator" )
758                                        + "If your env is fine with reuseSame, you can skip this warning with the "
759                                        + "configuration field skipMultiThreadWarning "
760                                        + "or -Dmaven.compiler.skipMultiThreadWarning=true" );
761                 }
762             }
763             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseSame );
764         }
765         else
766         {
767 
768             compilerConfiguration.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.ReuseCreated );
769         }
770 
771         getLog().debug( "CompilerReuseStrategy: " + compilerConfiguration.getCompilerReuseStrategy().getStrategy() );
772 
773         compilerConfiguration.setForceJavacCompilerUse( forceJavacCompilerUse );
774 
775         boolean canUpdateTarget;
776 
777         IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper( mojoExecution, session );
778 
779         final Set<File> sources;
780 
781         IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
782 
783         if ( useIncrementalCompilation )
784         {
785             getLog().debug( "useIncrementalCompilation enabled" );
786             try
787             {
788                 canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
789 
790                 sources = getCompileSources( compiler, compilerConfiguration );
791                 
792                 preparePaths( sources );
793 
794                 incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles( sources );
795 
796                 // CHECKSTYLE_OFF: LineLength
797                 if ( ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES ) && !canUpdateTarget )
798                     || isDependencyChanged()
799                     || isSourceChanged( compilerConfiguration, compiler )
800                     || incrementalBuildHelper.inputFileTreeChanged( incrementalBuildHelperRequest ) )
801                     // CHECKSTYLE_ON: LineLength
802                 {
803                     getLog().info( "Changes detected - recompiling the module!" );
804 
805                     compilerConfiguration.setSourceFiles( sources );
806                 }
807                 else
808                 {
809                     getLog().info( "Nothing to compile - all classes are up to date" );
810 
811                     return;
812                 }
813             }
814             catch ( CompilerException e )
815             {
816                 throw new MojoExecutionException( "Error while computing stale sources.", e );
817             }
818         }
819         else
820         {
821             getLog().debug( "useIncrementalCompilation disabled" );
822             
823             Set<File> staleSources;
824             try
825             {
826                 staleSources =
827                     computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
828 
829                 canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
830 
831                 if ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
832                     && !canUpdateTarget )
833                 {
834                     getLog().info( "RESCANNING!" );
835                     // TODO: This second scan for source files is sub-optimal
836                     String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
837 
838                     staleSources = computeStaleSources( compilerConfiguration, compiler,
839                                                              getSourceInclusionScanner( inputFileEnding ) );
840                 }
841                 
842             }
843             catch ( CompilerException e )
844             {
845                 throw new MojoExecutionException( "Error while computing stale sources.", e );
846             }
847 
848             if ( staleSources.isEmpty() )
849             {
850                 getLog().info( "Nothing to compile - all classes are up to date" );
851 
852                 return;
853             }
854 
855             compilerConfiguration.setSourceFiles( staleSources );
856             
857             try
858             {
859                 // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath
860                 sources = getCompileSources( compiler, compilerConfiguration );
861                 
862                 getLog().debug( "#sources: " + sources.size() );
863                 for ( File file : sources )
864                 {
865                     getLog().debug( file.getPath() );
866                 }
867 
868                 preparePaths( sources );
869             }
870             catch ( CompilerException e )
871             {
872                 throw new MojoExecutionException( "Error while computing stale sources.", e );
873             }
874         }
875        
876         // Dividing pathElements of classPath and modulePath is based on sourceFiles
877         compilerConfiguration.setClasspathEntries( getClasspathElements() );
878 
879         compilerConfiguration.setModulepathEntries( getModulepathElements() );
880         
881         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
882 
883         String effectiveCompilerArgument = getCompilerArgument();
884 
885         if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null )
886                         || ( compilerArgs != null ) )
887         {
888             if ( effectiveCompilerArguments != null )
889             {
890                 for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
891                 {
892                     String key = me.getKey();
893                     String value = me.getValue();
894                     if ( !key.startsWith( "-" ) )
895                     {
896                         key = "-" + key;
897                     }
898 
899                     if ( key.startsWith( "-A" ) && StringUtils.isNotEmpty( value ) )
900                     {
901                         compilerConfiguration.addCompilerCustomArgument( key + "=" + value, null );
902                     }
903                     else
904                     {
905                         compilerConfiguration.addCompilerCustomArgument( key, value );
906                     }
907                 }
908             }
909             if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
910             {
911                 compilerConfiguration.addCompilerCustomArgument( effectiveCompilerArgument, null );
912             }
913             if ( compilerArgs != null )
914             {
915                 for ( String arg : compilerArgs )
916                 {
917                     compilerConfiguration.addCompilerCustomArgument( arg, null );
918                 }
919             }
920         }
921         
922         // ----------------------------------------------------------------------
923         // Dump configuration
924         // ----------------------------------------------------------------------
925         if ( getLog().isDebugEnabled() )
926         {
927             getLog().debug( "Classpath:" );
928 
929             for ( String s : getClasspathElements() )
930             {
931                 getLog().debug( " " + s );
932             }
933 
934             if ( !getModulepathElements().isEmpty() )
935             {
936                 getLog().debug( "Modulepath:" );
937                 for ( String s : getModulepathElements() )
938                 {
939                     getLog().debug( " " + s );
940                 }
941             }
942 
943             getLog().debug( "Source roots:" );
944 
945             for ( String root : getCompileSourceRoots() )
946             {
947                 getLog().debug( " " + root );
948             }
949 
950             try
951             {
952                 if ( fork )
953                 {
954                     if ( compilerConfiguration.getExecutable() != null )
955                     {
956                         getLog().debug( "Excutable: " );
957                         getLog().debug( " " + compilerConfiguration.getExecutable() );
958                     }
959                 }
960 
961                 String[] cl = compiler.createCommandLine( compilerConfiguration );
962                 if ( getLog().isDebugEnabled() && cl != null && cl.length > 0 )
963                 {
964                     StringBuilder sb = new StringBuilder();
965                     sb.append( cl[0] );
966                     for ( int i = 1; i < cl.length; i++ )
967                     {
968                         sb.append( " " );
969                         sb.append( cl[i] );
970                     }
971                     getLog().debug( "Command line options:" );
972                     getLog().debug( sb );
973                 }
974             }
975             catch ( CompilerException ce )
976             {
977                 getLog().debug( ce );
978             }
979         }
980         
981 
982         List<String> jpmsLines = new ArrayList<>();
983         
984         // See http://openjdk.java.net/jeps/261
985         final List<String> runtimeArgs = Arrays.asList( "--upgrade-module-path", 
986                                                   "--add-exports",
987                                                   "--add-reads", 
988                                                   "--add-modules", 
989                                                   "--limit-modules" );
990         
991         // Custom arguments are all added as keys to an ordered Map
992         Iterator<Map.Entry<String, String>> entryIter =
993             compilerConfiguration.getCustomCompilerArgumentsEntries().iterator();
994         while ( entryIter.hasNext() )
995         {
996             Map.Entry<String, String> entry = entryIter.next();
997             
998             if ( runtimeArgs.contains( entry.getKey() ) )
999             {
1000                 jpmsLines.add( entry.getKey() );
1001                 
1002                 String value = entry.getValue();
1003                 if ( value == null )
1004                 {
1005                     entry = entryIter.next();
1006                     value = entry.getKey();
1007                 }
1008                 jpmsLines.add( value );
1009             }
1010             else if ( "--patch-module".equals( entry.getKey() ) )
1011             {
1012                 String value = entry.getValue();
1013                 if ( value == null )
1014                 {
1015                     entry = entryIter.next();
1016                     value = entry.getKey();
1017                 }
1018                 
1019                 String[] values = value.split( "=" );
1020 
1021                 StringBuilder patchModule = new StringBuilder( values[0] );
1022                 patchModule.append( '=' );
1023 
1024                 Set<String> patchModules = new LinkedHashSet<>();
1025                 Set<Path> sourceRoots = new HashSet<>( getCompileSourceRoots().size() );
1026                 for ( String sourceRoot : getCompileSourceRoots() )
1027                 {
1028                     sourceRoots.add( Paths.get( sourceRoot ) );
1029                 }
1030 
1031                 String[] files = values[1].split( PS );
1032 
1033                 for ( String file : files )
1034                 {
1035                     Path filePath = Paths.get( file );
1036                     if ( getOutputDirectory().toPath().equals( filePath ) )
1037                     {
1038                         patchModules.add( "_" ); // this jar
1039                     }
1040                     else if ( getOutputDirectory().toPath().startsWith( filePath ) )
1041                     {
1042                         // multirelease, can be ignored
1043                         continue;
1044                     }
1045                     else if ( sourceRoots.contains( filePath ) )
1046                     {
1047                         patchModules.add( "_" ); // this jar
1048                     }
1049                     else
1050                     {
1051                         JavaModuleDescriptor descriptor = getPathElements().get( file );
1052                         
1053                         if ( descriptor == null )
1054                         {
1055                             if ( Files.isDirectory( filePath ) )
1056                             {
1057                                 patchModules.add( file );
1058                             }
1059                             else
1060                             {
1061                                 getLog().warn( "Can't locate " + file );
1062                             }
1063                         }
1064                         else if ( !values[0].equals( descriptor.name() ) )
1065                         {
1066                             patchModules.add( descriptor.name() );
1067                         }
1068                     }
1069                 }
1070 
1071                 StringBuilder sb = new StringBuilder();
1072                 
1073                 if ( patchModules.size() > 0 )
1074                 {
1075                     for ( String mod : patchModules )
1076                     {
1077                         if ( sb.length() > 0 )
1078                         {
1079                             sb.append( ", " );
1080                         }
1081                         // use 'invalid' separator to ensure values are transformed
1082                         sb.append( mod );
1083                     }
1084 
1085                     jpmsLines.add( "--patch-module" );
1086                     jpmsLines.add( patchModule + sb.toString() );
1087                 }
1088 
1089             }
1090         }
1091         
1092         if ( !jpmsLines.isEmpty() ) 
1093         {
1094             Path jpmsArgs = Paths.get( getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args" );
1095             try
1096             {
1097                 Files.createDirectories( jpmsArgs.getParent() );
1098                 
1099                 Files.write( jpmsArgs, jpmsLines, Charset.defaultCharset() );
1100             }
1101             catch ( IOException e )
1102             {
1103                 getLog().warn( e.getMessage() );
1104             }
1105         }
1106 
1107 
1108         // ----------------------------------------------------------------------
1109         // Compile!
1110         // ----------------------------------------------------------------------
1111 
1112         if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
1113         {
1114             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
1115                                + ", i.e. build is platform dependent!" );
1116         }
1117 
1118         CompilerResult compilerResult;
1119 
1120 
1121         if ( useIncrementalCompilation )
1122         {
1123             incrementalBuildHelperRequest.outputDirectory( getOutputDirectory() );
1124 
1125             incrementalBuildHelper.beforeRebuildExecution( incrementalBuildHelperRequest );
1126 
1127             getLog().debug( "incrementalBuildHelper#beforeRebuildExecution" );
1128         }
1129 
1130         try
1131         {
1132             try
1133             {
1134                 compilerResult = compiler.performCompile( compilerConfiguration );
1135             }
1136             catch ( CompilerNotImplementedException cnie )
1137             {
1138                 List<CompilerError> messages = compiler.compile( compilerConfiguration );
1139                 compilerResult = convertToCompilerResult( messages );
1140             }
1141         }
1142         catch ( Exception e )
1143         {
1144             // TODO: don't catch Exception
1145             throw new MojoExecutionException( "Fatal error compiling", e );
1146         }
1147 
1148         if ( useIncrementalCompilation )
1149         {
1150             if ( incrementalBuildHelperRequest.getOutputDirectory().exists() )
1151             {
1152                 getLog().debug( "incrementalBuildHelper#afterRebuildExecution" );
1153                 // now scan the same directory again and create a diff
1154                 incrementalBuildHelper.afterRebuildExecution( incrementalBuildHelperRequest );
1155             }
1156             else
1157             {
1158                 getLog().debug(
1159                     "skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist" );
1160             }
1161         }
1162 
1163         List<CompilerMessage> warnings = new ArrayList<>();
1164         List<CompilerMessage> errors = new ArrayList<>();
1165         List<CompilerMessage> others = new ArrayList<>();
1166         for ( CompilerMessage message : compilerResult.getCompilerMessages() )
1167         {
1168             if ( message.getKind() == CompilerMessage.Kind.ERROR )
1169             {
1170                 errors.add( message );
1171             }
1172             else if ( message.getKind() == CompilerMessage.Kind.WARNING
1173                 || message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING )
1174             {
1175                 warnings.add( message );
1176             }
1177             else
1178             {
1179                 others.add( message );
1180             }
1181         }
1182 
1183         if ( failOnError && !compilerResult.isSuccess() )
1184         {
1185             for ( CompilerMessage message : others )
1186             {
1187                 assert message.getKind() != CompilerMessage.Kind.ERROR
1188                     && message.getKind() != CompilerMessage.Kind.WARNING
1189                     && message.getKind() != CompilerMessage.Kind.MANDATORY_WARNING;
1190                 getLog().info( message.toString() );
1191             }
1192             if ( !warnings.isEmpty() )
1193             {
1194                 getLog().info( "-------------------------------------------------------------" );
1195                 getLog().warn( "COMPILATION WARNING : " );
1196                 getLog().info( "-------------------------------------------------------------" );
1197                 for ( CompilerMessage warning : warnings )
1198                 {
1199                     getLog().warn( warning.toString() );
1200                 }
1201                 getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
1202                 getLog().info( "-------------------------------------------------------------" );
1203             }
1204 
1205             if ( !errors.isEmpty() )
1206             {
1207                 getLog().info( "-------------------------------------------------------------" );
1208                 getLog().error( "COMPILATION ERROR : " );
1209                 getLog().info( "-------------------------------------------------------------" );
1210                 for ( CompilerMessage error : errors )
1211                 {
1212                     getLog().error( error.toString() );
1213                 }
1214                 getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
1215                 getLog().info( "-------------------------------------------------------------" );
1216             }
1217 
1218             if ( !errors.isEmpty() )
1219             {
1220                 throw new CompilationFailureException( errors );
1221             }
1222             else
1223             {
1224                 throw new CompilationFailureException( warnings );
1225             }
1226         }
1227         else
1228         {
1229             for ( CompilerMessage message : compilerResult.getCompilerMessages() )
1230             {
1231                 switch ( message.getKind() )
1232                 {
1233                     case NOTE:
1234                     case OTHER:
1235                         getLog().info( message.toString() );
1236                         break;
1237 
1238                     case ERROR:
1239                         getLog().error( message.toString() );
1240                         break;
1241 
1242                     case MANDATORY_WARNING:
1243                     case WARNING:
1244                     default:
1245                         getLog().warn( message.toString() );
1246                         break;
1247                 }
1248             }
1249         }
1250     }
1251 
1252     protected boolean isTestCompile()
1253     {
1254         return false;
1255     }
1256 
1257     protected CompilerResult convertToCompilerResult( List<CompilerError> compilerErrors )
1258     {
1259         if ( compilerErrors == null )
1260         {
1261             return new CompilerResult();
1262         }
1263         List<CompilerMessage> messages = new ArrayList<>( compilerErrors.size() );
1264         boolean success = true;
1265         for ( CompilerError compilerError : compilerErrors )
1266         {
1267             messages.add(
1268                 new CompilerMessage( compilerError.getFile(), compilerError.getKind(), compilerError.getStartLine(),
1269                                      compilerError.getStartColumn(), compilerError.getEndLine(),
1270                                      compilerError.getEndColumn(), compilerError.getMessage() ) );
1271             if ( compilerError.isError() )
1272             {
1273                 success = false;
1274             }
1275         }
1276 
1277         return new CompilerResult( success, messages );
1278     }
1279 
1280     /**
1281      * @return all source files for the compiler
1282      */
1283     private Set<File> getCompileSources( Compiler compiler, CompilerConfiguration compilerConfiguration )
1284         throws MojoExecutionException, CompilerException
1285     {
1286         String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
1287         if ( StringUtils.isEmpty( inputFileEnding ) )
1288         {
1289             // see MCOMPILER-199 GroovyEclipseCompiler doesn't set inputFileEnding
1290             // so we can presume it's all files from the source directory
1291             inputFileEnding = ".*";
1292         }
1293         SourceInclusionScanner scanner = getSourceInclusionScanner( inputFileEnding );
1294 
1295         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
1296 
1297         scanner.addSourceMapping( mapping );
1298 
1299         Set<File> compileSources = new HashSet<>();
1300 
1301         for ( String sourceRoot : getCompileSourceRoots() )
1302         {
1303             File rootFile = new File( sourceRoot );
1304 
1305             if ( !rootFile.isDirectory()
1306                 || rootFile.getAbsoluteFile().equals( compilerConfiguration.getGeneratedSourcesDirectory() ) )
1307             {
1308                 continue;
1309             }
1310 
1311             try
1312             {
1313                 compileSources.addAll( scanner.getIncludedSources( rootFile, null ) );
1314             }
1315             catch ( InclusionScanException e )
1316             {
1317                 throw new MojoExecutionException(
1318                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
1319             }
1320         }
1321 
1322         return compileSources;
1323     }
1324 
1325     /**
1326      * @param compilerConfiguration
1327      * @param compiler
1328      * @return <code>true</code> if at least a single source file is newer than it's class file
1329      */
1330     private boolean isSourceChanged( CompilerConfiguration compilerConfiguration, Compiler compiler )
1331         throws CompilerException, MojoExecutionException
1332     {
1333         Set<File> staleSources =
1334             computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
1335 
1336         if ( getLog().isDebugEnabled() )
1337         {
1338             for ( File f : staleSources )
1339             {
1340                 getLog().debug( "Stale source detected: " + f.getAbsolutePath() );
1341             }
1342         }
1343         return staleSources != null && staleSources.size() > 0;
1344     }
1345 
1346 
1347     /**
1348      * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependent
1349      *
1350      * @return number of thread for this build or 1 if not multi-thread build
1351      */
1352     protected int getRequestThreadCount()
1353     {
1354         try
1355         {
1356             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
1357             Object mavenExecutionRequest = getRequestMethod.invoke( this.session );
1358             Method getThreadCountMethod = mavenExecutionRequest.getClass().getMethod( "getThreadCount" );
1359             String threadCount = (String) getThreadCountMethod.invoke( mavenExecutionRequest );
1360             return Integer.valueOf( threadCount );
1361         }
1362         catch ( Exception e )
1363         {
1364             getLog().debug( "unable to get threadCount for the current build: " + e.getMessage() );
1365         }
1366         return 1;
1367     }
1368 
1369     protected Date getBuildStartTime()
1370     {
1371         Date buildStartTime = null;
1372         try
1373         {
1374             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
1375             Object mavenExecutionRequest = getRequestMethod.invoke( session );
1376             Method getStartTimeMethod = mavenExecutionRequest.getClass().getMethod( "getStartTime" );
1377             buildStartTime = (Date) getStartTimeMethod.invoke( mavenExecutionRequest );
1378         }
1379         catch ( Exception e )
1380         {
1381             getLog().debug( "unable to get start time for the current build: " + e.getMessage() );
1382         }
1383 
1384         if ( buildStartTime == null )
1385         {
1386             return new Date();
1387         }
1388 
1389         return buildStartTime;
1390     }
1391 
1392 
1393     private String getMemoryValue( String setting )
1394     {
1395         String value = null;
1396 
1397         // Allow '128' or '128m'
1398         if ( isDigits( setting ) )
1399         {
1400             value = setting + "m";
1401         }
1402         else if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) )
1403             && ( setting.toLowerCase().endsWith( "m" ) ) )
1404         {
1405             value = setting;
1406         }
1407         return value;
1408     }
1409 
1410     //TODO remove the part with ToolchainManager lookup once we depend on
1411     //3.0.9 (have it as prerequisite). Define as regular component field then.
1412     protected final Toolchain getToolchain()
1413     {
1414         Toolchain tc = null;
1415         
1416         if ( jdkToolchain != null )
1417         {
1418             // Maven 3.3.1 has plugin execution scoped Toolchain Support
1419             try
1420             {
1421                 Method getToolchainsMethod =
1422                     toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
1423                                                            Map.class );
1424 
1425                 @SuppressWarnings( "unchecked" )
1426                 List<Toolchain> tcs =
1427                     (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
1428                                                                   jdkToolchain );
1429 
1430                 if ( tcs != null && tcs.size() > 0 )
1431                 {
1432                     tc = tcs.get( 0 );
1433                 }
1434             }
1435             catch ( NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
1436                 | InvocationTargetException e )
1437             {
1438                 // ignore
1439             }
1440         }
1441         
1442         if ( tc == null )
1443         {
1444             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
1445         }
1446         
1447         return tc;
1448     }
1449 
1450     private boolean isDigits( String string )
1451     {
1452         for ( int i = 0; i < string.length(); i++ )
1453         {
1454             if ( !Character.isDigit( string.charAt( i ) ) )
1455             {
1456                 return false;
1457             }
1458         }
1459         return true;
1460     }
1461 
1462     private Set<File> computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler,
1463                                            SourceInclusionScanner scanner )
1464         throws MojoExecutionException, CompilerException
1465     {
1466         SourceMapping mapping = getSourceMapping( compilerConfiguration, compiler );
1467 
1468         File outputDirectory;
1469         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1470         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
1471         {
1472             outputDirectory = buildDirectory;
1473         }
1474         else
1475         {
1476             outputDirectory = getOutputDirectory();
1477         }
1478 
1479         scanner.addSourceMapping( mapping );
1480 
1481         Set<File> staleSources = new HashSet<>();
1482 
1483         for ( String sourceRoot : getCompileSourceRoots() )
1484         {
1485             File rootFile = new File( sourceRoot );
1486 
1487             if ( !rootFile.isDirectory() )
1488             {
1489                 continue;
1490             }
1491 
1492             try
1493             {
1494                 staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) );
1495             }
1496             catch ( InclusionScanException e )
1497             {
1498                 throw new MojoExecutionException(
1499                     "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e );
1500             }
1501         }
1502 
1503         return staleSources;
1504     }
1505 
1506     private SourceMapping getSourceMapping( CompilerConfiguration compilerConfiguration, Compiler compiler )
1507         throws CompilerException, MojoExecutionException
1508     {
1509         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1510 
1511         SourceMapping mapping;
1512         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
1513         {
1514             mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ),
1515                                          compiler.getOutputFileEnding( compilerConfiguration ) );
1516         }
1517         else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
1518         {
1519             mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ),
1520                                                      compiler.getOutputFile( compilerConfiguration ) );
1521 
1522         }
1523         else
1524         {
1525             throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." );
1526         }
1527         return mapping;
1528     }
1529 
1530     /**
1531      * @todo also in ant plugin. This should be resolved at some point so that it does not need to
1532      * be calculated continuously - or should the plugins accept empty source roots as is?
1533      */
1534     private static List<String> removeEmptyCompileSourceRoots( List<String> compileSourceRootsList )
1535     {
1536         List<String> newCompileSourceRootsList = new ArrayList<>();
1537         if ( compileSourceRootsList != null )
1538         {
1539             // copy as I may be modifying it
1540             for ( String srcDir : compileSourceRootsList )
1541             {
1542                 if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() )
1543                 {
1544                     newCompileSourceRootsList.add( srcDir );
1545                 }
1546             }
1547         }
1548         return newCompileSourceRootsList;
1549     }
1550 
1551     /**
1552      * We just compare the timestamps of all local dependency files (inter-module dependency classpath) and the own
1553      * generated classes and if we got a file which is &gt;= the buid-started timestamp, then we catched a file which
1554      * got changed during this build.
1555      *
1556      * @return <code>true</code> if at least one single dependency has changed.
1557      */
1558     protected boolean isDependencyChanged()
1559     {
1560         if ( session == null )
1561         {
1562             // we just cannot determine it, so don't do anything beside logging
1563             getLog().info( "Cannot determine build start date, skipping incremental build detection." );
1564             return false;
1565         }
1566 
1567         if ( fileExtensions == null || fileExtensions.isEmpty() )
1568         {
1569             fileExtensions = new ArrayList<>();
1570             fileExtensions.add( "class" );
1571         }
1572 
1573         Date buildStartTime = getBuildStartTime();
1574 
1575         List<String> pathElements = new ArrayList<>();
1576         pathElements.addAll( getClasspathElements() );
1577         pathElements.addAll( getModulepathElements() );
1578         
1579         for ( String pathElement : pathElements )
1580         {
1581             // ProjectArtifacts are artifacts which are available in the local project
1582             // that's the only ones we are interested in now.
1583             File artifactPath = new File( pathElement );
1584             if ( artifactPath.isDirectory() )
1585             {
1586                 if ( hasNewFile( artifactPath, buildStartTime ) )
1587                 {
1588                     getLog().debug( "New dependency detected: " + artifactPath.getAbsolutePath() );
1589                     return true;
1590                 }
1591             }
1592         }
1593 
1594         // obviously there was no new file detected.
1595         return false;
1596     }
1597 
1598     /**
1599      * @param classPathEntry entry to check
1600      * @param buildStartTime time build start
1601      * @return if any changes occurred
1602      */
1603     private boolean hasNewFile( File classPathEntry, Date buildStartTime )
1604     {
1605         if ( !classPathEntry.exists() )
1606         {
1607             return false;
1608         }
1609 
1610         if ( classPathEntry.isFile() )
1611         {
1612             return classPathEntry.lastModified() >= buildStartTime.getTime()
1613                     && fileExtensions.contains( FileUtils.getExtension( classPathEntry.getName() ) );
1614         }
1615 
1616         File[] children = classPathEntry.listFiles();
1617 
1618         for ( File child : children )
1619         {
1620             if ( hasNewFile( child, buildStartTime ) )
1621             {
1622                 return true;
1623             }
1624         }
1625 
1626         return false;
1627     }
1628 
1629     private List<String> resolveProcessorPathEntries()
1630         throws MojoExecutionException
1631     {
1632         if ( annotationProcessorPaths == null || annotationProcessorPaths.isEmpty() )
1633         {
1634             return null;
1635         }
1636 
1637         try
1638         {
1639             Set<Artifact> requiredArtifacts = new LinkedHashSet<>();
1640 
1641             for ( DependencyCoordinate coord : annotationProcessorPaths )
1642             {
1643                 ArtifactHandler handler = artifactHandlerManager.getArtifactHandler( coord.getType() );
1644 
1645                 Artifact artifact = new DefaultArtifact(
1646                      coord.getGroupId(),
1647                      coord.getArtifactId(),
1648                      VersionRange.createFromVersionSpec( coord.getVersion() ),
1649                      Artifact.SCOPE_RUNTIME,
1650                      coord.getType(),
1651                      coord.getClassifier(),
1652                      handler,
1653                      false );
1654 
1655                 requiredArtifacts.add( artifact );
1656             }
1657 
1658             ArtifactResolutionRequest request = new ArtifactResolutionRequest()
1659                             .setArtifact( requiredArtifacts.iterator().next() )
1660                             .setResolveRoot( true )
1661                             .setResolveTransitively( true )
1662                             .setArtifactDependencies( requiredArtifacts )
1663                             .setLocalRepository( session.getLocalRepository() )
1664                             .setRemoteRepositories( project.getRemoteArtifactRepositories() );
1665 
1666             ArtifactResolutionResult resolutionResult = repositorySystem.resolve( request );
1667 
1668             resolutionErrorHandler.throwErrors( request, resolutionResult );
1669 
1670             List<String> elements = new ArrayList<String>( resolutionResult.getArtifacts().size() );
1671 
1672             for ( Object resolved : resolutionResult.getArtifacts() )
1673             {
1674                 elements.add( ( (Artifact) resolved ).getFile().getAbsolutePath() );
1675             }
1676 
1677             return elements;
1678         }
1679         catch ( Exception e )
1680         {
1681             throw new MojoExecutionException( "Resolution of annotationProcessorPath dependencies failed: "
1682                 + e.getLocalizedMessage(), e );
1683         }
1684     }
1685 
1686     private void writePlugin( MessageBuilder mb )
1687     {
1688         mb.a( "    <plugin>" ).newline();
1689         mb.a( "      <groupId>org.apache.maven.plugins</groupId>" ).newline();
1690         mb.a( "      <artifactId>maven-compiler-plugin</artifactId>" ).newline();
1691         
1692         String version = getMavenCompilerPluginVersion();
1693         if ( version != null )
1694         {
1695             mb.a( "      <version>" ).a( version ).a( "</version>" ).newline();
1696         }
1697         writeConfig( mb );
1698         
1699         mb.a( "    </plugin>" ).newline();
1700     }
1701 
1702     private void writeConfig( MessageBuilder mb )
1703     {
1704         mb.a( "      <configuration>" ).newline();
1705 
1706         if ( release != null )
1707         {
1708             mb.a( "        <release>" ).a( release ).a( "</release>" ).newline();
1709         }
1710         else if ( JavaVersion.JAVA_VERSION.isAtLeast( "9" ) )
1711         {
1712             String rls = target.replaceAll( ".\\.", "" );
1713             // when using Java9+, motivate to use release instead of source/target
1714             mb.a( "        <release>" ).a( rls ).a( "</release>" ).newline();
1715         }
1716         else
1717         {
1718             mb.a( "        <source>" ).a( source ).a( "</source>" ).newline();
1719             mb.a( "        <target>" ).a( target ).a( "</target>" ).newline();
1720         }
1721         mb.a( "      </configuration>" ).newline();
1722     }
1723 
1724     private String getMavenCompilerPluginVersion()
1725     {
1726         Properties pomProperties = new Properties();
1727 
1728         try ( InputStream is = AbstractCompilerMojo.class
1729             .getResourceAsStream( "/META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties" ) )
1730         {
1731             if ( is != null )
1732             {
1733                 pomProperties.load( is );
1734             }
1735         }
1736         catch ( IOException e )
1737         {
1738             // noop
1739         }
1740 
1741         return pomProperties.getProperty( "version" );
1742     }
1743 
1744     public void setTarget( String target )
1745     {
1746         this.target = target;
1747         targetOrReleaseSet = true;
1748     }
1749 
1750     public void setRelease( String release )
1751     {
1752         this.release = release;
1753         targetOrReleaseSet = true;
1754     }
1755 }