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     // Used to compare with older targets
115     static final String MODULE_INFO_TARGET = "1.9";
116 
117     // ----------------------------------------------------------------------
118     // Configurables
119     // ----------------------------------------------------------------------
120 
121     /**
122      * Indicates whether the build will continue even if there are compilation errors.
123      *
124      * @since 2.0.2
125      */
126     @Parameter(property = "maven.compiler.failOnError", defaultValue = "true")
127     private boolean failOnError = true;
128 
129     /**
130      * Indicates whether the build will continue even if there are compilation warnings.
131      *
132      * @since 3.6
133      */
134     @Parameter(property = "maven.compiler.failOnWarning", defaultValue = "false")
135     private boolean failOnWarning;
136 
137     /**
138      * Set to <code>true</code> to include debugging information in the compiled class files.
139      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-g">javac -g</a>
140      * @see #debuglevel
141      */
142     @Parameter(property = "maven.compiler.debug", defaultValue = "true")
143     private boolean debug = true;
144 
145     /**
146      * Set to <code>true</code> to generate metadata for reflection on method parameters.
147      * @since 3.6.2
148      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-parameters">javac -parameters</a>
149      */
150     @Parameter(property = "maven.compiler.parameters", defaultValue = "false")
151     private boolean parameters;
152 
153     /**
154      * Set to <code>true</code> to enable preview language features of the java compiler
155      * @since 3.10.1
156      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-enable-preview">javac --enable-preview</a>
157      */
158     @Parameter(property = "maven.compiler.enablePreview", defaultValue = "false")
159     private boolean enablePreview;
160 
161     /**
162      * Set to <code>true</code> to show messages about what the compiler is doing.
163      *
164      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-verbose">javac -verbose</a>
165      */
166     @Parameter(property = "maven.compiler.verbose", defaultValue = "false")
167     private boolean verbose;
168 
169     /**
170      * Sets whether to show source locations where deprecated APIs are used.
171      */
172     @Parameter(property = "maven.compiler.showDeprecation", defaultValue = "false")
173     private boolean showDeprecation;
174 
175     /**
176      * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods.
177      * @deprecated This property is a no-op in {@code javac}.
178      */
179     @Deprecated
180     @Parameter(property = "maven.compiler.optimize", defaultValue = "false")
181     private boolean optimize;
182 
183     /**
184      * Set to <code>false</code> to disable warnings during compilation.
185      */
186     @Parameter(property = "maven.compiler.showWarnings", defaultValue = "true")
187     private boolean showWarnings;
188 
189     /**
190      * <p>The {@code -source} argument for the Java compiler.</p>
191      *
192      * <p><b>NOTE: </b></p>
193      * <p>Since 3.8.0 the default value has changed from 1.5 to 1.6</p>
194      * <p>Since 3.9.0 the default value has changed from 1.6 to 1.7</p>
195      * <p>Since 3.11.0 the default value has changed from 1.7 to 1.8</p>
196      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-source">javac -source</a>
197      */
198     @Parameter(property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE)
199     protected String source;
200 
201     /**
202      * <p>The {@code -target} argument for the Java compiler.</p>
203      *
204      * <p><b>NOTE: </b></p>
205      * <p>Since 3.8.0 the default value has changed from 1.5 to 1.6</p>
206      * <p>Since 3.9.0 the default value has changed from 1.6 to 1.7</p>
207      * <p>Since 3.11.0 the default value has changed from 1.7 to 1.8</p>
208      *
209      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-target">javac -target</a>
210      */
211     @Parameter(property = "maven.compiler.target", defaultValue = DEFAULT_TARGET)
212     protected String target;
213 
214     /**
215      * The {@code -release} argument for the Java compiler, supported since Java9
216      *
217      * @since 3.6
218      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-release">javac -release</a>
219      */
220     @Parameter(property = "maven.compiler.release")
221     protected String release;
222 
223     /**
224      * The {@code -encoding} argument for the Java compiler.
225      *
226      * @since 2.1
227      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-encoding">javac -encoding</a>
228      */
229     @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
230     private String encoding;
231 
232     /**
233      * Sets the granularity in milliseconds of the last modification
234      * date for testing whether a source needs recompilation.
235      */
236     @Parameter(property = "lastModGranularityMs", defaultValue = "0")
237     private int staleMillis;
238 
239     /**
240      * The compiler id of the compiler to use. See this
241      * <a href="non-javac-compilers.html">guide</a> for more information.
242      */
243     @Parameter(property = "maven.compiler.compilerId", defaultValue = "javac")
244     private String compilerId;
245 
246     /**
247      * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>.
248      * @deprecated This parameter is no longer evaluated by the underlying compilers, instead the actual
249      * version of the {@code javac} binary is automatically retrieved.
250      */
251     @Deprecated
252     @Parameter(property = "maven.compiler.compilerVersion")
253     private String compilerVersion;
254 
255     /**
256      * Allows running the compiler in a separate process.
257      * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable.
258      */
259     @Parameter(property = "maven.compiler.fork", defaultValue = "false")
260     private boolean fork;
261 
262     /**
263      * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
264      * if {@link #fork} is set to <code>true</code>.
265      *
266      * @since 2.0.1
267      */
268     @Parameter(property = "maven.compiler.meminitial")
269     private String meminitial;
270 
271     /**
272      * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
273      * if {@link #fork} is set to <code>true</code>.
274      *
275      * @since 2.0.1
276      */
277     @Parameter(property = "maven.compiler.maxmem")
278     private String maxmem;
279 
280     /**
281      * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>.
282      */
283     @Parameter(property = "maven.compiler.executable")
284     private String executable;
285 
286     /**
287      * <p>
288      * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
289      * If not set, both compilation and annotation processing are performed at the same time.
290      * </p>
291      * <p>Allowed values are:</p>
292      * <ul>
293      * <li><code>none</code> - no annotation processing is performed.</li>
294      * <li><code>only</code> - only annotation processing is done, no compilation.</li>
295      * <li><code>full</code> - annotation processing and compilation.</li>
296      * </ul>
297      *
298      * <code>full</code> is the default. Starting with JDK 21, this option must be set explicitly.
299      *
300      * @since 2.2
301      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-proc">javac -proc</a>
302      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#annotation-processing">javac Annotation Processing</a>
303      */
304     @Parameter(property = "maven.compiler.proc")
305     private String proc;
306 
307     /**
308      * <p>
309      * Names of annotation processors to run. Only applies to JDK 1.6+
310      * If not set, the default annotation processors discovery process applies.
311      * </p>
312      *
313      * @since 2.2
314      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-processor">javac -processor</a>
315      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#annotation-processing">javac Annotation Processing</a>
316      */
317     @Parameter
318     private String[] annotationProcessors;
319 
320     /**
321      * <p>
322      * Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation
323      * processors only in those classpath elements. If omitted, the default classpath is used to detect annotation
324      * processors. The detection itself depends on the configuration of {@code annotationProcessors}.
325      * </p>
326      * <p>
327      * Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier,
328      * type). Transitive dependencies are added automatically. Exclusions are supported as well. Example:
329      * </p>
330      *
331      * <pre>
332      * &lt;configuration&gt;
333      *   &lt;annotationProcessorPaths&gt;
334      *     &lt;path&gt;
335      *       &lt;groupId&gt;org.sample&lt;/groupId&gt;
336      *       &lt;artifactId&gt;sample-annotation-processor&lt;/artifactId&gt;
337      *       &lt;version&gt;1.2.3&lt;/version&gt; &lt;!-- Optional - taken from dependency management if not specified --&gt;
338      *       &lt;!-- Optionally exclude transitive dependencies --&gt;
339      *       &lt;exclusions&gt;
340      *         &lt;exclusion&gt;
341      *           &lt;groupId&gt;org.sample&lt;/groupId&gt;
342      *           &lt;artifactId&gt;sample-dependency&lt;/artifactId&gt;
343      *         &lt;/exclusion&gt;
344      *       &lt;/exclusions&gt;
345      *     &lt;/path&gt;
346      *     &lt;!-- ... more ... --&gt;
347      *   &lt;/annotationProcessorPaths&gt;
348      * &lt;/configuration&gt;
349      * </pre>
350      *
351      * <b>Note:</b> Exclusions are supported from version 3.11.0.
352      *
353      * @since 3.5
354      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-processor-path">javac -processorpath</a>
355      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#annotation-processing">javac Annotation Processing</a>
356      *
357      */
358     @Parameter
359     private List<DependencyCoordinate> annotationProcessorPaths;
360 
361     /**
362      * <p>
363      * Whether to use the Maven dependency management section when resolving transitive dependencies of annotation
364      * processor paths.
365      * </p>
366      * <p>
367      * This flag does not enable / disable the ability to resolve the version of annotation processor paths
368      * from dependency management section. It only influences the resolution of transitive dependencies of those
369      * top-level paths.
370      * </p>
371      *
372      * @since 3.12.0
373      */
374     @Parameter(defaultValue = "false")
375     private boolean annotationProcessorPathsUseDepMgmt;
376 
377     /**
378      * <p>
379      * Sets the arguments to be passed to the compiler (prepending a dash).
380      * </p>
381      * <p>
382      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
383      * </p>
384      * <p>
385      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
386      * </p>
387      * <p>
388      * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following:
389      * </p>
390      *
391      * <pre>
392      * &lt;compilerArguments&gt;
393      *   &lt;Xmaxerrs&gt;1000&lt;/Xmaxerrs&gt;
394      *   &lt;Xlint/&gt;
395      *   &lt;Xlint:-path/&gt;
396      *   &lt;Averbose&gt;true&lt;/Averbose&gt;
397      * &lt;/compilerArguments&gt;
398      * </pre>
399      *
400      * @since 2.0.1
401      * @deprecated use {@link #compilerArgs} instead.
402      */
403     @Parameter
404     @Deprecated
405     protected Map<String, String> compilerArguments;
406 
407     /**
408      * <p>
409      * Sets the arguments to be passed to the compiler.
410      * </p>
411      * <p>
412      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
413      * </p>
414      * Example:
415      * <pre>
416      * &lt;compilerArgs&gt;
417      *   &lt;arg&gt;-Xmaxerrs&lt;/arg&gt;
418      *   &lt;arg&gt;1000&lt;/arg&gt;
419      *   &lt;arg&gt;-Xlint&lt;/arg&gt;
420      *   &lt;arg&gt;-J-Duser.language=en_us&lt;/arg&gt;
421      * &lt;/compilerArgs&gt;
422      * </pre>
423      *
424      * @since 3.1
425      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-J">javac -J</a>
426      */
427     @Parameter
428     protected List<String> compilerArgs;
429 
430     /**
431      * <p>
432      * Sets the unformatted single argument string to be passed to the compiler. To pass multiple arguments such as
433      * <code>-Xmaxerrs 1000</code> (which are actually two arguments) you have to use {@link #compilerArgs}.
434      * </p>
435      * <p>
436      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
437      * </p>
438      * <p>
439      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
440      * </p>
441      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-J">javac -J</a>
442      */
443     @Parameter
444     protected String compilerArgument;
445 
446     /**
447      * Sets the name of the output file when compiling a set of
448      * sources to a single file.
449      * <p/>
450      * expression="${project.build.finalName}"
451      */
452     @Parameter
453     private String outputFileName;
454 
455     /**
456      * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a
457      * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>.
458      * If debug level is not specified, by default, nothing will be appended to <code>-g</code>.
459      * If {@link #debug} is not turned on, this attribute will be ignored.
460      *
461      * @since 2.1
462      * @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>
463      */
464     @Parameter(property = "maven.compiler.debuglevel")
465     private String debuglevel;
466 
467     /**
468      * Keyword to be appended to the <code>-implicit:</code> command-line switch.
469      *
470      * @since 3.10.2
471      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-implicit">javac -implicit</a>
472      */
473     @Parameter(property = "maven.compiler.implicit")
474     private String implicit;
475 
476     /**
477      *
478      */
479     @Component
480     private ToolchainManager toolchainManager;
481 
482     /**
483      * <p>
484      * Specify the requirements for this jdk toolchain for using a different {@code javac} than the one of the JRE used
485      * by Maven. This overrules the toolchain selected by the
486      * <a href="https://maven.apache.org/plugins/maven-toolchains-plugin/">maven-toolchain-plugin</a>.
487      * </p>
488      * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html"> Guide to Toolchains</a> for more
489      * info)
490      *
491      * <pre>
492      * &lt;configuration&gt;
493      *   &lt;jdkToolchain&gt;
494      *     &lt;version&gt;11&lt;/version&gt;
495      *   &lt;/jdkToolchain&gt;
496      *   ...
497      * &lt;/configuration&gt;
498      *
499      * &lt;configuration&gt;
500      *   &lt;jdkToolchain&gt;
501      *     &lt;version&gt;1.8&lt;/version&gt;
502      *     &lt;vendor&gt;zulu&lt;/vendor&gt;
503      *   &lt;/jdkToolchain&gt;
504      *   ...
505      * &lt;/configuration&gt;
506      * </pre>
507      * <strong>note:</strong> requires at least Maven 3.3.1
508      *
509      * @since 3.6
510      */
511     @Parameter
512     private Map<String, String> jdkToolchain;
513 
514     // ----------------------------------------------------------------------
515     // Read-only parameters
516     // ----------------------------------------------------------------------
517 
518     /**
519      * The directory to run the compiler from if fork is true.
520      */
521     @Parameter(defaultValue = "${basedir}", required = true, readonly = true)
522     private File basedir;
523 
524     /**
525      * The target directory of the compiler if fork is true.
526      */
527     @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true)
528     private File buildDirectory;
529 
530     /**
531      * Plexus compiler manager.
532      */
533     @Component
534     private CompilerManager compilerManager;
535 
536     /**
537      * The current build session instance. This is used for toolchain manager API calls.
538      */
539     @Parameter(defaultValue = "${session}", readonly = true, required = true)
540     private MavenSession session;
541 
542     /**
543      * The current project instance. This is used for propagating generated-sources paths as compile/testCompile source
544      * roots.
545      */
546     @Parameter(defaultValue = "${project}", readonly = true, required = true)
547     private MavenProject project;
548 
549     /**
550      * Strategy to re use javacc class created:
551      * <ul>
552      * <li><code>reuseCreated</code> (default): will reuse already created but in case of multi-threaded builds, each
553      * thread will have its own instance</li>
554      * <li><code>reuseSame</code>: the same Javacc class will be used for each compilation even for multi-threaded build
555      * </li>
556      * <li><code>alwaysNew</code>: a new Javacc class will be created for each compilation</li>
557      * </ul>
558      * Note this parameter value depends on the os/jdk you are using, but the default value should work on most of env.
559      *
560      * @since 2.5
561      */
562     @Parameter(defaultValue = "${reuseCreated}", property = "maven.compiler.compilerReuseStrategy")
563     private String compilerReuseStrategy = "reuseCreated";
564 
565     /**
566      * @since 2.5
567      */
568     @Parameter(defaultValue = "false", property = "maven.compiler.skipMultiThreadWarning")
569     private boolean skipMultiThreadWarning;
570 
571     /**
572      * Legacy parameter name of {@link #forceLegacyJavacApi}. Only considered if {@link #forceLegacyJavacApi} is
573      * not set or {@code false}.
574      * @since 3.0
575      * @deprecated Use {@link #forceLegacyJavacApi} instead
576      */
577     @Deprecated
578     @Parameter(defaultValue = "false", property = "maven.compiler.forceJavacCompilerUse")
579     private boolean forceJavacCompilerUse;
580 
581     /**
582      * 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>
583      * if available in your current JDK.
584      * 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">
585      * {@code com.sun.tools.javac} API</a> instead.
586      * <p>
587      * <em>This only has an effect for {@link #compilerId} being {@code javac} and {@link #fork} being {@code false}</em>.
588      *
589      * @since 3.13
590      */
591     @Parameter(defaultValue = "false", property = "maven.compiler.forceLegacyJavacApi")
592     private boolean forceLegacyJavacApi;
593 
594     /**
595      * @since 3.0 needed for storing the status for the incremental build support.
596      */
597     @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true)
598     private MojoExecution mojoExecution;
599 
600     /**
601      * File extensions to check timestamp for incremental build.
602      *
603      * @since 3.1
604      */
605     @Parameter(defaultValue = "class,jar")
606     private Set<String> fileExtensions;
607 
608     /**
609      * <p>to enable/disable incremental compilation feature.</p>
610      * <p>This leads to two different modes depending on the underlying compiler. The default javac compiler does the
611      * following:</p>
612      * <ul>
613      * <li>true <strong>(default)</strong> in this mode the compiler plugin determines whether any JAR files the
614      * current module depends on have changed in the current build run; or any source file was added, removed or
615      * changed since the last compilation. If this is the case, the compiler plugin recompiles all sources.</li>
616      * <li>false <strong>(not recommended)</strong> this only compiles source files which are newer than their
617      * corresponding class files, namely which have changed since the last compilation. This does not
618      * recompile other classes which use the changed class, potentially leaving them with references to methods that no
619      * longer exist, leading to errors at runtime.</li>
620      * </ul>
621      *
622      * @since 3.1
623      */
624     @Parameter(defaultValue = "true", property = "maven.compiler.useIncrementalCompilation")
625     private boolean useIncrementalCompilation = true;
626 
627     /**
628      * Package info source files that only contain javadoc and no annotation on the package
629      * can lead to no class file being generated by the compiler.  This causes a file miss
630      * on the next compilations and forces an unnecessary recompilation. The default value
631      * of <code>true</code> causes an empty class file to be generated.  This behavior can
632      * be changed by setting this parameter to <code>false</code>.
633      *
634      * @since 3.10
635      */
636     @Parameter(defaultValue = "true", property = "maven.compiler.createMissingPackageInfoClass")
637     private boolean createMissingPackageInfoClass = true;
638 
639     @Parameter(defaultValue = "false", property = "maven.compiler.showCompilationChanges")
640     private boolean showCompilationChanges = false;
641 
642     /**
643      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
644      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
645      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
646      * @since 3.12.0
647      */
648     @Parameter(defaultValue = "${project.build.outputTimestamp}")
649     private String outputTimestamp;
650 
651     /**
652      * Resolves the artifacts needed.
653      */
654     @Component
655     private RepositorySystem repositorySystem;
656 
657     /**
658      * Artifact handler manager.
659      */
660     @Component
661     private ArtifactHandlerManager artifactHandlerManager;
662 
663     protected abstract SourceInclusionScanner getSourceInclusionScanner(int staleMillis);
664 
665     protected abstract SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding);
666 
667     protected abstract List<String> getClasspathElements();
668 
669     protected abstract List<String> getModulepathElements();
670 
671     protected abstract Map<String, JavaModuleDescriptor> getPathElements();
672 
673     protected abstract List<String> getCompileSourceRoots();
674 
675     protected abstract void preparePaths(Set<File> sourceFiles);
676 
677     protected abstract File getOutputDirectory();
678 
679     protected abstract String getSource();
680 
681     protected abstract String getTarget();
682 
683     protected abstract String getRelease();
684 
685     protected abstract String getCompilerArgument();
686 
687     protected abstract Map<String, String> getCompilerArguments();
688 
689     protected abstract File getGeneratedSourcesDirectory();
690 
691     protected abstract String getDebugFileName();
692 
693     protected final MavenProject getProject() {
694         return project;
695     }
696 
697     protected final Optional<Path> getModuleDeclaration(final Set<File> sourceFiles) {
698         for (File sourceFile : sourceFiles) {
699             if ("module-info.java".equals(sourceFile.getName())) {
700                 return Optional.of(sourceFile.toPath());
701             }
702         }
703         return Optional.empty();
704     }
705 
706     private boolean targetOrReleaseSet;
707 
708     @Override
709     public void execute() throws MojoExecutionException, CompilationFailureException {
710         // ----------------------------------------------------------------------
711         // Look up the compiler. This is done before other code than can
712         // cause the mojo to return before the lookup is done possibly resulting
713         // in misconfigured POMs still building.
714         // ----------------------------------------------------------------------
715 
716         Compiler compiler;
717 
718         getLog().debug("Using compiler '" + compilerId + "'.");
719 
720         try {
721             compiler = compilerManager.getCompiler(compilerId);
722         } catch (NoSuchCompilerException e) {
723             throw new MojoExecutionException("No such compiler '" + e.getCompilerId() + "'.", e);
724         }
725 
726         // -----------toolchains start here ----------------------------------
727         // use the compilerId as identifier for toolchains as well.
728         Toolchain tc = getToolchain();
729         if (tc != null) {
730             getLog().info("Toolchain in maven-compiler-plugin: " + tc);
731             if (executable != null) {
732                 getLog().warn("Toolchains are ignored, 'executable' parameter is set to " + executable);
733             } else {
734                 fork = true;
735                 // TODO somehow shaky dependency between compilerId and tool executable.
736                 executable = tc.findTool(compilerId);
737             }
738         }
739         // ----------------------------------------------------------------------
740         //
741         // ----------------------------------------------------------------------
742 
743         List<String> compileSourceRoots = removeEmptyCompileSourceRoots(getCompileSourceRoots());
744 
745         if (compileSourceRoots.isEmpty()) {
746             getLog().info("No sources to compile");
747 
748             return;
749         }
750 
751         // Verify that target or release is set
752         if (!targetOrReleaseSet) {
753             MessageBuilder mb = MessageUtils.buffer()
754                     .a("No explicit value set for target or release! ")
755                     .a("To ensure the same result even after upgrading this plugin, please add ")
756                     .newline()
757                     .newline();
758 
759             writePlugin(mb);
760 
761             getLog().warn(mb.toString());
762         }
763 
764         // ----------------------------------------------------------------------
765         // Create the compiler configuration
766         // ----------------------------------------------------------------------
767 
768         CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
769 
770         compilerConfiguration.setOutputLocation(getOutputDirectory().getAbsolutePath());
771 
772         compilerConfiguration.setOptimize(optimize);
773 
774         compilerConfiguration.setDebug(debug);
775 
776         compilerConfiguration.setDebugFileName(getDebugFileName());
777 
778         compilerConfiguration.setImplicitOption(implicit);
779 
780         if (debug && (debuglevel != null && !debuglevel.isEmpty())) {
781             String[] split = StringUtils.split(debuglevel, ",");
782             for (String aSplit : split) {
783                 if (!(aSplit.equalsIgnoreCase("none")
784                         || aSplit.equalsIgnoreCase("lines")
785                         || aSplit.equalsIgnoreCase("vars")
786                         || aSplit.equalsIgnoreCase("source"))) {
787                     throw new IllegalArgumentException("The specified debug level: '" + aSplit + "' is unsupported. "
788                             + "Legal values are 'none', 'lines', 'vars', and 'source'.");
789                 }
790             }
791             compilerConfiguration.setDebugLevel(debuglevel);
792         }
793 
794         compilerConfiguration.setParameters(parameters);
795 
796         compilerConfiguration.setEnablePreview(enablePreview);
797 
798         compilerConfiguration.setVerbose(verbose);
799 
800         compilerConfiguration.setShowWarnings(showWarnings);
801 
802         compilerConfiguration.setFailOnWarning(failOnWarning);
803 
804         if (failOnWarning && !showWarnings) {
805             getLog().warn("The property failOnWarning is set to true, but showWarnings is set to false.");
806             getLog().warn("With compiler's warnings silenced the failOnWarning has no effect.");
807         }
808 
809         compilerConfiguration.setShowDeprecation(showDeprecation);
810 
811         compilerConfiguration.setSourceVersion(getSource());
812 
813         compilerConfiguration.setTargetVersion(getTarget());
814 
815         compilerConfiguration.setReleaseVersion(getRelease());
816 
817         compilerConfiguration.setProc(proc);
818 
819         File generatedSourcesDirectory = getGeneratedSourcesDirectory();
820         compilerConfiguration.setGeneratedSourcesDirectory(
821                 generatedSourcesDirectory != null ? generatedSourcesDirectory.getAbsoluteFile() : null);
822 
823         if (generatedSourcesDirectory != null) {
824             if (!generatedSourcesDirectory.exists()) {
825                 generatedSourcesDirectory.mkdirs();
826             }
827 
828             String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath();
829 
830             compileSourceRoots.add(generatedSourcesPath);
831 
832             if (isTestCompile()) {
833                 getLog().debug("Adding " + generatedSourcesPath + " to test-compile source roots:\n  "
834                         + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n  "));
835 
836                 project.addTestCompileSourceRoot(generatedSourcesPath);
837 
838                 getLog().debug("New test-compile source roots:\n  "
839                         + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n  "));
840             } else {
841                 getLog().debug("Adding " + generatedSourcesPath + " to compile source roots:\n  "
842                         + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n  "));
843 
844                 project.addCompileSourceRoot(generatedSourcesPath);
845 
846                 getLog().debug("New compile source roots:\n  "
847                         + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n  "));
848             }
849         }
850 
851         compilerConfiguration.setSourceLocations(compileSourceRoots);
852 
853         compilerConfiguration.setAnnotationProcessors(annotationProcessors);
854 
855         compilerConfiguration.setProcessorPathEntries(resolveProcessorPathEntries());
856 
857         compilerConfiguration.setSourceEncoding(encoding);
858 
859         compilerConfiguration.setFork(fork);
860 
861         if (fork) {
862             if (!(meminitial == null || meminitial.isEmpty())) {
863                 String value = getMemoryValue(meminitial);
864 
865                 if (value != null) {
866                     compilerConfiguration.setMeminitial(value);
867                 } else {
868                     getLog().info("Invalid value for meminitial '" + meminitial + "'. Ignoring this option.");
869                 }
870             }
871 
872             if (!(maxmem == null || maxmem.isEmpty())) {
873                 String value = getMemoryValue(maxmem);
874 
875                 if (value != null) {
876                     compilerConfiguration.setMaxmem(value);
877                 } else {
878                     getLog().info("Invalid value for maxmem '" + maxmem + "'. Ignoring this option.");
879                 }
880             }
881         }
882 
883         compilerConfiguration.setExecutable(executable);
884 
885         compilerConfiguration.setWorkingDirectory(basedir);
886 
887         compilerConfiguration.setCompilerVersion(compilerVersion);
888 
889         compilerConfiguration.setBuildDirectory(buildDirectory);
890 
891         compilerConfiguration.setOutputFileName(outputFileName);
892 
893         if (CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals(this.compilerReuseStrategy)) {
894             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.AlwaysNew);
895         } else if (CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy()
896                 .equals(this.compilerReuseStrategy)) {
897             if (getRequestThreadCount() > 1) {
898                 if (!skipMultiThreadWarning) {
899                     getLog().warn("You are in a multi-thread build and compilerReuseStrategy is set to reuseSame."
900                             + " This can cause issues in some environments (os/jdk)!"
901                             + " Consider using reuseCreated strategy."
902                             + System.getProperty("line.separator")
903                             + "If your env is fine with reuseSame, you can skip this warning with the "
904                             + "configuration field skipMultiThreadWarning "
905                             + "or -Dmaven.compiler.skipMultiThreadWarning=true");
906                 }
907             }
908             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseSame);
909         } else {
910 
911             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseCreated);
912         }
913 
914         getLog().debug("CompilerReuseStrategy: "
915                 + compilerConfiguration.getCompilerReuseStrategy().getStrategy());
916 
917         compilerConfiguration.setForceJavacCompilerUse(forceLegacyJavacApi || forceJavacCompilerUse);
918 
919         boolean canUpdateTarget;
920 
921         IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper(mojoExecution, session);
922 
923         final Set<File> sources;
924 
925         IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
926 
927         if (useIncrementalCompilation) {
928             getLog().debug("useIncrementalCompilation enabled");
929             try {
930                 canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration);
931 
932                 sources = getCompileSources(compiler, compilerConfiguration);
933 
934                 preparePaths(sources);
935 
936                 incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles(sources);
937 
938                 // Strategies used to detect modifications.
939                 String immutableOutputFile = (compiler.getCompilerOutputStyle()
940                                         .equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES)
941                                 && !canUpdateTarget)
942                         ? "immutable single output file"
943                         : null;
944                 String dependencyChanged = isDependencyChanged() ? "changed dependency" : null;
945                 String sourceChanged = isSourceChanged(compilerConfiguration, compiler) ? "changed source code" : null;
946                 String inputFileTreeChanged = hasInputFileTreeChanged(incrementalBuildHelper, sources)
947                         ? "added or removed source files"
948                         : null;
949 
950                 // Get the first cause for the rebuild compilation detection.
951                 String cause = Stream.of(immutableOutputFile, dependencyChanged, sourceChanged, inputFileTreeChanged)
952                         .filter(Objects::nonNull)
953                         .findFirst()
954                         .orElse(null);
955 
956                 if (cause != null) {
957                     getLog().info("Recompiling the module because of "
958                             + MessageUtils.buffer().strong(cause) + ".");
959                     compilerConfiguration.setSourceFiles(sources);
960                 } else {
961                     getLog().info("Nothing to compile - all classes are up to date.");
962                     return;
963                 }
964             } catch (CompilerException e) {
965                 throw new MojoExecutionException("Error while computing stale sources.", e);
966             }
967         } else {
968             getLog().debug("useIncrementalCompilation disabled");
969 
970             Set<File> staleSources;
971             try {
972                 staleSources =
973                         computeStaleSources(compilerConfiguration, compiler, getSourceInclusionScanner(staleMillis));
974 
975                 canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration);
976 
977                 if (compiler.getCompilerOutputStyle().equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES)
978                         && !canUpdateTarget) {
979                     getLog().info("RESCANNING!");
980                     // TODO: This second scan for source files is sub-optimal
981                     String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration);
982 
983                     staleSources = computeStaleSources(
984                             compilerConfiguration, compiler, getSourceInclusionScanner(inputFileEnding));
985                 }
986 
987             } catch (CompilerException e) {
988                 throw new MojoExecutionException("Error while computing stale sources.", e);
989             }
990 
991             if (staleSources.isEmpty()) {
992                 getLog().info("Nothing to compile - all classes are up to date.");
993                 return;
994             }
995 
996             compilerConfiguration.setSourceFiles(staleSources);
997 
998             try {
999                 // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath
1000                 sources = getCompileSources(compiler, compilerConfiguration);
1001 
1002                 if (getLog().isDebugEnabled()) {
1003                     getLog().debug("#sources: " + sources.size());
1004                     for (File file : sources) {
1005                         getLog().debug(file.getPath());
1006                     }
1007                 }
1008 
1009                 preparePaths(sources);
1010             } catch (CompilerException e) {
1011                 throw new MojoExecutionException("Error while computing stale sources.", e);
1012             }
1013         }
1014 
1015         // Dividing pathElements of classPath and modulePath is based on sourceFiles
1016         compilerConfiguration.setClasspathEntries(getClasspathElements());
1017 
1018         compilerConfiguration.setModulepathEntries(getModulepathElements());
1019 
1020         compilerConfiguration.setIncludes(getIncludes());
1021 
1022         compilerConfiguration.setExcludes(getExcludes());
1023 
1024         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
1025 
1026         String effectiveCompilerArgument = getCompilerArgument();
1027 
1028         if ((effectiveCompilerArguments != null) || (effectiveCompilerArgument != null) || (compilerArgs != null)) {
1029             if (effectiveCompilerArguments != null) {
1030                 for (Map.Entry<String, String> me : effectiveCompilerArguments.entrySet()) {
1031                     String key = me.getKey();
1032                     String value = me.getValue();
1033                     if (!key.startsWith("-")) {
1034                         key = "-" + key;
1035                     }
1036 
1037                     if (key.startsWith("-A") && (value != null && !value.isEmpty())) {
1038                         compilerConfiguration.addCompilerCustomArgument(key + "=" + value, null);
1039                     } else {
1040                         compilerConfiguration.addCompilerCustomArgument(key, value);
1041                     }
1042                 }
1043             }
1044             if (!(effectiveCompilerArgument == null || effectiveCompilerArgument.isEmpty())) {
1045                 compilerConfiguration.addCompilerCustomArgument(effectiveCompilerArgument, null);
1046             }
1047             if (compilerArgs != null) {
1048                 for (String arg : compilerArgs) {
1049                     compilerConfiguration.addCompilerCustomArgument(arg, null);
1050                 }
1051             }
1052         }
1053 
1054         // ----------------------------------------------------------------------
1055         // Dump configuration
1056         // ----------------------------------------------------------------------
1057         if (getLog().isDebugEnabled()) {
1058             getLog().debug("Classpath:");
1059 
1060             for (String s : getClasspathElements()) {
1061                 getLog().debug(" " + s);
1062             }
1063 
1064             if (!getModulepathElements().isEmpty()) {
1065                 getLog().debug("Modulepath:");
1066                 for (String s : getModulepathElements()) {
1067                     getLog().debug(" " + s);
1068                 }
1069             }
1070 
1071             getLog().debug("Source roots:");
1072 
1073             for (String root : getCompileSourceRoots()) {
1074                 getLog().debug(" " + root);
1075             }
1076 
1077             try {
1078                 if (fork) {
1079                     if (compilerConfiguration.getExecutable() != null) {
1080                         getLog().debug("Executable: ");
1081                         getLog().debug(" " + compilerConfiguration.getExecutable());
1082                     }
1083                 }
1084 
1085                 String[] cl = compiler.createCommandLine(compilerConfiguration);
1086                 if (cl != null && cl.length > 0) {
1087                     StringBuilder sb = new StringBuilder();
1088                     sb.append(cl[0]);
1089                     for (int i = 1; i < cl.length; i++) {
1090                         sb.append(" ");
1091                         sb.append(cl[i]);
1092                     }
1093                     getLog().debug("Command line options:");
1094                     getLog().debug(sb);
1095                 }
1096             } catch (CompilerException ce) {
1097                 getLog().debug(ce);
1098             }
1099         }
1100 
1101         List<String> jpmsLines = new ArrayList<>();
1102 
1103         // See http://openjdk.java.net/jeps/261
1104         final List<String> runtimeArgs = Arrays.asList(
1105                 "--upgrade-module-path", "--add-exports", "--add-reads", "--add-modules", "--limit-modules");
1106 
1107         // Custom arguments are all added as keys to an ordered Map
1108         Iterator<Map.Entry<String, String>> entryIter =
1109                 compilerConfiguration.getCustomCompilerArgumentsEntries().iterator();
1110         while (entryIter.hasNext()) {
1111             Map.Entry<String, String> entry = entryIter.next();
1112 
1113             if (runtimeArgs.contains(entry.getKey())) {
1114                 jpmsLines.add(entry.getKey());
1115 
1116                 String value = entry.getValue();
1117                 if (value == null) {
1118                     entry = entryIter.next();
1119                     value = entry.getKey();
1120                 }
1121                 jpmsLines.add(value);
1122             } else if ("--patch-module".equals(entry.getKey())) {
1123                 String value = entry.getValue();
1124                 if (value == null) {
1125                     entry = entryIter.next();
1126                     value = entry.getKey();
1127                 }
1128 
1129                 String[] values = value.split("=");
1130 
1131                 StringBuilder patchModule = new StringBuilder(values[0]);
1132                 patchModule.append('=');
1133 
1134                 Set<String> patchModules = new LinkedHashSet<>();
1135                 Set<Path> sourceRoots = new HashSet<>(getCompileSourceRoots().size());
1136                 for (String sourceRoot : getCompileSourceRoots()) {
1137                     sourceRoots.add(Paths.get(sourceRoot));
1138                 }
1139 
1140                 String[] files = values[1].split(PS);
1141 
1142                 for (String file : files) {
1143                     Path filePath = Paths.get(file);
1144                     if (getOutputDirectory().toPath().equals(filePath)) {
1145                         patchModules.add("_"); // this jar
1146                     } else if (getOutputDirectory().toPath().startsWith(filePath)) {
1147                         // multirelease, can be ignored
1148                         continue;
1149                     } else if (sourceRoots.contains(filePath)) {
1150                         patchModules.add("_"); // this jar
1151                     } else {
1152                         JavaModuleDescriptor descriptor = getPathElements().get(file);
1153 
1154                         if (descriptor == null) {
1155                             if (Files.isDirectory(filePath)) {
1156                                 patchModules.add(file);
1157                             } else {
1158                                 getLog().warn("Can't locate " + file);
1159                             }
1160                         } else if (!values[0].equals(descriptor.name())) {
1161                             patchModules.add(descriptor.name());
1162                         }
1163                     }
1164                 }
1165 
1166                 StringBuilder sb = new StringBuilder();
1167 
1168                 if (!patchModules.isEmpty()) {
1169                     for (String mod : patchModules) {
1170                         if (sb.length() > 0) {
1171                             sb.append(", ");
1172                         }
1173                         // use 'invalid' separator to ensure values are transformed
1174                         sb.append(mod);
1175                     }
1176 
1177                     jpmsLines.add("--patch-module");
1178                     jpmsLines.add(patchModule + sb.toString());
1179                 }
1180             }
1181         }
1182 
1183         if (!jpmsLines.isEmpty()) {
1184             Path jpmsArgs = Paths.get(getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args");
1185             try {
1186                 Files.createDirectories(jpmsArgs.getParent());
1187 
1188                 Files.write(jpmsArgs, jpmsLines, Charset.defaultCharset());
1189             } catch (IOException e) {
1190                 getLog().warn(e.getMessage());
1191             }
1192         }
1193 
1194         // ----------------------------------------------------------------------
1195         // Compile!
1196         // ----------------------------------------------------------------------
1197 
1198         if (StringUtils.isEmpty(compilerConfiguration.getSourceEncoding())) {
1199             getLog().warn("File encoding has not been set, using platform encoding "
1200                     + MessageUtils.buffer().strong(Charset.defaultCharset())
1201                     + ", i.e. build is platform dependent!");
1202         }
1203 
1204         CompilerResult compilerResult;
1205 
1206         if (useIncrementalCompilation) {
1207             incrementalBuildHelperRequest.outputDirectory(getOutputDirectory());
1208 
1209             // MCOMPILER-333: Cleanup the generated source files created by annotation processing
1210             // to avoid issues with `javac` compiler when the source code is rebuild.
1211             if (getGeneratedSourcesDirectory() != null) {
1212                 try (Stream<Path> walk =
1213                         Files.walk(getGeneratedSourcesDirectory().toPath())) {
1214                     walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
1215                     // MCOMPILER-567: The directory must already exist because javac does not create it.
1216                     Files.createDirectories(getGeneratedSourcesDirectory().toPath());
1217                 } catch (IOException ex) {
1218                     getLog().warn("I/O error deleting the annotation processing generated files: " + ex.getMessage());
1219                 }
1220             }
1221 
1222             incrementalBuildHelper.beforeRebuildExecution(incrementalBuildHelperRequest);
1223 
1224             getLog().debug("incrementalBuildHelper#beforeRebuildExecution");
1225         }
1226 
1227         try {
1228             compilerResult = compiler.performCompile(compilerConfiguration);
1229         } catch (Exception e) {
1230             // TODO: don't catch Exception
1231             throw new MojoExecutionException("Fatal error compiling", e);
1232         }
1233 
1234         if (createMissingPackageInfoClass
1235                 && compilerResult.isSuccess()
1236                 && compiler.getCompilerOutputStyle() == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) {
1237             try {
1238                 SourceMapping sourceMapping = getSourceMapping(compilerConfiguration, compiler);
1239                 createMissingPackageInfoClasses(compilerConfiguration, sourceMapping, sources);
1240             } catch (Exception e) {
1241                 getLog().warn("Error creating missing package info classes", e);
1242             }
1243         }
1244 
1245         if (outputTimestamp != null && (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 }