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