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     @SuppressWarnings("checkstyle:MethodLength")
707     public void execute() throws MojoExecutionException, CompilationFailureException {
708         // ----------------------------------------------------------------------
709         // Look up the compiler. This is done before other code than can
710         // cause the mojo to return before the lookup is done possibly resulting
711         // in misconfigured POMs still building.
712         // ----------------------------------------------------------------------
713 
714         Compiler compiler;
715 
716         getLog().debug("Using compiler '" + compilerId + "'.");
717 
718         try {
719             compiler = compilerManager.getCompiler(compilerId);
720         } catch (NoSuchCompilerException e) {
721             throw new MojoExecutionException("No such compiler '" + e.getCompilerId() + "'.", e);
722         }
723 
724         // -----------toolchains start here ----------------------------------
725         // use the compilerId as identifier for toolchains as well.
726         Toolchain tc = getToolchain();
727         if (tc != null) {
728             getLog().info("Toolchain in maven-compiler-plugin: " + tc);
729             if (executable != null) {
730                 getLog().warn("Toolchains are ignored, 'executable' parameter is set to " + executable);
731             } else {
732                 fork = true;
733                 // TODO somehow shaky dependency between compilerId and tool executable.
734                 executable = tc.findTool(compilerId);
735             }
736         }
737         // ----------------------------------------------------------------------
738         //
739         // ----------------------------------------------------------------------
740 
741         List<String> compileSourceRoots = removeEmptyCompileSourceRoots(getCompileSourceRoots());
742 
743         if (compileSourceRoots.isEmpty()) {
744             getLog().info("No sources to compile");
745 
746             return;
747         }
748 
749         // Verify that target or release is set
750         if (!targetOrReleaseSet) {
751             MessageBuilder mb = MessageUtils.buffer()
752                     .a("No explicit value set for target or release! ")
753                     .a("To ensure the same result even after upgrading this plugin, please add ")
754                     .newline()
755                     .newline();
756 
757             writePlugin(mb);
758 
759             getLog().warn(mb.toString());
760         }
761 
762         // ----------------------------------------------------------------------
763         // Create the compiler configuration
764         // ----------------------------------------------------------------------
765 
766         CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
767 
768         compilerConfiguration.setOutputLocation(getOutputDirectory().getAbsolutePath());
769 
770         compilerConfiguration.setOptimize(optimize);
771 
772         compilerConfiguration.setDebug(debug);
773 
774         compilerConfiguration.setDebugFileName(getDebugFileName());
775 
776         compilerConfiguration.setImplicitOption(implicit);
777 
778         if (debug && (debuglevel != null && !debuglevel.isEmpty())) {
779             String[] split = StringUtils.split(debuglevel, ",");
780             for (String aSplit : split) {
781                 if (!(aSplit.equalsIgnoreCase("none")
782                         || aSplit.equalsIgnoreCase("lines")
783                         || aSplit.equalsIgnoreCase("vars")
784                         || aSplit.equalsIgnoreCase("source"))) {
785                     throw new IllegalArgumentException("The specified debug level: '" + aSplit + "' is unsupported. "
786                             + "Legal values are 'none', 'lines', 'vars', and 'source'.");
787                 }
788             }
789             compilerConfiguration.setDebugLevel(debuglevel);
790         }
791 
792         compilerConfiguration.setParameters(parameters);
793 
794         compilerConfiguration.setEnablePreview(enablePreview);
795 
796         compilerConfiguration.setVerbose(verbose);
797 
798         compilerConfiguration.setShowWarnings(showWarnings);
799 
800         compilerConfiguration.setFailOnWarning(failOnWarning);
801 
802         if (failOnWarning && !showWarnings) {
803             getLog().warn("The property failOnWarning is set to true, but showWarnings is set to false.");
804             getLog().warn("With compiler's warnings silenced the failOnWarning has no effect.");
805         }
806 
807         compilerConfiguration.setShowDeprecation(showDeprecation);
808 
809         compilerConfiguration.setSourceVersion(getSource());
810 
811         compilerConfiguration.setTargetVersion(getTarget());
812 
813         compilerConfiguration.setReleaseVersion(getRelease());
814 
815         compilerConfiguration.setProc(proc);
816 
817         File generatedSourcesDirectory = getGeneratedSourcesDirectory();
818         compilerConfiguration.setGeneratedSourcesDirectory(
819                 generatedSourcesDirectory != null ? generatedSourcesDirectory.getAbsoluteFile() : null);
820 
821         if (generatedSourcesDirectory != null) {
822             if (!generatedSourcesDirectory.exists()) {
823                 generatedSourcesDirectory.mkdirs();
824             }
825 
826             String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath();
827 
828             compileSourceRoots.add(generatedSourcesPath);
829 
830             if (isTestCompile()) {
831                 getLog().debug("Adding " + generatedSourcesPath + " to test-compile source roots:\n  "
832                         + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n  "));
833 
834                 project.addTestCompileSourceRoot(generatedSourcesPath);
835 
836                 getLog().debug("New test-compile source roots:\n  "
837                         + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n  "));
838             } else {
839                 getLog().debug("Adding " + generatedSourcesPath + " to compile source roots:\n  "
840                         + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n  "));
841 
842                 project.addCompileSourceRoot(generatedSourcesPath);
843 
844                 getLog().debug("New compile source roots:\n  "
845                         + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n  "));
846             }
847         }
848 
849         compilerConfiguration.setSourceLocations(compileSourceRoots);
850 
851         compilerConfiguration.setAnnotationProcessors(annotationProcessors);
852 
853         compilerConfiguration.setProcessorPathEntries(resolveProcessorPathEntries());
854 
855         compilerConfiguration.setSourceEncoding(encoding);
856 
857         compilerConfiguration.setFork(fork);
858 
859         if (fork) {
860             if (!(meminitial == null || meminitial.isEmpty())) {
861                 String value = getMemoryValue(meminitial);
862 
863                 if (value != null) {
864                     compilerConfiguration.setMeminitial(value);
865                 } else {
866                     getLog().info("Invalid value for meminitial '" + meminitial + "'. Ignoring this option.");
867                 }
868             }
869 
870             if (!(maxmem == null || maxmem.isEmpty())) {
871                 String value = getMemoryValue(maxmem);
872 
873                 if (value != null) {
874                     compilerConfiguration.setMaxmem(value);
875                 } else {
876                     getLog().info("Invalid value for maxmem '" + maxmem + "'. Ignoring this option.");
877                 }
878             }
879         }
880 
881         compilerConfiguration.setExecutable(executable);
882 
883         compilerConfiguration.setWorkingDirectory(basedir);
884 
885         compilerConfiguration.setCompilerVersion(compilerVersion);
886 
887         compilerConfiguration.setBuildDirectory(buildDirectory);
888 
889         compilerConfiguration.setOutputFileName(outputFileName);
890 
891         if (CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals(this.compilerReuseStrategy)) {
892             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.AlwaysNew);
893         } else if (CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy()
894                 .equals(this.compilerReuseStrategy)) {
895             if (getRequestThreadCount() > 1) {
896                 if (!skipMultiThreadWarning) {
897                     getLog().warn("You are in a multi-thread build and compilerReuseStrategy is set to reuseSame."
898                             + " This can cause issues in some environments (os/jdk)!"
899                             + " Consider using reuseCreated strategy."
900                             + System.getProperty("line.separator")
901                             + "If your env is fine with reuseSame, you can skip this warning with the "
902                             + "configuration field skipMultiThreadWarning "
903                             + "or -Dmaven.compiler.skipMultiThreadWarning=true");
904                 }
905             }
906             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseSame);
907         } else {
908 
909             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseCreated);
910         }
911 
912         getLog().debug("CompilerReuseStrategy: "
913                 + compilerConfiguration.getCompilerReuseStrategy().getStrategy());
914 
915         compilerConfiguration.setForceJavacCompilerUse(forceLegacyJavacApi || forceJavacCompilerUse);
916 
917         boolean canUpdateTarget;
918 
919         IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper(mojoExecution, session);
920 
921         final Set<File> sources;
922 
923         IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
924 
925         if (useIncrementalCompilation) {
926             getLog().debug("useIncrementalCompilation enabled");
927             try {
928                 canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration);
929 
930                 sources = getCompileSources(compiler, compilerConfiguration);
931 
932                 preparePaths(sources);
933 
934                 incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles(sources);
935 
936                 // Strategies used to detect modifications.
937                 String immutableOutputFile = (compiler.getCompilerOutputStyle()
938                                         .equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES)
939                                 && !canUpdateTarget)
940                         ? "immutable single output file"
941                         : null;
942                 String dependencyChanged = isDependencyChanged() ? "changed dependency" : null;
943                 String sourceChanged = isSourceChanged(compilerConfiguration, compiler) ? "changed source code" : null;
944                 String inputFileTreeChanged = hasInputFileTreeChanged(incrementalBuildHelper, sources)
945                         ? "added or removed source files"
946                         : null;
947 
948                 // Get the first cause for the rebuild compilation detection.
949                 String cause = Stream.of(immutableOutputFile, dependencyChanged, sourceChanged, inputFileTreeChanged)
950                         .filter(Objects::nonNull)
951                         .findFirst()
952                         .orElse(null);
953 
954                 if (cause != null) {
955                     getLog().info("Recompiling the module because of "
956                             + MessageUtils.buffer().strong(cause) + ".");
957                     compilerConfiguration.setSourceFiles(sources);
958                 } else {
959                     getLog().info("Nothing to compile - all classes are up to date.");
960                     return;
961                 }
962             } catch (CompilerException e) {
963                 throw new MojoExecutionException("Error while computing stale sources.", e);
964             }
965         } else {
966             getLog().debug("useIncrementalCompilation disabled");
967 
968             Set<File> staleSources;
969             try {
970                 staleSources =
971                         computeStaleSources(compilerConfiguration, compiler, getSourceInclusionScanner(staleMillis));
972 
973                 canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration);
974 
975                 if (compiler.getCompilerOutputStyle().equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES)
976                         && !canUpdateTarget) {
977                     getLog().info("RESCANNING!");
978                     // TODO: This second scan for source files is sub-optimal
979                     String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration);
980 
981                     staleSources = computeStaleSources(
982                             compilerConfiguration, compiler, getSourceInclusionScanner(inputFileEnding));
983                 }
984 
985             } catch (CompilerException e) {
986                 throw new MojoExecutionException("Error while computing stale sources.", e);
987             }
988 
989             if (staleSources.isEmpty()) {
990                 getLog().info("Nothing to compile - all classes are up to date.");
991                 return;
992             }
993 
994             compilerConfiguration.setSourceFiles(staleSources);
995 
996             try {
997                 // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath
998                 sources = getCompileSources(compiler, compilerConfiguration);
999 
1000                 if (getLog().isDebugEnabled()) {
1001                     getLog().debug("#sources: " + sources.size());
1002                     for (File file : sources) {
1003                         getLog().debug(file.getPath());
1004                     }
1005                 }
1006 
1007                 preparePaths(sources);
1008             } catch (CompilerException e) {
1009                 throw new MojoExecutionException("Error while computing stale sources.", e);
1010             }
1011         }
1012 
1013         // Dividing pathElements of classPath and modulePath is based on sourceFiles
1014         compilerConfiguration.setClasspathEntries(getClasspathElements());
1015 
1016         compilerConfiguration.setModulepathEntries(getModulepathElements());
1017 
1018         compilerConfiguration.setIncludes(getIncludes());
1019 
1020         compilerConfiguration.setExcludes(getExcludes());
1021 
1022         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
1023 
1024         String effectiveCompilerArgument = getCompilerArgument();
1025 
1026         if ((effectiveCompilerArguments != null) || (effectiveCompilerArgument != null) || (compilerArgs != null)) {
1027             if (effectiveCompilerArguments != null) {
1028                 for (Map.Entry<String, String> me : effectiveCompilerArguments.entrySet()) {
1029                     String key = me.getKey();
1030                     String value = me.getValue();
1031                     if (!key.startsWith("-")) {
1032                         key = "-" + key;
1033                     }
1034 
1035                     if (key.startsWith("-A") && (value != null && !value.isEmpty())) {
1036                         compilerConfiguration.addCompilerCustomArgument(key + "=" + value, null);
1037                     } else {
1038                         compilerConfiguration.addCompilerCustomArgument(key, value);
1039                     }
1040                 }
1041             }
1042             if (!(effectiveCompilerArgument == null || effectiveCompilerArgument.isEmpty())) {
1043                 compilerConfiguration.addCompilerCustomArgument(effectiveCompilerArgument, null);
1044             }
1045             if (compilerArgs != null) {
1046                 for (String arg : compilerArgs) {
1047                     compilerConfiguration.addCompilerCustomArgument(arg, null);
1048                 }
1049             }
1050         }
1051 
1052         // ----------------------------------------------------------------------
1053         // Dump configuration
1054         // ----------------------------------------------------------------------
1055         if (getLog().isDebugEnabled()) {
1056             getLog().debug("Classpath:");
1057 
1058             for (String s : getClasspathElements()) {
1059                 getLog().debug(" " + s);
1060             }
1061 
1062             if (!getModulepathElements().isEmpty()) {
1063                 getLog().debug("Modulepath:");
1064                 for (String s : getModulepathElements()) {
1065                     getLog().debug(" " + s);
1066                 }
1067             }
1068 
1069             getLog().debug("Source roots:");
1070 
1071             for (String root : getCompileSourceRoots()) {
1072                 getLog().debug(" " + root);
1073             }
1074 
1075             try {
1076                 if (fork) {
1077                     if (compilerConfiguration.getExecutable() != null) {
1078                         getLog().debug("Executable: ");
1079                         getLog().debug(" " + compilerConfiguration.getExecutable());
1080                     }
1081                 }
1082 
1083                 String[] cl = compiler.createCommandLine(compilerConfiguration);
1084                 if (cl != null && cl.length > 0) {
1085                     StringBuilder sb = new StringBuilder();
1086                     sb.append(cl[0]);
1087                     for (int i = 1; i < cl.length; i++) {
1088                         sb.append(" ");
1089                         sb.append(cl[i]);
1090                     }
1091                     getLog().debug("Command line options:");
1092                     getLog().debug(sb);
1093                 }
1094             } catch (CompilerException ce) {
1095                 getLog().debug(ce);
1096             }
1097         }
1098 
1099         List<String> jpmsLines = new ArrayList<>();
1100 
1101         // See http://openjdk.java.net/jeps/261
1102         final List<String> runtimeArgs = Arrays.asList(
1103                 "--upgrade-module-path", "--add-exports", "--add-reads", "--add-modules", "--limit-modules");
1104 
1105         // Custom arguments are all added as keys to an ordered Map
1106         Iterator<Map.Entry<String, String>> entryIter =
1107                 compilerConfiguration.getCustomCompilerArgumentsEntries().iterator();
1108         while (entryIter.hasNext()) {
1109             Map.Entry<String, String> entry = entryIter.next();
1110 
1111             if (runtimeArgs.contains(entry.getKey())) {
1112                 jpmsLines.add(entry.getKey());
1113 
1114                 String value = entry.getValue();
1115                 if (value == null) {
1116                     entry = entryIter.next();
1117                     value = entry.getKey();
1118                 }
1119                 jpmsLines.add(value);
1120             } else if ("--patch-module".equals(entry.getKey())) {
1121                 String value = entry.getValue();
1122                 if (value == null) {
1123                     entry = entryIter.next();
1124                     value = entry.getKey();
1125                 }
1126 
1127                 String[] values = value.split("=");
1128 
1129                 StringBuilder patchModule = new StringBuilder(values[0]);
1130                 patchModule.append('=');
1131 
1132                 Set<String> patchModules = new LinkedHashSet<>();
1133                 Set<Path> sourceRoots = new HashSet<>(getCompileSourceRoots().size());
1134                 for (String sourceRoot : getCompileSourceRoots()) {
1135                     sourceRoots.add(Paths.get(sourceRoot));
1136                 }
1137 
1138                 String[] files = values[1].split(PS);
1139 
1140                 for (String file : files) {
1141                     Path filePath = Paths.get(file);
1142                     if (getOutputDirectory().toPath().equals(filePath)) {
1143                         patchModules.add("_"); // this jar
1144                     } else if (getOutputDirectory().toPath().startsWith(filePath)) {
1145                         // multirelease, can be ignored
1146                         continue;
1147                     } else if (sourceRoots.contains(filePath)) {
1148                         patchModules.add("_"); // this jar
1149                     } else {
1150                         JavaModuleDescriptor descriptor = getPathElements().get(file);
1151 
1152                         if (descriptor == null) {
1153                             if (Files.isDirectory(filePath)) {
1154                                 patchModules.add(file);
1155                             } else {
1156                                 getLog().warn("Can't locate " + file);
1157                             }
1158                         } else if (!values[0].equals(descriptor.name())) {
1159                             patchModules.add(descriptor.name());
1160                         }
1161                     }
1162                 }
1163 
1164                 StringBuilder sb = new StringBuilder();
1165 
1166                 if (!patchModules.isEmpty()) {
1167                     for (String mod : patchModules) {
1168                         if (sb.length() > 0) {
1169                             sb.append(", ");
1170                         }
1171                         // use 'invalid' separator to ensure values are transformed
1172                         sb.append(mod);
1173                     }
1174 
1175                     jpmsLines.add("--patch-module");
1176                     jpmsLines.add(patchModule + sb.toString());
1177                 }
1178             }
1179         }
1180 
1181         if (!jpmsLines.isEmpty()) {
1182             Path jpmsArgs = Paths.get(getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args");
1183             try {
1184                 Files.createDirectories(jpmsArgs.getParent());
1185 
1186                 Files.write(jpmsArgs, jpmsLines, Charset.defaultCharset());
1187             } catch (IOException e) {
1188                 getLog().warn(e.getMessage());
1189             }
1190         }
1191 
1192         // ----------------------------------------------------------------------
1193         // Compile!
1194         // ----------------------------------------------------------------------
1195 
1196         if (StringUtils.isEmpty(compilerConfiguration.getSourceEncoding())) {
1197             getLog().warn("File encoding has not been set, using platform encoding "
1198                     + MessageUtils.buffer().strong(Charset.defaultCharset())
1199                     + ", i.e. build is platform dependent!");
1200         }
1201 
1202         CompilerResult compilerResult;
1203 
1204         if (useIncrementalCompilation) {
1205             incrementalBuildHelperRequest.outputDirectory(getOutputDirectory());
1206 
1207             // MCOMPILER-333: Cleanup the generated source files created by annotation processing
1208             // to avoid issues with `javac` compiler when the source code is rebuild.
1209             if (getGeneratedSourcesDirectory() != null) {
1210                 try (Stream<Path> walk =
1211                         Files.walk(getGeneratedSourcesDirectory().toPath())) {
1212                     walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
1213                     // MCOMPILER-567: The directory must already exist because javac does not create it.
1214                     Files.createDirectories(getGeneratedSourcesDirectory().toPath());
1215                 } catch (IOException ex) {
1216                     getLog().warn("I/O error deleting the annotation processing generated files: " + ex.getMessage());
1217                 }
1218             }
1219 
1220             incrementalBuildHelper.beforeRebuildExecution(incrementalBuildHelperRequest);
1221 
1222             getLog().debug("incrementalBuildHelper#beforeRebuildExecution");
1223         }
1224 
1225         try {
1226             compilerResult = compiler.performCompile(compilerConfiguration);
1227         } catch (Exception e) {
1228             // TODO: don't catch Exception
1229             throw new MojoExecutionException("Fatal error compiling", e);
1230         }
1231 
1232         if (createMissingPackageInfoClass
1233                 && compilerResult.isSuccess()
1234                 && compiler.getCompilerOutputStyle() == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) {
1235             try {
1236                 SourceMapping sourceMapping = getSourceMapping(compilerConfiguration, compiler);
1237                 createMissingPackageInfoClasses(compilerConfiguration, sourceMapping, sources);
1238             } catch (Exception e) {
1239                 getLog().warn("Error creating missing package info classes", e);
1240             }
1241         }
1242 
1243         if (outputTimestamp != null
1244                 && !outputTimestamp.isEmpty()
1245                 && (outputTimestamp.length() > 1 || Character.isDigit(outputTimestamp.charAt(0)))) {
1246             // if Reproducible Builds mode, apply workaround
1247             patchJdkModuleVersion(compilerResult, sources);
1248         }
1249 
1250         if (useIncrementalCompilation) {
1251             if (incrementalBuildHelperRequest.getOutputDirectory().exists()) {
1252                 getLog().debug("incrementalBuildHelper#afterRebuildExecution");
1253                 // now scan the same directory again and create a diff
1254                 incrementalBuildHelper.afterRebuildExecution(incrementalBuildHelperRequest);
1255             } else {
1256                 getLog().debug(
1257                                 "skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist");
1258             }
1259         }
1260 
1261         List<CompilerMessage> warnings = new ArrayList<>();
1262         List<CompilerMessage> errors = new ArrayList<>();
1263         List<CompilerMessage> others = new ArrayList<>();
1264         for (CompilerMessage message : compilerResult.getCompilerMessages()) {
1265             switch (message.getKind()) {
1266                 case ERROR:
1267                     errors.add(message);
1268                     break;
1269                 case WARNING:
1270                 case MANDATORY_WARNING:
1271                     warnings.add(message);
1272                     break;
1273                 default:
1274                     others.add(message);
1275                     break;
1276             }
1277         }
1278 
1279         if (failOnError && !compilerResult.isSuccess()) {
1280             for (CompilerMessage message : others) {
1281                 assert message.getKind() != CompilerMessage.Kind.ERROR
1282                         && message.getKind() != CompilerMessage.Kind.WARNING
1283                         && message.getKind() != CompilerMessage.Kind.MANDATORY_WARNING;
1284                 getLog().info(message.toString());
1285             }
1286             if (!warnings.isEmpty()) {
1287                 getLog().info("-------------------------------------------------------------");
1288                 getLog().warn("COMPILATION WARNING : ");
1289                 getLog().info("-------------------------------------------------------------");
1290                 for (CompilerMessage warning : warnings) {
1291                     getLog().warn(warning.toString());
1292                 }
1293                 getLog().info(warnings.size() + ((warnings.size() > 1) ? " warnings " : " warning"));
1294                 getLog().info("-------------------------------------------------------------");
1295             }
1296 
1297             if (!errors.isEmpty()) {
1298                 getLog().info("-------------------------------------------------------------");
1299                 getLog().error("COMPILATION ERROR : ");
1300                 getLog().info("-------------------------------------------------------------");
1301                 for (CompilerMessage error : errors) {
1302                     getLog().error(error.toString());
1303                 }
1304                 getLog().info(errors.size() + ((errors.size() > 1) ? " errors " : " error"));
1305                 getLog().info("-------------------------------------------------------------");
1306             }
1307 
1308             if (!errors.isEmpty()) {
1309                 throw new CompilationFailureException(errors);
1310             } else {
1311                 throw new CompilationFailureException(warnings);
1312             }
1313         } else {
1314             for (CompilerMessage message : compilerResult.getCompilerMessages()) {
1315                 switch (message.getKind()) {
1316                     case NOTE:
1317                     case OTHER:
1318                         getLog().info(message.toString());
1319                         break;
1320                     case ERROR:
1321                         getLog().error(message.toString());
1322                         break;
1323                     case MANDATORY_WARNING:
1324                     case WARNING:
1325                     default:
1326                         getLog().warn(message.toString());
1327                         break;
1328                 }
1329             }
1330         }
1331     }
1332 
1333     private void createMissingPackageInfoClasses(
1334             CompilerConfiguration compilerConfiguration, SourceMapping sourceMapping, Set<File> sources)
1335             throws InclusionScanException, IOException {
1336         for (File source : sources) {
1337             String path = source.toString();
1338             if (path.endsWith(File.separator + "package-info.java")) {
1339                 for (String root : getCompileSourceRoots()) {
1340                     root = root + File.separator;
1341                     if (path.startsWith(root)) {
1342                         String rel = path.substring(root.length());
1343                         Set<File> files = sourceMapping.getTargetFiles(getOutputDirectory(), rel);
1344                         for (File file : files) {
1345                             if (!file.exists()) {
1346                                 File parentFile = file.getParentFile();
1347 
1348                                 if (!parentFile.exists()) {
1349                                     Files.createDirectories(parentFile.toPath());
1350                                 }
1351 
1352                                 byte[] bytes = generatePackage(compilerConfiguration, rel);
1353                                 Files.write(file.toPath(), bytes);
1354                             }
1355                         }
1356                     }
1357                 }
1358             }
1359         }
1360     }
1361 
1362     private byte[] generatePackage(CompilerConfiguration compilerConfiguration, String javaFile) {
1363         int version = getOpcode(compilerConfiguration);
1364         String internalPackageName = javaFile.substring(0, javaFile.length() - ".java".length());
1365         if (File.separatorChar != '/') {
1366             internalPackageName = internalPackageName.replace(File.separatorChar, '/');
1367         }
1368         ClassWriter cw = new ClassWriter(0);
1369         cw.visit(
1370                 version,
1371                 Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE,
1372                 internalPackageName,
1373                 null,
1374                 "java/lang/Object",
1375                 null);
1376         cw.visitSource("package-info.java", null);
1377         return cw.toByteArray();
1378     }
1379 
1380     private int getOpcode(CompilerConfiguration compilerConfiguration) {
1381         String version = compilerConfiguration.getReleaseVersion();
1382         if (version == null) {
1383             version = compilerConfiguration.getTargetVersion();
1384             if (version == null) {
1385                 version = "1.5";
1386             }
1387         }
1388         if (version.startsWith("1.")) {
1389             version = version.substring(2);
1390         }
1391         int iVersion = Integer.parseInt(version);
1392         if (iVersion < 2) {
1393             throw new IllegalArgumentException("Unsupported java version '" + version + "'");
1394         }
1395         return iVersion - 2 + Opcodes.V1_2;
1396     }
1397 
1398     protected boolean isTestCompile() {
1399         return false;
1400     }
1401 
1402     /**
1403      * @return all source files for the compiler
1404      */
1405     private Set<File> getCompileSources(Compiler compiler, CompilerConfiguration compilerConfiguration)
1406             throws MojoExecutionException, CompilerException {
1407         String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration);
1408         if (inputFileEnding == null || inputFileEnding.isEmpty()) {
1409             // see MCOMPILER-199 GroovyEclipseCompiler doesn't set inputFileEnding
1410             // so we can presume it's all files from the source directory
1411             inputFileEnding = ".*";
1412         }
1413         SourceInclusionScanner scanner = getSourceInclusionScanner(inputFileEnding);
1414 
1415         SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler);
1416 
1417         scanner.addSourceMapping(mapping);
1418 
1419         Set<File> compileSources = new HashSet<>();
1420 
1421         for (String sourceRoot : getCompileSourceRoots()) {
1422             File rootFile = new File(sourceRoot);
1423 
1424             if (!rootFile.isDirectory()
1425                     || rootFile.getAbsoluteFile().equals(compilerConfiguration.getGeneratedSourcesDirectory())) {
1426                 continue;
1427             }
1428 
1429             try {
1430                 compileSources.addAll(scanner.getIncludedSources(rootFile, null));
1431             } catch (InclusionScanException e) {
1432                 throw new MojoExecutionException(
1433                         "Error scanning source root: '" + sourceRoot + "' for stale files to recompile.", e);
1434             }
1435         }
1436 
1437         return compileSources;
1438     }
1439 
1440     protected abstract Set<String> getIncludes();
1441 
1442     protected abstract Set<String> getExcludes();
1443 
1444     /**
1445      * @param compilerConfiguration
1446      * @param compiler
1447      * @return {@code true} if at least a single source file is newer than it's class file
1448      */
1449     private boolean isSourceChanged(CompilerConfiguration compilerConfiguration, Compiler compiler) {
1450         Set<File> staleSources = Collections.emptySet();
1451         try {
1452             staleSources = computeStaleSources(compilerConfiguration, compiler, getSourceInclusionScanner(staleMillis));
1453         } catch (MojoExecutionException | CompilerException ex) {
1454             // we cannot detect Stale Sources, so don't do anything beside logging
1455             getLog().warn("Cannot detect stale sources.");
1456             return false;
1457         }
1458 
1459         if (getLog().isDebugEnabled() || showCompilationChanges) {
1460             for (File f : staleSources) {
1461                 getLog().info("\tStale source detected: " + f.getAbsolutePath());
1462             }
1463         }
1464         return !staleSources.isEmpty();
1465     }
1466 
1467     /**
1468      * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependent
1469      *
1470      * @return number of thread for this build or 1 if not multi-thread build
1471      */
1472     protected int getRequestThreadCount() {
1473         return session.getRequest().getDegreeOfConcurrency();
1474     }
1475 
1476     protected Date getBuildStartTime() {
1477         return getBuildStartTimeInstant().map(Date::from).orElseGet(Date::new);
1478     }
1479 
1480     private Optional<Instant> getBuildStartTimeInstant() {
1481         return Optional.ofNullable(session.getRequest())
1482                 .map(MavenExecutionRequest::getStartTime)
1483                 .map(Date::toInstant)
1484                 .map(i -> i.truncatedTo(ChronoUnit.MILLIS));
1485     }
1486 
1487     private String getMemoryValue(String setting) {
1488         String value = null;
1489 
1490         // Allow '128' or '128m'
1491         if (isDigits(setting)) {
1492             value = setting + "m";
1493         } else if ((isDigits(setting.substring(0, setting.length() - 1)))
1494                 && (setting.toLowerCase().endsWith("m"))) {
1495             value = setting;
1496         }
1497         return value;
1498     }
1499 
1500     protected final Toolchain getToolchain() {
1501         Toolchain tc = null;
1502 
1503         if (jdkToolchain != null) {
1504             List<Toolchain> tcs = toolchainManager.getToolchains(session, "jdk", jdkToolchain);
1505             if (tcs != null && !tcs.isEmpty()) {
1506                 tc = tcs.get(0);
1507             }
1508         }
1509 
1510         if (tc == null) {
1511             tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
1512         }
1513 
1514         return tc;
1515     }
1516 
1517     private boolean isDigits(String string) {
1518         for (int i = 0; i < string.length(); i++) {
1519             if (!Character.isDigit(string.charAt(i))) {
1520                 return false;
1521             }
1522         }
1523         return true;
1524     }
1525 
1526     private Set<File> computeStaleSources(
1527             CompilerConfiguration compilerConfiguration, Compiler compiler, SourceInclusionScanner scanner)
1528             throws MojoExecutionException, CompilerException {
1529         SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler);
1530 
1531         File outputDirectory;
1532         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1533         if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) {
1534             outputDirectory = buildDirectory;
1535         } else {
1536             outputDirectory = getOutputDirectory();
1537         }
1538 
1539         scanner.addSourceMapping(mapping);
1540 
1541         Set<File> staleSources = new HashSet<>();
1542 
1543         for (String sourceRoot : getCompileSourceRoots()) {
1544             File rootFile = new File(sourceRoot);
1545 
1546             if (!rootFile.isDirectory()) {
1547                 continue;
1548             }
1549 
1550             try {
1551                 staleSources.addAll(scanner.getIncludedSources(rootFile, outputDirectory));
1552             } catch (InclusionScanException e) {
1553                 throw new MojoExecutionException(
1554                         "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e);
1555             }
1556         }
1557 
1558         return staleSources;
1559     }
1560 
1561     private SourceMapping getSourceMapping(CompilerConfiguration compilerConfiguration, Compiler compiler)
1562             throws CompilerException, MojoExecutionException {
1563         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1564 
1565         SourceMapping mapping;
1566         if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) {
1567             mapping = new SuffixMapping(
1568                     compiler.getInputFileEnding(compilerConfiguration),
1569                     compiler.getOutputFileEnding(compilerConfiguration));
1570         } else if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) {
1571             mapping = new SingleTargetSourceMapping(
1572                     compiler.getInputFileEnding(compilerConfiguration), compiler.getOutputFile(compilerConfiguration));
1573 
1574         } else {
1575             throw new MojoExecutionException("Unknown compiler output style: '" + outputStyle + "'.");
1576         }
1577         return mapping;
1578     }
1579 
1580     /**
1581      * @todo also in ant plugin. This should be resolved at some point so that it does not need to
1582      * be calculated continuously - or should the plugins accept empty source roots as is?
1583      */
1584     private static List<String> removeEmptyCompileSourceRoots(List<String> compileSourceRootsList) {
1585         List<String> newCompileSourceRootsList = new ArrayList<>();
1586         if (compileSourceRootsList != null) {
1587             // copy as I may be modifying it
1588             for (String srcDir : compileSourceRootsList) {
1589                 if (!newCompileSourceRootsList.contains(srcDir) && new File(srcDir).exists()) {
1590                     newCompileSourceRootsList.add(srcDir);
1591                 }
1592             }
1593         }
1594         return newCompileSourceRootsList;
1595     }
1596 
1597     /**
1598      * We just compare the timestamps of all local dependency files (inter-module dependency classpath) and the own
1599      * generated classes and if we got a file which is &gt;= the build-started timestamp, then we caught a file which
1600      * got changed during this build.
1601      *
1602      * @return {@code true} if at least one single dependency has changed.
1603      */
1604     protected boolean isDependencyChanged() {
1605         final Instant buildStartTime = getBuildStartTimeInstant().orElse(null);
1606         if (buildStartTime == null) {
1607             // we just cannot determine it, so don't do anything beside logging
1608             getLog().debug("Cannot determine build start time, skipping incremental build detection.");
1609             return false;
1610         }
1611 
1612         if (fileExtensions == null || fileExtensions.isEmpty()) {
1613             fileExtensions = new HashSet<>(Arrays.asList("class", "jar"));
1614         }
1615 
1616         List<String> pathElements = new ArrayList<>();
1617         pathElements.addAll(getClasspathElements());
1618         pathElements.addAll(getModulepathElements());
1619 
1620         for (String pathElement : pathElements) {
1621             Path artifactPath = Paths.get(pathElement);
1622 
1623             // Search files only on dependencies (other modules), not on the current project,
1624             if (Files.isDirectory(artifactPath)
1625                     && !artifactPath.equals(getOutputDirectory().toPath())) {
1626                 try (Stream<Path> walk = Files.walk(artifactPath)) {
1627                     if (walk.anyMatch(p -> hasNewFile(p, buildStartTime))) {
1628                         return true;
1629                     }
1630                 } catch (IOException ex) {
1631                     // we just cannot determine it, so don't do anything beside logging
1632                     getLog().warn("I/O error walking the path: " + ex.getMessage());
1633                     return false;
1634                 }
1635             } else if (hasNewFile(artifactPath, buildStartTime)) {
1636                 return true;
1637             }
1638         }
1639 
1640         // obviously there was no new file detected.
1641         return false;
1642     }
1643 
1644     /**
1645      * @param file entry to check
1646      * @param buildStartTime time build start
1647      * @return if any changes occurred
1648      */
1649     private boolean hasNewFile(Path file, Instant buildStartTime) {
1650         if (Files.isRegularFile(file)
1651                 && fileExtensions.contains(
1652                         FileUtils.extension(file.getFileName().toString()))) {
1653             try {
1654                 Instant lastModifiedTime = Files.getLastModifiedTime(file)
1655                         .toInstant()
1656                         .minusMillis(staleMillis)
1657                         .truncatedTo(ChronoUnit.MILLIS);
1658                 boolean hasChanged = lastModifiedTime.isAfter(buildStartTime);
1659                 if (hasChanged && (getLog().isDebugEnabled() || showCompilationChanges)) {
1660                     getLog().info("\tNew dependency detected: " + file.toAbsolutePath());
1661                 }
1662                 return hasChanged;
1663             } catch (IOException ex) {
1664                 // we just cannot determine it, so don't do anything beside logging
1665                 getLog().warn("I/O error reading the lastModifiedTime: " + ex.getMessage());
1666             }
1667         }
1668 
1669         return false;
1670     }
1671 
1672     private List<String> resolveProcessorPathEntries() throws MojoExecutionException {
1673         if (annotationProcessorPaths == null || annotationProcessorPaths.isEmpty()) {
1674             return null;
1675         }
1676 
1677         try {
1678             List<org.eclipse.aether.graph.Dependency> dependencies = convertToDependencies(annotationProcessorPaths);
1679             List<org.eclipse.aether.graph.Dependency> managedDependencies =
1680                     getManagedDependenciesForAnnotationProcessorPaths();
1681             CollectRequest collectRequest =
1682                     new CollectRequest(dependencies, managedDependencies, project.getRemoteProjectRepositories());
1683             DependencyRequest dependencyRequest = new DependencyRequest();
1684             dependencyRequest.setCollectRequest(collectRequest);
1685             DependencyResult dependencyResult =
1686                     repositorySystem.resolveDependencies(session.getRepositorySession(), dependencyRequest);
1687 
1688             return dependencyResult.getArtifactResults().stream()
1689                     .map(resolved -> resolved.getArtifact().getFile().getAbsolutePath())
1690                     .collect(Collectors.toList());
1691         } catch (Exception e) {
1692             throw new MojoExecutionException(
1693                     "Resolution of annotationProcessorPath dependencies failed: " + e.getLocalizedMessage(), e);
1694         }
1695     }
1696 
1697     private List<org.eclipse.aether.graph.Dependency> convertToDependencies(
1698             List<DependencyCoordinate> annotationProcessorPaths) throws MojoExecutionException {
1699         List<org.eclipse.aether.graph.Dependency> dependencies = new ArrayList<>();
1700         for (DependencyCoordinate annotationProcessorPath : annotationProcessorPaths) {
1701             ArtifactHandler handler = artifactHandlerManager.getArtifactHandler(annotationProcessorPath.getType());
1702             String version = getAnnotationProcessorPathVersion(annotationProcessorPath);
1703             Artifact artifact = new DefaultArtifact(
1704                     annotationProcessorPath.getGroupId(),
1705                     annotationProcessorPath.getArtifactId(),
1706                     annotationProcessorPath.getClassifier(),
1707                     handler.getExtension(),
1708                     version);
1709             Set<Exclusion> exclusions = convertToAetherExclusions(annotationProcessorPath.getExclusions());
1710             dependencies.add(new org.eclipse.aether.graph.Dependency(artifact, JavaScopes.RUNTIME, false, exclusions));
1711         }
1712         return dependencies;
1713     }
1714 
1715     private String getAnnotationProcessorPathVersion(DependencyCoordinate annotationProcessorPath)
1716             throws MojoExecutionException {
1717         String configuredVersion = annotationProcessorPath.getVersion();
1718         if (configuredVersion != null) {
1719             return configuredVersion;
1720         } else {
1721             List<Dependency> managedDependencies = getProjectManagedDependencies();
1722             return findManagedVersion(annotationProcessorPath, managedDependencies)
1723                     .orElseThrow(() -> new MojoExecutionException(String.format(
1724                             "Cannot find version for annotation processor path '%s'. The version needs to be either"
1725                                     + " provided directly in the plugin configuration or via dependency management.",
1726                             annotationProcessorPath)));
1727         }
1728     }
1729 
1730     private Optional<String> findManagedVersion(
1731             DependencyCoordinate dependencyCoordinate, List<Dependency> managedDependencies) {
1732         return managedDependencies.stream()
1733                 .filter(dep -> Objects.equals(dep.getGroupId(), dependencyCoordinate.getGroupId())
1734                         && Objects.equals(dep.getArtifactId(), dependencyCoordinate.getArtifactId())
1735                         && Objects.equals(dep.getClassifier(), dependencyCoordinate.getClassifier())
1736                         && Objects.equals(dep.getType(), dependencyCoordinate.getType()))
1737                 .findAny()
1738                 .map(org.apache.maven.model.Dependency::getVersion);
1739     }
1740 
1741     private List<org.eclipse.aether.graph.Dependency> getManagedDependenciesForAnnotationProcessorPaths() {
1742         if (!annotationProcessorPathsUseDepMgmt) {
1743             return Collections.emptyList();
1744         }
1745         List<Dependency> projectManagedDependencies = getProjectManagedDependencies();
1746         ArtifactTypeRegistry artifactTypeRegistry =
1747                 session.getRepositorySession().getArtifactTypeRegistry();
1748 
1749         return projectManagedDependencies.stream()
1750                 .map(dep -> RepositoryUtils.toDependency(dep, artifactTypeRegistry))
1751                 .collect(Collectors.toList());
1752     }
1753 
1754     private List<Dependency> getProjectManagedDependencies() {
1755         DependencyManagement dependencyManagement = project.getDependencyManagement();
1756         if (dependencyManagement == null || dependencyManagement.getDependencies() == null) {
1757             return Collections.emptyList();
1758         }
1759         return dependencyManagement.getDependencies();
1760     }
1761 
1762     private Set<Exclusion> convertToAetherExclusions(Set<DependencyExclusion> exclusions) {
1763         if (exclusions == null || exclusions.isEmpty()) {
1764             return Collections.emptySet();
1765         }
1766         Set<Exclusion> aetherExclusions = new HashSet<>();
1767         for (DependencyExclusion exclusion : exclusions) {
1768             Exclusion aetherExclusion = new Exclusion(
1769                     exclusion.getGroupId(),
1770                     exclusion.getArtifactId(),
1771                     exclusion.getClassifier(),
1772                     exclusion.getExtension());
1773             aetherExclusions.add(aetherExclusion);
1774         }
1775         return aetherExclusions;
1776     }
1777 
1778     private void writePlugin(MessageBuilder mb) {
1779         mb.a("    <plugin>").newline();
1780         mb.a("      <groupId>org.apache.maven.plugins</groupId>").newline();
1781         mb.a("      <artifactId>maven-compiler-plugin</artifactId>").newline();
1782 
1783         String version = getMavenCompilerPluginVersion();
1784         if (version != null) {
1785             mb.a("      <version>").a(version).a("</version>").newline();
1786         }
1787         writeConfig(mb);
1788 
1789         mb.a("    </plugin>").newline();
1790     }
1791 
1792     private void writeConfig(MessageBuilder mb) {
1793         mb.a("      <configuration>").newline();
1794 
1795         if (release != null) {
1796             mb.a("        <release>").a(release).a("</release>").newline();
1797         } else if (JavaVersion.JAVA_VERSION.isAtLeast("9")) {
1798             String rls = target.replaceAll(".\\.", "");
1799             // when using Java9+, motivate to use release instead of source/target
1800             mb.a("        <release>").a(rls).a("</release>").newline();
1801         } else {
1802             mb.a("        <source>").a(source).a("</source>").newline();
1803             mb.a("        <target>").a(target).a("</target>").newline();
1804         }
1805         mb.a("      </configuration>").newline();
1806     }
1807 
1808     private String getMavenCompilerPluginVersion() {
1809         Properties pomProperties = new Properties();
1810 
1811         try (InputStream is = AbstractCompilerMojo.class.getResourceAsStream(
1812                 "/META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties")) {
1813             if (is != null) {
1814                 pomProperties.load(is);
1815             }
1816         } catch (IOException e) {
1817             // noop
1818         }
1819 
1820         return pomProperties.getProperty("version");
1821     }
1822 
1823     private boolean hasInputFileTreeChanged(IncrementalBuildHelper ibh, Set<File> inputFiles) {
1824         Path mojoConfigBase;
1825         try {
1826             mojoConfigBase = ibh.getMojoStatusDirectory().toPath();
1827         } catch (MojoExecutionException e) {
1828             // we cannot get the mojo status dir, so don't do anything beside logging
1829             getLog().warn("Error reading mojo status directory.");
1830             return false;
1831         }
1832         Path mojoConfigFile = mojoConfigBase.resolve(INPUT_FILES_LST_FILENAME);
1833 
1834         List<String> oldInputFiles = Collections.emptyList();
1835         if (Files.isRegularFile(mojoConfigFile)) {
1836             try {
1837                 oldInputFiles = Files.readAllLines(mojoConfigFile);
1838             } catch (IOException e) {
1839                 // we cannot read the mojo config file, so don't do anything beside logging
1840                 getLog().warn("Error while reading old mojo status: " + mojoConfigFile);
1841                 return false;
1842             }
1843         }
1844 
1845         List<String> newInputFiles =
1846                 inputFiles.stream().sorted().map(File::getAbsolutePath).collect(Collectors.toList());
1847 
1848         try {
1849             Files.write(mojoConfigFile, newInputFiles);
1850         } catch (IOException e) {
1851             // we cannot write the mojo config file, so don't do anything beside logging
1852             getLog().warn("Error while writing new mojo status: " + mojoConfigFile);
1853             return false;
1854         }
1855 
1856         DeltaList<String> inputTreeChanges = new DeltaList<>(oldInputFiles, newInputFiles);
1857         if (getLog().isDebugEnabled() || showCompilationChanges) {
1858             for (String fileAdded : inputTreeChanges.getAdded()) {
1859                 getLog().info("\tInput tree files (+): " + fileAdded);
1860             }
1861             for (String fileRemoved : inputTreeChanges.getRemoved()) {
1862                 getLog().info("\tInput tree files (-): " + fileRemoved);
1863             }
1864         }
1865 
1866         return inputTreeChanges.hasChanged();
1867     }
1868 
1869     public void setTarget(String target) {
1870         this.target = target;
1871         targetOrReleaseSet = true;
1872     }
1873 
1874     public void setRelease(String release) {
1875         this.release = release;
1876         targetOrReleaseSet = true;
1877     }
1878 
1879     final String getImplicit() {
1880         return implicit;
1881     }
1882 
1883     /**
1884      * JDK-8318913 workaround: Patch module-info.class to set the java release version for java/jdk modules.
1885      *
1886      * @param compilerResult should succeed.
1887      * @param sources the list of the source files to check for the "module-info.java"
1888      *
1889      * @see <a href="https://issues.apache.org/jira/browse/MCOMPILER-542">MCOMPILER-542</a>
1890      * @see <a href="https://bugs.openjdk.org/browse/JDK-8318913">JDK-8318913</a>
1891      */
1892     private void patchJdkModuleVersion(CompilerResult compilerResult, Set<File> sources) throws MojoExecutionException {
1893         if (compilerResult.isSuccess() && getModuleDeclaration(sources).isPresent()) {
1894             Path moduleDescriptor = getOutputDirectory().toPath().resolve("module-info.class");
1895             if (Files.isRegularFile(moduleDescriptor)) {
1896                 try {
1897                     final byte[] descriptorOriginal = Files.readAllBytes(moduleDescriptor);
1898                     final byte[] descriptorMod =
1899                             ModuleInfoTransformer.transform(descriptorOriginal, getRelease(), getLog());
1900                     if (descriptorMod != null) {
1901                         Files.write(moduleDescriptor, descriptorMod);
1902                     }
1903                 } catch (IOException ex) {
1904                     throw new MojoExecutionException("Error reading or writing module-info.class", ex);
1905                 }
1906             }
1907         }
1908     }
1909 }