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