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