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 javax.lang.model.SourceVersion;
22  import javax.tools.DiagnosticListener;
23  import javax.tools.JavaCompiler;
24  import javax.tools.JavaFileManager;
25  import javax.tools.JavaFileObject;
26  import javax.tools.OptionChecker;
27  import javax.tools.ToolProvider;
28  
29  import java.io.BufferedReader;
30  import java.io.BufferedWriter;
31  import java.io.File;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.StreamTokenizer;
35  import java.io.StringWriter;
36  import java.io.UncheckedIOException;
37  import java.nio.charset.Charset;
38  import java.nio.charset.UnsupportedCharsetException;
39  import java.nio.file.Files;
40  import java.nio.file.Path;
41  import java.util.Arrays;
42  import java.util.Collection;
43  import java.util.EnumSet;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Optional;
47  import java.util.ServiceLoader;
48  import java.util.Set;
49  import java.util.StringJoiner;
50  import java.util.jar.Attributes;
51  import java.util.jar.JarFile;
52  import java.util.jar.Manifest;
53  import java.util.stream.Stream;
54  
55  import org.apache.maven.api.JavaPathType;
56  import org.apache.maven.api.Language;
57  import org.apache.maven.api.PathScope;
58  import org.apache.maven.api.PathType;
59  import org.apache.maven.api.Project;
60  import org.apache.maven.api.ProjectScope;
61  import org.apache.maven.api.Session;
62  import org.apache.maven.api.SourceRoot;
63  import org.apache.maven.api.Toolchain;
64  import org.apache.maven.api.Type;
65  import org.apache.maven.api.annotations.Nonnull;
66  import org.apache.maven.api.annotations.Nullable;
67  import org.apache.maven.api.di.Inject;
68  import org.apache.maven.api.plugin.Log;
69  import org.apache.maven.api.plugin.Mojo;
70  import org.apache.maven.api.plugin.MojoException;
71  import org.apache.maven.api.plugin.annotations.Parameter;
72  import org.apache.maven.api.services.ArtifactManager;
73  import org.apache.maven.api.services.DependencyResolver;
74  import org.apache.maven.api.services.DependencyResolverRequest;
75  import org.apache.maven.api.services.DependencyResolverResult;
76  import org.apache.maven.api.services.MavenException;
77  import org.apache.maven.api.services.MessageBuilder;
78  import org.apache.maven.api.services.MessageBuilderFactory;
79  import org.apache.maven.api.services.ProjectManager;
80  import org.apache.maven.api.services.ToolchainManager;
81  
82  import static org.apache.maven.plugin.compiler.SourceDirectory.CLASS_FILE_SUFFIX;
83  import static org.apache.maven.plugin.compiler.SourceDirectory.MODULE_INFO;
84  
85  /**
86   * Base class of Mojos compiling Java source code.
87   * This plugin uses the {@link JavaCompiler} interface from JDK 6+.
88   * Each instance shall be used only once, then discarded.
89   *
90   * <h2>Thread-safety</h2>
91   * This class is not thread-safe. If this class is used in a multi-thread context,
92   * users are responsible for synchronizing all accesses to this <abbr>MOJO</abbr> instance.
93   * However, the executor returned by {@link #createExecutor(DiagnosticListener)} can safely
94   * launch the compilation in a background thread.
95   *
96   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a>
97   * @author Martin Desruisseaux
98   * @since 2.0
99   */
100 public abstract class AbstractCompilerMojo implements Mojo {
101     /**
102      * Whether to support legacy (and often deprecated) behavior.
103      * This is currently hard-coded to {@code true} for compatibility reason.
104      * TODO: consider making configurable.
105      */
106     static final boolean SUPPORT_LEGACY = true;
107 
108     /**
109      * Name of a {@link SourceVersion} enumeration value for a version above 17 (the current Maven target).
110      * The {@code SourceVersion} value cannot be referenced directly because it does not exist in Java 17.
111      * Used for detecting if {@code module-info.class} needs to be patched for reproducible builds.
112      */
113     private static final String RELEASE_22 = "RELEASE_22";
114 
115     /**
116      * Name of a {@link SourceVersion} enumeration value for a version above 17 (the current Maven target).
117      * The {@code SourceVersion} value cannot be referenced directly because it does not exist in Java 17.
118      * Used for determining the default value of the {@code -proc} compiler option.
119      */
120     private static final String RELEASE_23 = "RELEASE_23";
121 
122     /**
123      * The executable to use by default if nine is specified.
124      */
125     private static final String DEFAULT_EXECUTABLE = "javac";
126 
127     /**
128      * The quote character for filenames in shell scripts.
129      * Shall not be used with {@link javax.tools.JavaFileManager}.
130      */
131     static final char QUOTE = '"';
132 
133     // ----------------------------------------------------------------------
134     // Configurables
135     // ----------------------------------------------------------------------
136 
137     /**
138      * The {@code --module-version} argument for the Java compiler.
139      * This is ignored if not applicable, e.g., in non-modular projects.
140      *
141      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-module-version">javac --module-version</a>
142      * @since 4.0.0
143      */
144     @Parameter(property = "maven.compiler.moduleVersion", defaultValue = "${project.version}")
145     protected String moduleVersion;
146 
147     /**
148      * The {@code -encoding} argument for the Java compiler.
149      *
150      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-encoding">javac -encoding</a>
151      * @since 2.1
152      */
153     @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
154     protected String encoding;
155 
156     /**
157      * {@return the character set used for decoding bytes, or null for the platform default}
158      * No warning is emitted in the latter case because as of Java 18, the default is UTF-8,
159      * i.e. the encoding is no longer platform-dependent.
160      */
161     final Charset charset() {
162         if (encoding != null) {
163             try {
164                 return Charset.forName(encoding);
165             } catch (UnsupportedCharsetException e) {
166                 throw new CompilationFailureException("Invalid 'encoding' option: " + encoding, e);
167             }
168         }
169         return null;
170     }
171 
172     /**
173      * The {@code --source} argument for the Java compiler.
174      * <p><b>Notes:</b></p>
175      * <ul>
176      *   <li>Since 3.8.0 the default value has changed from 1.5 to 1.6.</li>
177      *   <li>Since 3.9.0 the default value has changed from 1.6 to 1.7.</li>
178      *   <li>Since 3.11.0 the default value has changed from 1.7 to 1.8.</li>
179      *   <li>Since 4.0.0-beta-2 the default value has been removed.
180      *       As of Java 9, the {@link #release} parameter is preferred.</li>
181      * </ul>
182      *
183      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-source">javac --source</a>
184      */
185     @Parameter(property = "maven.compiler.source")
186     protected String source;
187 
188     /**
189      * The {@code --target} argument for the Java compiler.
190      * <p><b>Notes:</b></p>
191      * <ul>
192      *   <li>Since 3.8.0 the default value has changed from 1.5 to 1.6.</li>
193      *   <li>Since 3.9.0 the default value has changed from 1.6 to 1.7.</li>
194      *   <li>Since 3.11.0 the default value has changed from 1.7 to 1.8.</li>
195      *   <li>Since 4.0.0-beta-2 the default value has been removed.
196      *       As of Java 9, the {@link #release} parameter is preferred.</li>
197      * </ul>
198      *
199      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-target">javac --target</a>
200      */
201     @Parameter(property = "maven.compiler.target")
202     protected String target;
203 
204     /**
205      * The {@code --release} argument for the Java compiler when the sources do not declare this version.
206      * The suggested way to declare the target Java release is to specify it with the sources like below:
207      *
208      * <pre>{@code
209      * <build>
210      *   <sources>
211      *     <source>
212      *       <directory>src/main/java</directory>
213      *       <targetVersion>17</targetVersion>
214      *     </source>
215      *   </sources>
216      * </build>}</pre>
217      *
218      * If such {@code <targetVersion>} element is found, it has precedence over this {@code release} property.
219      * If a source does not declare a target Java version, then the value of this {@code release} property is
220      * used as a fallback.
221      * If omitted, the compiler will generate bytecodes for the Java version running the compiler.
222      *
223      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-release">javac --release</a>
224      * @since 3.6
225      */
226     @Parameter(property = "maven.compiler.release")
227     protected String release;
228 
229     /**
230      * Whether {@link #target} or {@link #release} has a non-blank value.
231      * Used for logging a warning if no target Java version was specified.
232      */
233     private boolean targetOrReleaseSet;
234 
235     /**
236      * The highest version supported by the compiler, or {@code null} if not yet determined.
237      *
238      * @see #isVersionEqualOrNewer(String)
239      */
240     private SourceVersion supportedVersion;
241 
242     /**
243      * Whether to enable preview language features of the java compiler.
244      * If {@code true}, then the {@code --enable-preview} option will be added to compiler arguments.
245      *
246      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-enable-preview">javac --enable-preview</a>
247      * @since 3.10.1
248      */
249     @Parameter(property = "maven.compiler.enablePreview", defaultValue = "false")
250     protected boolean enablePreview;
251 
252     /**
253      * The root directories containing the source files to be compiled. If {@code null} or empty,
254      * the directories will be obtained from the {@code <Source>} elements declared in the project.
255      * If non-empty, the project {@code <Source>} elements are ignored. This configuration option
256      * should be used only when there is a need to override the project configuration.
257      *
258      * @deprecated Replaced by the project-wide {@code <sources>} element.
259      */
260     @Parameter
261     @Deprecated(since = "4.0.0")
262     protected List<String> compileSourceRoots;
263 
264     /**
265      * Additional arguments to be passed verbatim to the Java compiler. This parameter can be used when
266      * the Maven compiler plugin does not provide a parameter for a Java compiler option. It may happen,
267      * for example, for new or preview Java features which are not yet handled by this compiler plugin.
268      *
269      * <p>If an option has a value, the option and the value shall be specified in two separated {@code <arg>}
270      * elements. For example, the {@code -Xmaxerrs 1000} option (for setting the maximal number of errors to
271      * 1000) can be specified as below (together with other options):</p>
272      *
273      * <pre>{@code
274      * <compilerArgs>
275      *   <arg>-Xlint</arg>
276      *   <arg>-Xmaxerrs</arg>
277      *   <arg>1000</arg>
278      *   <arg>J-Duser.language=en_us</arg>
279      * </compilerArgs>}</pre>
280      *
281      * Note that {@code -J} options should be specified only if {@link #fork} is set to {@code true}.
282      * Other options can be specified regardless the {@link #fork} value.
283      * The compiler plugin does not verify whether the arguments given through this parameter are valid.
284      * For this reason, the other parameters provided by the compiler plugin should be preferred when
285      * they exist, because the plugin checks whether the corresponding options are supported.
286      *
287      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-J">javac -J</a>
288      * @since 3.1
289      */
290     @Parameter
291     protected List<String> compilerArgs;
292 
293     /**
294      * The single argument string to be passed to the compiler. To pass multiple arguments such as
295      * {@code -Xmaxerrs 1000} (which are actually two arguments), {@link #compilerArgs} is preferred.
296      *
297      * <p>Note that {@code -J} options should be specified only if {@link #fork} is set to {@code true}.</p>
298      *
299      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-J">javac -J</a>
300      *
301      * @deprecated Use {@link #compilerArgs} instead.
302      */
303     @Parameter
304     @Deprecated(since = "4.0.0")
305     protected String compilerArgument;
306 
307     /**
308      * Configures if annotation processing and/or compilation are performed by the compiler.
309      * If set, the value will be appended to the {@code -proc:} compiler option.
310      *
311      * Possible values are:
312      * <ul>
313      *   <li>{@code none} – no annotation processing is performed, only compilation is done.</li>
314      *   <li>{@code only} – only annotation processing is done, no compilation.</li>
315      *   <li>{@code full} – annotation processing followed by compilation is done.</li>
316      * </ul>
317      *
318      * The default value depends on the JDK used for the build.
319      * Prior to Java 23, the default was {@code full},
320      * so annotation processing and compilation were executed without explicit configuration.
321      *
322      * For security reasons, starting with Java 23 no annotation processing is done if neither
323      * any {@code -processor}, {@code -processor path} or {@code -processor module} are set,
324      * or either {@code only} or {@code full} is set.
325      * So literally the default is {@code none}.
326      * It is recommended to always list the annotation processors you want to execute
327      * instead of using the {@code proc} configuration,
328      * to ensure that only desired processors are executed and not any "hidden" (and maybe malicious).
329      *
330      * @see #annotationProcessors
331      * @see <a href="https://inside.java/2024/06/18/quality-heads-up/">Inside Java 2024-06-18 Quality Heads up</a>
332      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-proc">javac -proc</a>
333      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#annotation-processing">javac Annotation Processing</a>
334      * @since 2.2
335      */
336     @Parameter(property = "maven.compiler.proc")
337     protected String proc;
338     // Reminder: if above list of legal values is modified, update also addComaSeparated("-proc", …)
339 
340     /**
341      * Class names of annotation processors to run.
342      * If not set, the default annotation processors discovery process applies.
343      * If set, the value will be appended to the {@code -processor} compiler option.
344      *
345      * @see #proc
346      * @since 2.2
347      */
348     @Parameter
349     protected String[] annotationProcessors;
350 
351     /**
352      * Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation
353      * processors only in those classpath elements. If omitted (and {@code proc} is set to {@code only} or {@code full}), the default classpath is used to detect annotation
354      * processors. The detection itself depends on the configuration of {@link #annotationProcessors}.
355      * Since JDK 23 by default no annotation processing is performed as long as no processors is listed for security reasons.
356      * Therefore, you should always list the desired processors using this configuration element or {@code annotationProcessorPaths}.
357      *
358      * <p>
359      * Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier,
360      * type). Transitive dependencies are added automatically. Exclusions are supported as well. Example:
361      * </p>
362      *
363      * <pre>
364      * &lt;configuration&gt;
365      *   &lt;annotationProcessorPaths&gt;
366      *     &lt;path&gt;
367      *       &lt;groupId&gt;org.sample&lt;/groupId&gt;
368      *       &lt;artifactId&gt;sample-annotation-processor&lt;/artifactId&gt;
369      *       &lt;version&gt;1.2.3&lt;/version&gt; &lt;!-- Optional - taken from dependency management if not specified --&gt;
370      *       &lt;!-- Optionally exclude transitive dependencies --&gt;
371      *       &lt;exclusions&gt;
372      *         &lt;exclusion&gt;
373      *           &lt;groupId&gt;org.sample&lt;/groupId&gt;
374      *           &lt;artifactId&gt;sample-dependency&lt;/artifactId&gt;
375      *         &lt;/exclusion&gt;
376      *       &lt;/exclusions&gt;
377      *     &lt;/path&gt;
378      *     &lt;!-- ... more ... --&gt;
379      *   &lt;/annotationProcessorPaths&gt;
380      * &lt;/configuration&gt;
381      * </pre>
382      *
383      * <b>Note:</b> Exclusions are supported from version 3.11.0.
384      *
385      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-processor-path">javac -processorpath</a>
386      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#annotation-processing">javac Annotation Processing</a>
387      * @since 3.5
388      *
389      * @deprecated Replaced by ordinary dependencies with {@code <type>} element set to
390      * {@code processor}, {@code classpath-processor} or {@code modular-processor}.
391      */
392     @Parameter
393     @Deprecated(since = "4.0.0")
394     protected List<DependencyCoordinate> annotationProcessorPaths;
395 
396     /**
397      * Whether to use the Maven dependency management section when resolving transitive dependencies of annotation
398      * processor paths.
399      * <p>
400      * This flag does not enable / disable the ability to resolve the version of annotation processor paths
401      * from dependency management section. It only influences the resolution of transitive dependencies of those
402      * top-level paths.
403      * </p>
404      *
405      * @since 3.12.0
406      *
407      * @deprecated This flag is ignored.
408      * Replaced by ordinary dependencies with {@code <type>} element set to
409      * {@code processor}, {@code classpath-processor} or {@code modular-processor}.
410      */
411     @Deprecated(since = "4.0.0")
412     @Parameter(defaultValue = "false")
413     protected boolean annotationProcessorPathsUseDepMgmt;
414 
415     /**
416      * Whether to generate {@code package-info.class} even when empty.
417      * By default, package info source files that only contain javadoc and no annotation
418      * on the package can lead to no class file being generated by the compiler.
419      * It may cause a file miss on build systems that check for file existence in order to decide what to recompile.
420      *
421      * <p>If {@code true}, the {@code -Xpkginfo:always} compiler option is added if the compiler supports that
422      * extra option. If the extra option is not supported, then a warning is logged and no option is added to
423      * the compiler arguments.</p>
424      *
425      * @see #incrementalCompilation
426      * @since 3.10
427      */
428     @Parameter(property = "maven.compiler.createMissingPackageInfoClass", defaultValue = "false")
429     protected boolean createMissingPackageInfoClass;
430 
431     /**
432      * Whether to generate class files for implicitly referenced files.
433      * If set, the value will be appended to the {@code -implicit:} compiler option.
434      * Standard values are:
435      * <ul>
436      *   <li>{@code class} – automatically generates class files.</li>
437      *   <li>{@code none}  – suppresses class file generation.</li>
438      * </ul>
439      *
440      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-implicit">javac -implicit</a>
441      * @since 3.10.2
442      */
443     @Parameter(property = "maven.compiler.implicit")
444     protected String implicit;
445     // Reminder: if above list of legal values is modified, update also addComaSeparated("-implicit", …)
446 
447     /**
448      * Whether to generate metadata for reflection on method parameters.
449      * If {@code true}, the {@code -parameters} option will be added to compiler arguments.
450      *
451      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-parameters">javac -parameters</a>
452      * @since 3.6.2
453      */
454     @Parameter(property = "maven.compiler.parameters", defaultValue = "false")
455     protected boolean parameters;
456 
457     /**
458      * Whether to include debugging information in the compiled class files.
459      * The amount of debugging information to include is specified by the {@link #debuglevel} parameter.
460      * If this {@code debug} flag is {@code true}, then the {@code -g} option may be added to compiler arguments
461      * with a value determined by the {@link #debuglevel} argument. If this {@code debug} flag is {@code false},
462      * then the {@code -g:none} option will be added to the compiler arguments.
463      *
464      * @see #debuglevel
465      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-g">javac -g</a>
466      *
467      * @deprecated Setting this flag to {@code false} is replaced by {@code <debuglevel>none</debuglevel>}.
468      */
469     @Deprecated(since = "4.0.0")
470     @Parameter(property = "maven.compiler.debug", defaultValue = "true")
471     protected boolean debug = true;
472 
473     /**
474      * Kinds of debugging information to include in the compiled class files.
475      * Legal values are {@code lines}, {@code vars}, {@code source}, {@code all} and {@code none}.
476      * Values other than {@code all} and {@code none} can be combined in a comma-separated list.
477      *
478      * <p>If debug level is not specified, then the {@code -g} option will <em>not</em> be added,
479      * which means that the default debugging information will be generated
480      * (typically {@code lines} and {@code source} but not {@code vars}).</p>
481      *
482      * <p>If debug level is {@code all}, then only the {@code -g} option is added,
483      * which means that all debugging information will be generated.
484      * If debug level is anything else, then the comma-separated list of keywords
485      * is appended to the {@code -g} command-line switch.</p>
486      *
487      * @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>
488      * @since 2.1
489      */
490     @Parameter(property = "maven.compiler.debuglevel")
491     protected String debuglevel;
492     // Reminder: if above list of legal values is modified, update also addComaSeparated("-g", …)
493 
494     /**
495      * Whether to optimize the compiled code using the compiler's optimization methods.
496      * @deprecated This property is ignored.
497      */
498     @Deprecated(forRemoval = true)
499     @Parameter(property = "maven.compiler.optimize")
500     protected Boolean optimize;
501 
502     /**
503      * Whether to show messages about what the compiler is doing.
504      * If {@code true}, then the {@code -verbose} option will be added to compiler arguments.
505      * In addition, files such as {@code target/javac.args} will be generated even on successful compilation.
506      *
507      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-verbose">javac -verbose</a>
508      */
509     @Parameter(property = "maven.compiler.verbose", defaultValue = "false")
510     protected boolean verbose;
511 
512     /**
513      * Whether to provide more details about why a module is rebuilt.
514      * This is used only if {@link #incrementalCompilation} is set to something else than {@code "none"}.
515      *
516      * @see #incrementalCompilation
517      */
518     @Parameter(property = "maven.compiler.showCompilationChanges", defaultValue = "false")
519     protected boolean showCompilationChanges;
520 
521     /**
522      * Whether to show source locations where deprecated APIs are used.
523      * If {@code true}, then the {@code -deprecation} option will be added to compiler arguments.
524      * That option is itself a shorthand for {@code -Xlint:deprecation}.
525      *
526      * @see #showWarnings
527      * @see #failOnWarning
528      */
529     @Parameter(property = "maven.compiler.showDeprecation", defaultValue = "false")
530     protected boolean showDeprecation;
531 
532     /**
533      * Whether to show compilation warnings.
534      * If {@code false}, then the {@code -nowarn} option will be added to compiler arguments.
535      * That option is itself a shorthand for {@code -Xlint:none}.
536      *
537      * @see #showDeprecation
538      * @see #failOnWarning
539      */
540     @Parameter(property = "maven.compiler.showWarnings", defaultValue = "true")
541     protected boolean showWarnings = true;
542 
543     /**
544      * Whether the build will stop if there are compilation warnings.
545      * If {@code true}, then the {@code -Werror} option will be added to compiler arguments.
546      *
547      * @see #showWarnings
548      * @see #showDeprecation
549      * @since 3.6
550      */
551     @Parameter(property = "maven.compiler.failOnWarning", defaultValue = "false")
552     protected boolean failOnWarning;
553 
554     /**
555      * Whether the build will stop if there are compilation errors.
556      *
557      * @see #failOnWarning
558      * @since 2.0.2
559      */
560     @Parameter(property = "maven.compiler.failOnError", defaultValue = "true")
561     protected boolean failOnError = true;
562 
563     /**
564      * Sets the name of the output file when compiling a set of sources to a single file.
565      *
566      * <p>expression="${project.build.finalName}"</p>
567      *
568      * @deprecated Bundling many class files into a single file should be done by other plugins.
569      */
570     @Parameter
571     @Deprecated(since = "4.0.0", forRemoval = true)
572     protected String outputFileName;
573 
574     /**
575      * Timestamp for reproducible output archive entries. It can be either formatted as ISO 8601
576      * {@code yyyy-MM-dd'T'HH:mm:ssXXX} or as an int representing seconds since the epoch (like
577      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
578      *
579      * @since 3.12.0
580      *
581      * @deprecated Not used by the compiler plugin since it does not generate archive.
582      */
583     @Deprecated(since = "4.0.0", forRemoval = true)
584     @Parameter(defaultValue = "${project.build.outputTimestamp}")
585     protected String outputTimestamp;
586 
587     /**
588      * The algorithm to use for selecting which files to compile.
589      * Values can be {@code dependencies}, {@code sources}, {@code classes}, {@code rebuild-on-change},
590      * {@code rebuild-on-add}, {@code modules} or {@code none}.
591      *
592      * <p><b>{@code options}:</b>
593      * recompile all source files if the compiler options changed.
594      * Changes are detected on a <i>best-effort</i> basis only.</p>
595      *
596      * <p><b>{@code dependencies}:</b>
597      * recompile all source files if at least one dependency (JAR file) changed since the last build.
598      * This check is based on the last modification times of JAR files.</p>
599      *
600      * <p><b>{@code sources}:</b>
601      * recompile source files modified since the last build.
602      * In addition, if a source file has been deleted, then all source files are recompiled.
603      * This check is based on the modification times of source files
604      * rather than the modification times of the {@code *.class} files.</p>
605      *
606      * <p><b>{@code classes}:</b>
607      * recompile source files ({@code *.java}) associated to no output file ({@code *.class})
608      * or associated to an output file older than the source. This algorithm does not check
609      * if a source file has been removed, potentially leaving non-recompiled classes with
610      * references to classes that no longer exist.</p>
611      *
612      * <p>The {@code sources} and {@code classes} values are partially redundant,
613      * doing the same work in different ways. It is usually not necessary to specify those two values.</p>
614      *
615      * <p><b>{@code modules}:</b>
616      * recompile modules and let the compiler decides which individual files to recompile.
617      * The compiler plugin does not enumerate the source files to recompile (actually, it does not scan at all the
618      * source directories). Instead, it only specifies the module to recompile using the {@code --module} option.
619      * The Java compiler will scan the source directories itself and compile only those source files that are newer
620      * than the corresponding files in the output directory.</p>
621      *
622      * <p><b>{@code rebuild-on-add}:</b>
623      * modifier for recompiling all source files when the addition of a new file is detected.
624      * This flag is effective only when used together with {@code sources} or {@code classes}.
625      * When used with {@code classes}, it provides a way to detect class renaming
626      * (this is not needed with {@code sources} for detecting renaming).</p>
627      *
628      * <p><b>{@code rebuild-on-change}:</b>
629      * modifier for recompiling all source files when a change is detected in at least one source file.
630      * This flag is effective only when used together with {@code sources} or {@code classes}.
631      * It does not rebuild when a new source file is added without change in other files,
632      * unless {@code rebuild-on-add} is also specified.</p>
633      *
634      * <p><b>{@code none}:</b>
635      * the compiler plugin unconditionally specifies all sources to the Java compiler.
636      * This option is mutually exclusive with all other incremental compilation options.</p>
637      *
638      * <h4>Limitations</h4>
639      * In all cases, the current compiler-plugin does not detect structural changes other than file addition or removal.
640      * For example, the plugin does not detect whether a method has been removed in a class.
641      *
642      * <h4>Default value</h4>
643      * The default value depends on the context.
644      * If there is no annotation processor, then the default is {@code "options,dependencies,sources"}.
645      * It means that a full rebuild will be done if the compiler options or the dependencies changed,
646      * or if a source file has been deleted. Otherwise, only the modified source files will be recompiled.
647      *
648      * <p>If an annotation processor is present (e.g., {@link #proc} set to a value other than {@code "none"}),
649      * then the default value is same as above with the addition of {@code "rebuild-on-add,rebuild-on-change"}.
650      * It means that a full rebuild will be done if any kind of change is detected.</p>
651      *
652      * @see #staleMillis
653      * @see #fileExtensions
654      * @see #showCompilationChanges
655      * @see #createMissingPackageInfoClass
656      * @since 4.0.0
657      */
658     @Parameter(property = "maven.compiler.incrementalCompilation")
659     protected String incrementalCompilation;
660 
661     /**
662      * Whether to enable/disable incremental compilation feature.
663      *
664      * @since 3.1
665      *
666      * @deprecated Replaced by {@link #incrementalCompilation}.
667      * A value of {@code true} in this old property is equivalent to {@code "dependencies,sources,rebuild-on-add"}
668      * in the new property, and a value of {@code false} is equivalent to {@code "classes"}.
669      */
670     @Deprecated(since = "4.0.0")
671     @Parameter(property = "maven.compiler.useIncrementalCompilation")
672     protected Boolean useIncrementalCompilation;
673 
674     /**
675      * Returns the configuration of the incremental compilation.
676      * If the argument is null or blank, then this method applies
677      * the default values documented in {@link #incrementalCompilation} javadoc.
678      *
679      * @throws MojoException if a value is not recognized, or if mutually exclusive values are specified
680      */
681     final EnumSet<IncrementalBuild.Aspect> incrementalCompilationConfiguration() {
682         if (isAbsent(incrementalCompilation)) {
683             if (useIncrementalCompilation != null) {
684                 return useIncrementalCompilation
685                         ? EnumSet.of(
686                                 IncrementalBuild.Aspect.DEPENDENCIES,
687                                 IncrementalBuild.Aspect.SOURCES,
688                                 IncrementalBuild.Aspect.REBUILD_ON_ADD)
689                         : EnumSet.of(IncrementalBuild.Aspect.CLASSES);
690             }
691             return EnumSet.of(
692                     IncrementalBuild.Aspect.OPTIONS,
693                     IncrementalBuild.Aspect.DEPENDENCIES,
694                     IncrementalBuild.Aspect.SOURCES);
695         }
696         return IncrementalBuild.Aspect.parse(incrementalCompilation);
697     }
698 
699     /**
700      * Amends the configuration of incremental compilation for the presence of annotation processors.
701      *
702      * @param aspects the configuration to amend if an annotation processor is found
703      * @param dependencyTypes the type of dependencies, for checking if any of them is a processor path
704      */
705     final void amendincrementalCompilation(EnumSet<IncrementalBuild.Aspect> aspects, Set<PathType> dependencyTypes) {
706         if (isAbsent(incrementalCompilation) && hasAnnotationProcessor(dependencyTypes)) {
707             aspects.add(IncrementalBuild.Aspect.REBUILD_ON_ADD);
708             aspects.add(IncrementalBuild.Aspect.REBUILD_ON_CHANGE);
709         }
710     }
711 
712     /**
713      * File extensions to check timestamp for incremental build.
714      * Default contains only {@code class} and {@code jar}.
715      *
716      * TODO: Rename with a name making clearer that this parameter is about incremental build.
717      *
718      * @see #incrementalCompilation
719      * @since 3.1
720      */
721     @Parameter(defaultValue = "class,jar")
722     protected List<String> fileExtensions;
723 
724     /**
725      * The granularity in milliseconds of the last modification
726      * date for testing whether a source needs recompilation.
727      *
728      * @see #incrementalCompilation
729      */
730     @Parameter(property = "lastModGranularityMs", defaultValue = "0")
731     protected int staleMillis;
732 
733     /**
734      * Allows running the compiler in a separate process.
735      * If {@code false}, the plugin uses the built-in compiler, while if {@code true} it will use an executable.
736      *
737      * @see #executable
738      * @see #compilerId
739      * @see #meminitial
740      * @see #maxmem
741      */
742     @Parameter(property = "maven.compiler.fork", defaultValue = "false")
743     protected boolean fork;
744 
745     /**
746      * Requirements for this JDK toolchain for using a different {@code javac} than the one of the JDK used by Maven.
747      * This overrules the toolchain selected by the
748      * <a href="https://maven.apache.org/plugins/maven-toolchains-plugin/">maven-toolchain-plugin</a>.
749      * See <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html"> Guide to Toolchains</a>
750      * for more info.
751      *
752      * <pre>
753      * &lt;configuration&gt;
754      *   &lt;jdkToolchain&gt;
755      *     &lt;version&gt;11&lt;/version&gt;
756      *   &lt;/jdkToolchain&gt;
757      *   ...
758      * &lt;/configuration&gt;
759      *
760      * &lt;configuration&gt;
761      *   &lt;jdkToolchain&gt;
762      *     &lt;version&gt;1.8&lt;/version&gt;
763      *     &lt;vendor&gt;zulu&lt;/vendor&gt;
764      *   &lt;/jdkToolchain&gt;
765      *   ...
766      * &lt;/configuration&gt;
767      * </pre>
768      *
769      * @see #fork
770      * @see #executable
771      * @since 3.6
772      */
773     @Parameter
774     protected Map<String, String> jdkToolchain;
775 
776     /**
777      * Identifier of the compiler to use. This identifier shall match the identifier of a compiler known
778      * to the {@linkplain #jdkToolchain JDK tool chain}, or the {@linkplain JavaCompiler#name() name} of
779      * a {@link JavaCompiler} instance registered as a service findable by {@link ServiceLoader}.
780      * See this <a href="non-javac-compilers.html">guide</a> for more information.
781      * If unspecified, then the {@linkplain ToolProvider#getSystemJavaCompiler() system Java compiler} is used.
782      * The identifier of the system Java compiler is usually {@code javac}.
783      *
784      * @see #fork
785      * @see #executable
786      * @see JavaCompiler#name()
787      */
788     @Parameter(property = "maven.compiler.compilerId")
789     protected String compilerId;
790 
791     /**
792      * Version of the compiler to use if {@link #fork} is set to {@code true}.
793      * Examples! "1.3", "1.5".
794      *
795      * @deprecated This parameter is no longer used by the underlying compilers.
796      *
797      * @see #fork
798      */
799     @Deprecated(since = "4.0.0", forRemoval = true)
800     @Parameter(property = "maven.compiler.compilerVersion")
801     protected String compilerVersion;
802 
803     /**
804      * Whether to use the legacy {@code com.sun.tools.javac} API instead of {@code javax.tools} API.
805      *
806      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.compiler/javax/tools/package-summary.html">New API</a>
807      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/jdk.compiler/com/sun/tools/javac/package-summary.html">Legacy API</a>
808      * @since 3.13
809      *
810      * @deprecated Ignored because the compiler plugin now always use the {@code javax.tools} API.
811      */
812     @Deprecated(since = "4.0.0", forRemoval = true)
813     @Parameter(property = "maven.compiler.forceLegacyJavacApi")
814     protected Boolean forceLegacyJavacApi;
815 
816     /**
817      * Whether to use legacy compiler API.
818      *
819      * @since 3.0
820      *
821      * @deprecated Ignored because {@code java.lang.Compiler} has been deprecated and removed from the JDK.
822      */
823     @Deprecated(since = "4.0.0", forRemoval = true)
824     @Parameter(property = "maven.compiler.forceJavacCompilerUse")
825     protected Boolean forceJavacCompilerUse;
826 
827     /**
828      * Strategy to re use {@code javacc} class created. Legal values are:
829      * <ul>
830      *   <li>{@code reuseCreated} (default) – will reuse already created but in case of multi-threaded builds,
831      *       each thread will have its own instance.</li>
832      *   <li>{@code reuseSame} – the same Javacc class will be used for each compilation even
833      *       for multi-threaded build.</li>
834      *   <li>{@code alwaysNew} – a new Javacc class will be created for each compilation.</li>
835      * </ul>
836      * Note this parameter value depends on the OS/JDK you are using, but the default value should work on most of env.
837      *
838      * @since 2.5
839      *
840      * @deprecated Not supported anymore. The reuse of {@link JavaFileManager} instance is plugin implementation details.
841      */
842     @Deprecated(since = "4.0.0", forRemoval = true)
843     @Parameter(property = "maven.compiler.compilerReuseStrategy")
844     protected String compilerReuseStrategy;
845 
846     /**
847      * @since 2.5
848      *
849      * @deprecated Deprecated as a consequence of {@link #compilerReuseStrategy} deprecation.
850      */
851     @Deprecated(since = "4.0.0", forRemoval = true)
852     @Parameter(property = "maven.compiler.skipMultiThreadWarning")
853     protected Boolean skipMultiThreadWarning;
854 
855     /**
856      * Executable of the compiler to use when {@link #fork} is {@code true}.
857      * If this parameter is specified, then the {@link #jdkToolchain} is ignored.
858      *
859      * @see #jdkToolchain
860      * @see #fork
861      * @see #compilerId
862      */
863     @Parameter(property = "maven.compiler.executable")
864     protected String executable;
865 
866     /**
867      * Initial size, in megabytes, of the memory allocation pool if {@link #fork} is set to {@code true}.
868      * Examples: "64", "64M". Suffixes "k" (for kilobytes) and "G" (for gigabytes) are also accepted.
869      * If no suffix is provided, "M" is assumed.
870      *
871      * @see #fork
872      * @since 2.0.1
873      */
874     @Parameter(property = "maven.compiler.meminitial")
875     protected String meminitial;
876 
877     /**
878      * Maximum size, in megabytes, of the memory allocation pool if {@link #fork} is set to {@code true}.
879      * Examples: "128", "128M". Suffixes "k" (for kilobytes) and "G" (for gigabytes) are also accepted.
880      * If no suffix is provided, "M" is assumed.
881      *
882      * @see #fork
883      * @since 2.0.1
884      */
885     @Parameter(property = "maven.compiler.maxmem")
886     protected String maxmem;
887 
888     // ----------------------------------------------------------------------
889     // Read-only parameters
890     // ----------------------------------------------------------------------
891 
892     /**
893      * The directory to run the compiler from if fork is true.
894      */
895     @Parameter(defaultValue = "${project.basedir}", required = true, readonly = true)
896     protected Path basedir;
897 
898     /**
899      * Path to a file where to cache information about the last incremental build.
900      * This is used when "incremental" builds are enabled for detecting additions
901      * or removals of source files, or changes in plugin configuration.
902      * This file should be in the output directory and can be deleted at any time
903      */
904     @Parameter(
905             defaultValue =
906                     "${project.build.directory}/maven-status/${mojo.plugin.descriptor.artifactId}/${mojo.executionId}.cache",
907             required = true,
908             readonly = true)
909     protected Path mojoStatusPath;
910 
911     /**
912      * The current build session instance.
913      */
914     @Inject
915     protected Session session;
916 
917     /**
918      * The current project instance.
919      */
920     @Inject
921     protected Project project;
922 
923     @Inject
924     protected ProjectManager projectManager;
925 
926     @Inject
927     protected ArtifactManager artifactManager;
928 
929     @Inject
930     protected ToolchainManager toolchainManager;
931 
932     @Inject
933     protected MessageBuilderFactory messageBuilderFactory;
934 
935     /**
936      * The logger for reporting information or warnings to the user.
937      * Currently, this is also used for console output.
938      *
939      * <h4>Thread safety</h4>
940      * This logger should be thread-safe if the {@link ToolExecutor} is executed in a background thread.
941      */
942     @Inject
943     protected Log logger;
944 
945     /**
946      * Cached value for writing replacement proposal when a deprecated option is used.
947      * This is set to a non-null value when first needed. An empty string means that
948      * this information couldn't be fetched.
949      *
950      * @see #writePlugin(MessageBuilder, String, String)
951      */
952     private String mavenCompilerPluginVersion;
953 
954     /**
955      * A tip about how to launch the Java compiler from the command-line.
956      * The command-line may have {@code -J} options before the argument file.
957      * This is non-null if the compilation failed or if Maven is executed in debug mode.
958      */
959     private String tipForCommandLineCompilation;
960 
961     /**
962      * {@code MAIN_COMPILE} if this MOJO is for compiling the main code,
963      * or {@code TEST_COMPILE} if compiling the tests.
964      */
965     final PathScope compileScope;
966 
967     /**
968      * Creates a new MOJO.
969      *
970      * @param compileScope {@code MAIN_COMPILE} or {@code TEST_COMPILE}
971      */
972     protected AbstractCompilerMojo(PathScope compileScope) {
973         this.compileScope = compileScope;
974     }
975 
976     /**
977      * {@return the inclusion filters for the compiler, or an empty list for all Java source files}
978      * The filter patterns are described in {@link java.nio.file.FileSystem#getPathMatcher(String)}.
979      * If no syntax is specified, the default syntax is a derivative of "glob" compatible with the
980      * behavior of Maven 3.
981      */
982     protected abstract Set<String> getIncludes();
983 
984     /**
985      * {@return the exclusion filters for the compiler, or an empty list if none}
986      * The filter patterns are described in {@link java.nio.file.FileSystem#getPathMatcher(String)}.
987      * If no syntax is specified, the default syntax is a derivative of "glob" compatible with the
988      * behavior of Maven 3.
989      */
990     protected abstract Set<String> getExcludes();
991 
992     /**
993      * {@return the exclusion filters for the incremental calculation}
994      * Updated source files, if excluded by this filter, will not cause the project to be rebuilt.
995      *
996      * @see SourceFile#ignoreModification
997      */
998     protected abstract Set<String> getIncrementalExcludes();
999 
1000     /**
1001      * {@return whether all includes/excludes matchers specified in the plugin configuration are empty}
1002      * This method checks only the plugin configuration. It does not check the {@code <source>} elements.
1003      */
1004     final boolean hasNoFileMatchers() {
1005         return getIncludes().isEmpty()
1006                 && getExcludes().isEmpty()
1007                 && getIncrementalExcludes().isEmpty();
1008     }
1009 
1010     /**
1011      * {@return the destination directory (or class output directory) for class files}
1012      * This directory will be given to the {@code -d} Java compiler option.
1013      */
1014     @Nonnull
1015     protected abstract Path getOutputDirectory();
1016 
1017     /**
1018      * {@return the {@code --source} argument for the Java compiler}
1019      * The default implementation returns the {@link #source} value.
1020      */
1021     @Nullable
1022     protected String getSource() {
1023         return source;
1024     }
1025 
1026     /**
1027      * {@return the {@code --target} argument for the Java compiler}
1028      * The default implementation returns the {@link #target} value.
1029      */
1030     @Nullable
1031     protected String getTarget() {
1032         return target;
1033     }
1034 
1035     /**
1036      * {@return the {@code --release} argument for the Java compiler}
1037      * The default implementation returns the {@link #release} value.
1038      */
1039     @Nullable
1040     protected String getRelease() {
1041         return release;
1042     }
1043 
1044     /**
1045      * {@return the root directories of Java source code for the given scope}
1046      * This method ignores the deprecated {@link #compileSourceRoots} element.
1047      *
1048      * @param scope whether to get the directories for main code or for the test code
1049      */
1050     final Stream<SourceRoot> getSourceRoots(ProjectScope scope) {
1051         return projectManager.getEnabledSourceRoots(project, scope, Language.JAVA_FAMILY);
1052     }
1053 
1054     /**
1055      * {@return the root directories of the Java source files to compile, excluding empty directories}
1056      * The list needs to be modifiable for allowing the addition of generated source directories.
1057      * This is determined from the {@link #compileSourceRoots} plugin configuration if non-empty,
1058      * or from {@code <source>} elements otherwise.
1059      *
1060      * @param outputDirectory the directory where to store the compilation results
1061      * @throws IOException if this method needs to walk through directories and that operation failed
1062      */
1063     final List<SourceDirectory> getSourceDirectories(final Path outputDirectory) throws IOException {
1064         if (isAbsent(compileSourceRoots)) {
1065             Stream<SourceRoot> roots = getSourceRoots(compileScope.projectScope());
1066             return SourceDirectory.fromProject(roots, getRelease(), outputDirectory);
1067         } else {
1068             return SourceDirectory.fromPluginConfiguration(
1069                     compileSourceRoots, moduleOfPreviousExecution(), getRelease(), outputDirectory);
1070         }
1071     }
1072 
1073     /**
1074      * {@return the path where to place generated source files created by annotation processing}
1075      */
1076     @Nullable
1077     protected abstract Path getGeneratedSourcesDirectory();
1078 
1079     /**
1080      * Returns the module which is being patched in a multi-release project, or {@code null} if none.
1081      * This is used when the {@link CompilerMojo#multiReleaseOutput} deprecated flag is {@code true}.
1082      * This module name is handled in a special way because, contrarily to the case where the project
1083      * uses the recommended {@code <sources>} elements (in which case all target releases are compiled
1084      * in a single Maven Compiler Plugin execution), the Maven Compiler Plugin does not know what have
1085      * been compiled for the other releases, because each target release is compiled with an execution
1086      * of {@link CompilerMojo} separated from other executions.
1087      *
1088      * @return the module name in a previous execution of the compiler plugin, or {@code null} if none
1089      * @throws IOException if this method needs to walk through directories and that operation failed
1090      *
1091      * @see CompilerMojo#addImplicitDependencies(ToolExecutor)
1092      *
1093      * @deprecated For compatibility with the previous way to build multi-release JAR file.
1094      *             May be removed after we drop support of the old way to do multi-release.
1095      */
1096     @Deprecated(since = "4.0.0")
1097     String moduleOfPreviousExecution() throws IOException {
1098         return null;
1099     }
1100 
1101     /**
1102      * {@return whether the sources contain at least one {@code module-info.java} file}
1103      * Note that the sources may contain more than one {@code module-info.java} file
1104      * if compiling a project with Module Source Hierarchy.
1105      *
1106      * <p>If the user explicitly specified a modular or classpath project, then the
1107      * {@code module-info.java} is assumed to exist or not without verification.</p>
1108      *
1109      * <p>The test compiler overrides this method for checking the existence of the
1110      * the {@code module-info.class} file in the main output directory instead.</p>
1111      *
1112      * @param roots root directories of the sources to compile
1113      * @throws IOException if this method needed to read a module descriptor and failed
1114      */
1115     boolean hasModuleDeclaration(final List<SourceDirectory> roots) throws IOException {
1116         return switch (project.getPackaging().type().id()) {
1117             case Type.CLASSPATH_JAR -> false;
1118             case Type.MODULAR_JAR -> true;
1119             default -> {
1120                 for (SourceDirectory root : roots) {
1121                     if (root.getModuleInfo().isPresent()) {
1122                         yield true;
1123                     }
1124                 }
1125                 yield false;
1126             }
1127         };
1128     }
1129 
1130     /**
1131      * {@return the file where to dump the command-line when debug logging is enabled or when the compilation failed}
1132      * For example, if the value is {@code "javac"}, then the Java compiler can be launched
1133      * from the command-line by typing {@code javac @target/javac.args}.
1134      * The debug file will contain the compiler options together with the list of source files to compile.
1135      *
1136      * <p>Note: debug logging should not be confused with the {@link #debug} flag.</p>
1137      *
1138      * @see CompilerMojo#debugFileName
1139      * @see TestCompilerMojo#debugFileName
1140      */
1141     @Nullable
1142     protected abstract String getDebugFileName();
1143 
1144     /**
1145      * {@return the debug file name with its path, or null if none}
1146      * This method does not check if the debug file will be written, as the compilation result is not yet known.
1147      */
1148     final Path getDebugFilePath() {
1149         String filename = getDebugFileName();
1150         if (isAbsent(filename)) {
1151             return null;
1152         }
1153         // Do not use `this.getOutputDirectory()` because it may be deeper in `classes/META-INF/versions/`.
1154         return Path.of(project.getBuild().getOutputDirectory()).resolveSibling(filename);
1155     }
1156 
1157     /**
1158      * Returns whether the debug file should be written after a successful build.
1159      * By default, debug files are written only if the build failed.
1160      * However, some options can change this behavior.
1161      */
1162     final boolean shouldWriteDebugFile() {
1163         return verbose || logger.isDebugEnabled();
1164     }
1165 
1166     /**
1167      * Runs the Java compiler. This method performs the following steps:
1168      *
1169      * <ol>
1170      *   <li>Get a Java compiler by a call to {@link #compiler()}.</li>
1171      *   <li>Get the options to give to the compiler by a call to {@link #parseParameters(OptionChecker)}.</li>
1172      *   <li>Get an executor with {@link #createExecutor(DiagnosticListener)} with the default listener.</li>
1173      *   <li>{@linkplain ToolExecutor#applyIncrementalBuild Apply the incremental build} if enabled.</li>
1174      *   <li>{@linkplain ToolExecutor#compile Execute the compilation}.</li>
1175      *   <li>Shows messages in the {@linkplain #logger}.</li>
1176      * </ol>
1177      *
1178      * @throws MojoException if the compiler cannot be run
1179      */
1180     @Override
1181     public void execute() throws MojoException {
1182         JavaCompiler compiler = compiler();
1183         for (SourceVersion version : compiler.getSourceVersions()) {
1184             if (supportedVersion == null || version.compareTo(supportedVersion) >= 0) {
1185                 supportedVersion = version;
1186             }
1187         }
1188         Options configuration = parseParameters(compiler);
1189         try {
1190             compile(compiler, configuration);
1191         } catch (RuntimeException e) {
1192             String message = e.getLocalizedMessage();
1193             if (message == null) {
1194                 message = e.getClass().getSimpleName();
1195             } else if (e instanceof MojoException) {
1196                 int s = message.indexOf(System.lineSeparator());
1197                 if (s >= 0) {
1198                     message = message.substring(0, s); // Log only the first line.
1199                 }
1200             }
1201             MessageBuilder mb = messageBuilderFactory
1202                     .builder()
1203                     .strong("COMPILATION ERROR: ")
1204                     .a(message);
1205             logger.error(mb.toString(), verbose ? e : null);
1206             if (tipForCommandLineCompilation != null) {
1207                 logger.info(tipForCommandLineCompilation);
1208                 tipForCommandLineCompilation = null;
1209             }
1210             if (failOnError) {
1211                 throw e;
1212             }
1213         } catch (IOException e) {
1214             logger.error("I/O error while compiling the project.", e);
1215             throw new CompilationFailureException("I/O error while compiling the project.", e);
1216         }
1217     }
1218 
1219     /**
1220      * Creates a new task by taking a snapshot of the current configuration of this <abbr>MOJO</abbr>.
1221      * This method creates the {@linkplain ToolExecutor#outputDirectory output directory} if it does not already exist.
1222      *
1223      * <h4>Multi-threading</h4>
1224      * This method and the returned objects are not thread-safe.
1225      * However, this method takes a snapshot of the configuration of this <abbr>MOJO</abbr>.
1226      * Changes in this <abbr>MOJO</abbr> after this method call will not affect the returned executor.
1227      * Therefore, the executor can safely be executed in a background thread,
1228      * provided that the {@link #logger} is thread-safe.
1229      *
1230      * @param listener where to send compilation warnings, or {@code null} for the Maven logger
1231      * @return the task to execute for compiling the project using the configuration in this <abbr>MOJO</abbr>
1232      * @throws MojoException if this method identifies an invalid parameter in this <abbr>MOJO</abbr>
1233      * @throws IOException if an error occurred while creating the output directory or scanning the source directories
1234      * @throws MavenException if an error occurred while fetching dependencies
1235      */
1236     public ToolExecutor createExecutor(DiagnosticListener<? super JavaFileObject> listener) throws IOException {
1237         var executor = new ToolExecutor(this, listener);
1238         if (!(targetOrReleaseSet || executor.isReleaseSpecifiedForAll())) {
1239             MessageBuilder mb = messageBuilderFactory
1240                     .builder()
1241                     .a("No explicit value set for --release or --target. "
1242                             + "To ensure the same result in different environments, please add")
1243                     .newline()
1244                     .newline();
1245             writePlugin(mb, "release", String.valueOf(Runtime.version().feature()));
1246             logger.warn(mb.build());
1247         }
1248         return executor;
1249     }
1250 
1251     /**
1252      * {@return the compiler to use for compiling the code}
1253      * If {@link #fork} is {@code true}, the returned compiler will be a wrapper for a command line.
1254      * Otherwise, it will be the compiler identified by {@link #compilerId} if a value was supplied,
1255      * or the standard compiler provided with the Java platform otherwise.
1256      *
1257      * @throws MojoException if no compiler was found
1258      */
1259     public JavaCompiler compiler() throws MojoException {
1260         /*
1261          * Use the `compilerId` as identifier for toolchains.
1262          * I.e, we assume that `compilerId` is also the name of the executable binary.
1263          */
1264         getToolchain().ifPresent((tc) -> {
1265             logger.info("Toolchain in maven-compiler-plugin is \"" + tc + "\".");
1266             if (executable != null) {
1267                 logger.warn(
1268                         "Toolchains are ignored because the 'executable' parameter is set to \"" + executable + "\".");
1269             } else {
1270                 fork = true;
1271                 if (compilerId == null) {
1272                     compilerId = DEFAULT_EXECUTABLE;
1273                 }
1274                 // TODO somehow shaky dependency between compilerId and tool executable.
1275                 executable = tc.findTool(compilerId);
1276             }
1277         });
1278         if (fork) {
1279             if (executable == null) {
1280                 executable = DEFAULT_EXECUTABLE;
1281             }
1282             return new ForkedCompiler(this);
1283         }
1284         /*
1285          * Search a `javax.tools.JavaCompiler` having a name matching the specified `compilerId`.
1286          * This is done before other code that can cause the mojo to return before the lookup is
1287          * done, possibly resulting in misconfigured POMs still building. If no `compilerId` was
1288          * specified, then the Java compiler bundled with the JDK is used (it may be absent).
1289          */
1290         if (logger.isDebugEnabled()) {
1291             logger.debug(
1292                     "Using " + (compilerId != null ? ("compiler \"" + compilerId + '"') : "system compiler") + '.');
1293         }
1294         if (compilerId == null) {
1295             compilerId = DEFAULT_EXECUTABLE;
1296             final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
1297             if (compiler != null) {
1298                 return compiler;
1299             }
1300         } else {
1301             for (JavaCompiler t : ServiceLoader.load(JavaCompiler.class)) {
1302                 if (compilerId.equals(t.name())) {
1303                     return t;
1304                 }
1305             }
1306         }
1307         throw new CompilationFailureException("No such \"" + compilerId + "\" compiler.");
1308     }
1309 
1310     /**
1311      * Parses the parameters declared in the <abbr>MOJO</abbr>.
1312      * The {@link #release} parameter is excluded because it is handled in a special way
1313      * in order to support the compilation of multi-version projects.
1314      *
1315      * @param  compiler  the tools to use for verifying the validity of options
1316      * @return the options after validation
1317      */
1318     public Options parseParameters(final OptionChecker compiler) {
1319         /*
1320          * Options to provide to the compiler, excluding all kinds of path (source files, destination directory,
1321          * class-path, module-path, etc.). Some options are validated by Maven in addition of being validated by
1322          * the compiler. In those cases, the validation by the compiler is done before the validation by Maven.
1323          * For example, Maven will check for illegal values in the "-g" option only if the compiler rejected
1324          * the fully formatted option (e.g. "-g:vars,lines") that we provided to it.
1325          */
1326         final var configuration = new Options(compiler, logger);
1327         configuration.addIfNonBlank("--source", getSource());
1328         targetOrReleaseSet = configuration.addIfNonBlank("--target", getTarget());
1329         targetOrReleaseSet |= configuration.setRelease(getRelease());
1330         configuration.addIfTrue("--enable-preview", enablePreview);
1331         configuration.addComaSeparated("-proc", proc, List.of("none", "only", "full"), null);
1332         if (annotationProcessors != null) {
1333             var list = new StringJoiner(",");
1334             for (String p : annotationProcessors) {
1335                 list.add(p);
1336             }
1337             configuration.addIfNonBlank("-processor", list.toString());
1338         }
1339         configuration.addComaSeparated("-implicit", implicit, List.of("none", "class"), null);
1340         configuration.addIfTrue("-parameters", parameters);
1341         configuration.addIfTrue("-Xpkginfo:always", createMissingPackageInfoClass);
1342         if (debug) {
1343             configuration.addComaSeparated(
1344                     "-g",
1345                     debuglevel,
1346                     List.of("lines", "vars", "source", "all", "none"),
1347                     (options) -> Arrays.asList(options).contains("all") ? new String[0] : options);
1348         } else {
1349             configuration.addIfTrue("-g:none", true);
1350         }
1351         configuration.addIfNonBlank("--module-version", moduleVersion);
1352         configuration.addIfTrue("-deprecation", showDeprecation);
1353         configuration.addIfTrue("-nowarn", !showWarnings);
1354         configuration.addIfTrue("-Werror", failOnWarning);
1355         configuration.addIfTrue("-verbose", verbose);
1356         if (fork) {
1357             configuration.addMemoryValue("-J-Xms", "meminitial", meminitial, SUPPORT_LEGACY);
1358             configuration.addMemoryValue("-J-Xmx", "maxmem", maxmem, SUPPORT_LEGACY);
1359         }
1360         return configuration;
1361     }
1362 
1363     /**
1364      * Runs the compiler, then shows the result in the Maven logger.
1365      *
1366      * @param compiler the compiler
1367      * @param configuration options to provide to the compiler
1368      * @throws IOException if an input file cannot be read
1369      * @throws MojoException if the compilation failed
1370      */
1371     @SuppressWarnings("UseSpecificCatch")
1372     private void compile(final JavaCompiler compiler, final Options configuration) throws IOException {
1373         final ToolExecutor executor = createExecutor(null);
1374         if (!executor.applyIncrementalBuild(this, configuration)) {
1375             return;
1376         }
1377         Throwable failureCause = null;
1378         final var compilerOutput = new StringWriter();
1379         boolean success;
1380         try {
1381             success = executor.compile(compiler, configuration, compilerOutput);
1382         } catch (Exception | NoClassDefFoundError e) {
1383             // `NoClassDefFoundError` may happen if a dependency of an annotation processor is missing.
1384             success = false;
1385             failureCause = e;
1386         }
1387         /*
1388          * The compilation errors or warnings should have already been reported by `DiagnosticLogger`.
1389          * However, the compiler may have other messages not associated to a particular source file.
1390          * For example, `ForkedCompiler` uses this writer if the compilation has been interrupted.
1391          */
1392         String additionalMessage = compilerOutput.toString();
1393         if (!additionalMessage.isBlank()) {
1394             if (success || failureCause != null) { // Keep the error level for the exception message.
1395                 logger.warn(additionalMessage);
1396             } else {
1397                 logger.error(additionalMessage);
1398             }
1399         }
1400         if (failureCause != null) {
1401             String message = failureCause.getMessage();
1402             if (message != null) {
1403                 logger.error(message);
1404             } else {
1405                 logger.error(failureCause);
1406             }
1407         }
1408         /*
1409          * In case of failure, or if debugging is enabled, dump the options to a file.
1410          * By default, the file will have the ".args" extension.
1411          */
1412         if (!success || shouldWriteDebugFile()) {
1413             IOException suppressed = null;
1414             try {
1415                 writeDebugFile(executor, configuration, success);
1416                 if (success && tipForCommandLineCompilation != null) {
1417                     logger.debug(tipForCommandLineCompilation);
1418                     tipForCommandLineCompilation = null;
1419                 }
1420             } catch (IOException e) {
1421                 suppressed = e;
1422             }
1423             if (!success) {
1424                 var message = new StringBuilder(100)
1425                         .append("Cannot compile ")
1426                         .append(project.getId())
1427                         .append(' ')
1428                         .append(compileScope.projectScope().id())
1429                         .append(" classes.");
1430                 if (executor.listener instanceof DiagnosticLogger diagnostic) {
1431                     diagnostic.firstError(failureCause).ifPresent((c) -> message.append(System.lineSeparator())
1432                             .append("The first error is: ")
1433                             .append(c));
1434                 }
1435                 var failure = new CompilationFailureException(message.toString(), failureCause);
1436                 if (suppressed != null) {
1437                     failure.addSuppressed(suppressed);
1438                 }
1439                 throw failure;
1440             }
1441             if (suppressed != null) {
1442                 throw suppressed;
1443             }
1444         }
1445         /*
1446          * Workaround for MCOMPILER-542, needed only if a modular project is compiled with a JDK older than Java 22.
1447          * Note: a previous version used as an heuristic way to detect if Reproducible Build was enabled. This check
1448          * has been removed because Reproducible Build are enabled by default in Maven now.
1449          */
1450         if (!isVersionEqualOrNewer(RELEASE_22)) {
1451             Path moduleDescriptor = executor.outputDirectory.resolve(MODULE_INFO + CLASS_FILE_SUFFIX);
1452             if (Files.isRegularFile(moduleDescriptor)) {
1453                 byte[] oridinal = Files.readAllBytes(moduleDescriptor);
1454                 byte[] modified = ByteCodeTransformer.patchJdkModuleVersion(oridinal, getRelease(), logger);
1455                 if (modified != null) {
1456                     Files.write(moduleDescriptor, modified);
1457                 }
1458             }
1459         }
1460     }
1461 
1462     /**
1463      * Returns whether the compiler supports the given source version or newer versions.
1464      * The specified source version shall be the name of one of the {@link SourceVersion} enumeration values.
1465      * Note that a return value of {@code true} does not mean that the compiler supports that exact version,
1466      * as it may supports only newer versions.
1467      */
1468     private boolean isVersionEqualOrNewer(String sourceVersion) {
1469         final SourceVersion requested;
1470         try {
1471             requested = SourceVersion.valueOf(sourceVersion);
1472         } catch (IllegalArgumentException e) {
1473             // The current tool is from a JDK older than the one for the requested source release.
1474             return false;
1475         }
1476         if (supportedVersion == null) {
1477             supportedVersion = SourceVersion.latestSupported();
1478         }
1479         return supportedVersion.compareTo(requested) >= 0;
1480     }
1481 
1482     /**
1483      * Returns whether the given string is null or empty, ignoring spaces.
1484      * This is a convenience for a frequent check, and also for clarity.
1485      */
1486     private static boolean isAbsent(String c) {
1487         return (c == null) || c.isBlank();
1488     }
1489 
1490     /**
1491      * Returns whether the given array is null or empty.
1492      * Defined as a complement of {@link #isAbsent(Collection)}.
1493      */
1494     private static boolean isAbsent(Object[] c) {
1495         return (c == null) || c.length == 0;
1496     }
1497 
1498     /**
1499      * Returns whether the given collection is null or empty.
1500      * This is a convenience for a frequent check, and also for clarity.
1501      */
1502     static boolean isAbsent(Collection<?> c) {
1503         return (c == null) || c.isEmpty();
1504     }
1505 
1506     /**
1507      * {@return the tool chain specified by the user in plugin parameters}
1508      */
1509     private Optional<Toolchain> getToolchain() {
1510         if (jdkToolchain != null) {
1511             List<Toolchain> tcs = toolchainManager.getToolchains(session, "jdk", jdkToolchain);
1512             if (!isAbsent(tcs)) {
1513                 return Optional.of(tcs.get(0));
1514             }
1515         }
1516         return toolchainManager.getToolchainFromBuildContext(session, "jdk");
1517     }
1518 
1519     /**
1520      * Returns the module name as declared in the given {@code module-info.java} source file.
1521      * This approach is less reliable than reading the compiled {@code module-info.class} file,
1522      * but is sometime needed when the compiled file is not yet available.
1523      *
1524      * @param source the source file to parse (may be null or not exist)
1525      * @return the module name, or {@code null} if not found
1526      *
1527      * @deprecated This is invoked only by other deprecated methods.
1528      */
1529     @Deprecated(since = "4.0.0")
1530     final String parseModuleInfoName(Path source) throws IOException {
1531         if (source != null && Files.exists(source)) {
1532             Charset charset = charset();
1533             try (BufferedReader in =
1534                     (charset != null) ? Files.newBufferedReader(source, charset) : Files.newBufferedReader(source)) {
1535                 var tokenizer = new StreamTokenizer(in);
1536                 tokenizer.slashSlashComments(true);
1537                 tokenizer.slashStarComments(true);
1538                 int t;
1539                 while ((t = tokenizer.nextToken()) != StreamTokenizer.TT_EOF) {
1540                     if (t == StreamTokenizer.TT_WORD && "module".equals(tokenizer.sval)) {
1541                         do {
1542                             t = tokenizer.nextToken();
1543                         } while (t == StreamTokenizer.TT_EOL);
1544                         if (t == StreamTokenizer.TT_WORD) {
1545                             return tokenizer.sval;
1546                         }
1547                         break; // Found a "module" keyword followed by something that we didn't recognized.
1548                     }
1549                 }
1550             }
1551         }
1552         return null;
1553     }
1554 
1555     /**
1556      * {@return all dependencies grouped by the path types where to place them}
1557      * If the module-path contains any filename-based dependency and this <abbr>MOJO</abbr>
1558      * is compiling the main code, then a warning will be logged.
1559      *
1560      * @param hasModuleDeclaration whether to allow placement of dependencies on the module-path.
1561      * @throws IOException if an I/O error occurred while fetching dependencies
1562      * @throws MavenException if an error occurred while fetching dependencies for a reason other than I/O.
1563      */
1564     final DependencyResolverResult resolveDependencies(boolean hasModuleDeclaration) throws IOException {
1565         DependencyResolver resolver = session.getService(DependencyResolver.class);
1566         if (resolver == null) { // Null value happen during tests, depending on the mock used.
1567             return null;
1568         }
1569         var allowedTypes = EnumSet.of(JavaPathType.CLASSES, JavaPathType.PROCESSOR_CLASSES);
1570         if (hasModuleDeclaration) {
1571             allowedTypes.add(JavaPathType.MODULES);
1572             allowedTypes.add(JavaPathType.PROCESSOR_MODULES);
1573         }
1574         DependencyResolverResult dependencies = resolver.resolve(DependencyResolverRequest.builder()
1575                 .session(session)
1576                 .project(project)
1577                 .requestType(DependencyResolverRequest.RequestType.RESOLVE)
1578                 .pathScope(compileScope)
1579                 .pathTypeFilter(allowedTypes)
1580                 .build());
1581         /*
1582          * Report errors or warnings. If possible, we rethrow the first exception directly without
1583          * wrapping in a `MojoException` for making the stack-trace a little bit easier to analyze.
1584          */
1585         Exception exception = null;
1586         for (Exception cause : dependencies.getExceptions()) {
1587             if (exception != null) {
1588                 exception.addSuppressed(cause);
1589             } else if (cause instanceof UncheckedIOException e) {
1590                 exception = e.getCause();
1591             } else if (cause instanceof RuntimeException || cause instanceof IOException) {
1592                 exception = cause;
1593             } else {
1594                 exception = new CompilationFailureException("Cannot collect the compile-time dependencies.", cause);
1595             }
1596         }
1597         if (exception != null) {
1598             if (exception instanceof IOException e) {
1599                 throw e;
1600             } else {
1601                 throw (RuntimeException) exception; // A ClassCastException here would be a bug in above loop.
1602             }
1603         }
1604         if (ProjectScope.MAIN.equals(compileScope.projectScope())) {
1605             String warning = dependencies.warningForFilenameBasedAutomodules().orElse(null);
1606             if (warning != null) { // Do not use Optional.ifPresent(…) for avoiding confusing source class name.
1607                 logger.warn(warning);
1608             }
1609         }
1610         return dependencies;
1611     }
1612 
1613     /**
1614      * Adds paths to the annotation processor dependencies. Paths are added to the list associated
1615      * to the {@link JavaPathType#PROCESSOR_CLASSES} entry of given map, which should be modifiable.
1616      *
1617      * @param addTo the modifiable map and lists where to append more paths to annotation processor dependencies
1618      * @throws MojoException if an error occurred while resolving the dependencies
1619      *
1620      * @deprecated Replaced by ordinary dependencies with {@code <type>} element set to
1621      * {@code processor}, {@code classpath-processor} or {@code modular-processor}.
1622      */
1623     @Deprecated(since = "4.0.0")
1624     @SuppressWarnings("UseSpecificCatch")
1625     final void resolveProcessorPathEntries(Map<PathType, Collection<Path>> addTo) throws MojoException {
1626         List<DependencyCoordinate> dependencies = annotationProcessorPaths;
1627         if (!isAbsent(dependencies)) {
1628             try {
1629                 List<org.apache.maven.api.DependencyCoordinates> coords = dependencies.stream()
1630                         .map((coord) -> coord.toCoordinate(project, session))
1631                         .toList();
1632                 Session sessionWithRepo =
1633                         session.withRemoteRepositories(projectManager.getRemoteProjectRepositories(project));
1634                 addTo.merge(
1635                         JavaPathType.PROCESSOR_CLASSES,
1636                         sessionWithRepo
1637                                 .getService(DependencyResolver.class)
1638                                 .resolve(DependencyResolverRequest.builder()
1639                                         .session(sessionWithRepo)
1640                                         .dependencies(coords)
1641                                         .managedDependencies(project.getManagedDependencies())
1642                                         .requestType(DependencyResolverRequest.RequestType.RESOLVE)
1643                                         .pathScope(PathScope.MAIN_RUNTIME)
1644                                         .build())
1645                                 .getPaths(),
1646                         (oldPaths, newPaths) -> {
1647                             oldPaths.addAll(newPaths);
1648                             return oldPaths;
1649                         });
1650             } catch (MojoException e) {
1651                 throw e;
1652             } catch (Exception e) {
1653                 throw new CompilationFailureException(
1654                         "Resolution of annotationProcessorPath dependencies failed: " + e.getMessage(), e);
1655             }
1656         }
1657     }
1658 
1659     /**
1660      * {@return whether an annotation processor seems to be present}
1661      *
1662      * @param dependencyTypes the type of dependencies, for checking if any of them is a processor path
1663      *
1664      * @see #incrementalCompilation
1665      */
1666     private boolean hasAnnotationProcessor(final Set<PathType> dependencyTypes) {
1667         if (isAbsent(proc)) {
1668             /*
1669              * If the `proc` parameter was not specified, its default value depends on the Java version.
1670              * It was "full" prior Java 23 and become "none if no other processor option" since Java 23.
1671              */
1672             if (isVersionEqualOrNewer(RELEASE_23)) {
1673                 if (isAbsent(annotationProcessors) && isAbsent(annotationProcessorPaths)) {
1674                     return dependencyTypes.contains(JavaPathType.PROCESSOR_CLASSES)
1675                             || dependencyTypes.contains(JavaPathType.PROCESSOR_MODULES);
1676                 }
1677             }
1678         } else if (proc.equalsIgnoreCase("none")) {
1679             return false;
1680         }
1681         return true;
1682     }
1683 
1684     /**
1685      * Ensures that the directory for generated sources exists, and adds it to the list of source directories
1686      * known to the project manager. This is used for adding the output of annotation processor.
1687      * The returned set is either empty or a singleton.
1688      *
1689      * @param dependencyTypes the type of dependencies, for checking if any of them is a processor path
1690      * @return the added directory in a singleton set, or an empty set if none
1691      * @throws IOException if the directory cannot be created
1692      */
1693     final Set<Path> addGeneratedSourceDirectory(final Set<PathType> dependencyTypes) throws IOException {
1694         Path generatedSourcesDirectory = getGeneratedSourcesDirectory();
1695         if (generatedSourcesDirectory == null) {
1696             return Set.of();
1697         }
1698         /*
1699          * Do not create an empty directory if this plugin is not going to generate new source files.
1700          * However, if a directory already exists, use it because maybe its content was generated by
1701          * another plugin executed before the compiler plugin.
1702          */
1703         if (hasAnnotationProcessor(dependencyTypes)) {
1704             // `createDirectories(Path)` does nothing if the directory already exists.
1705             generatedSourcesDirectory = Files.createDirectories(generatedSourcesDirectory);
1706         } else if (Files.notExists(generatedSourcesDirectory)) {
1707             return Set.of();
1708         }
1709         ProjectScope scope = compileScope.projectScope();
1710         projectManager.addSourceRoot(project, scope, Language.JAVA_FAMILY, generatedSourcesDirectory.toAbsolutePath());
1711         if (logger.isDebugEnabled()) {
1712             var sb = new StringBuilder("Adding \"")
1713                     .append(generatedSourcesDirectory)
1714                     .append("\" to ")
1715                     .append(scope.id())
1716                     .append("-compile source roots. New roots are:");
1717             projectManager
1718                     .getEnabledSourceRoots(project, scope, Language.JAVA_FAMILY)
1719                     .forEach((p) -> {
1720                         sb.append(System.lineSeparator()).append("    ").append(p.directory());
1721                     });
1722             logger.debug(sb.toString());
1723         }
1724         return Set.of(generatedSourcesDirectory);
1725     }
1726 
1727     /**
1728      * Formats the {@code <plugin>} block of code for configuring this plugin with the given option.
1729      *
1730      * @param mb the message builder where to format the block of code
1731      * @param option name of the XML sub-element of {@code <configuration>} for the option
1732      * @param value the option value, or {@code null} if none
1733      */
1734     private void writePlugin(MessageBuilder mb, String option, String value) {
1735         if (mavenCompilerPluginVersion == null) {
1736             try (InputStream is = AbstractCompilerMojo.class.getResourceAsStream("/" + JarFile.MANIFEST_NAME)) {
1737                 if (is != null) {
1738                     mavenCompilerPluginVersion =
1739                             new Manifest(is).getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
1740                 }
1741             } catch (IOException e) {
1742                 // noop
1743             }
1744             if (mavenCompilerPluginVersion == null) {
1745                 mavenCompilerPluginVersion = "";
1746             }
1747         }
1748         mb.a("    <plugin>").newline();
1749         mb.a("      <groupId>org.apache.maven.plugins</groupId>").newline();
1750         mb.a("      <artifactId>maven-compiler-plugin</artifactId>").newline();
1751         if (!isAbsent(mavenCompilerPluginVersion)) {
1752             mb.a("      <version>")
1753                     .a(mavenCompilerPluginVersion)
1754                     .a("</version>")
1755                     .newline();
1756         }
1757         mb.a("      <configuration>").newline();
1758         mb.a("        <").a(option).a('>').a(value).a("</").a(option).a('>').newline();
1759         mb.a("      </configuration>").newline();
1760         mb.a("    </plugin>").newline();
1761     }
1762 
1763     /**
1764      * Dumps the compiler options together with the list of source files into a debug file.
1765      * This is invoked in case of compilation failure, or if debug is enabled.
1766      *
1767      * <h4>Syntax</h4>
1768      * The arguments within a file can be separated by spaces or new line characters.
1769      * If a file name contains embedded spaces, then the whole file name must be between double quotation marks.
1770      * The -J options are not supported.
1771      *
1772      * @param executor the executor that compiled the classes
1773      * @param configuration options provided to the compiler
1774      * @param showBaseVersion whether the tip shown to user suggests the base Java release instead of the last one
1775      * @throws IOException if an error occurred while writing the debug file
1776      */
1777     private void writeDebugFile(final ToolExecutor executor, final Options configuration, final boolean showBaseVersion)
1778             throws IOException {
1779         final Path debugFilePath = getDebugFilePath();
1780         if (debugFilePath == null) {
1781             logger.warn("The <debugFileName> parameter should not be empty.");
1782             return;
1783         }
1784         final var commandLine = new StringBuilder("For trying to compile from the command-line, use:");
1785         Path dir = basedir;
1786         if (dir != null) { // Should never be null, but it has been observed with some Maven versions.
1787             try {
1788                 dir = Path.of(System.getProperty("user.dir")).relativize(dir);
1789             } catch (IllegalArgumentException e) {
1790                 // Ignore, keep the absolute path.
1791             }
1792             String chdir = dir.toString();
1793             if (!chdir.isEmpty()) {
1794                 boolean isWindows = (File.separatorChar == '\\');
1795                 commandLine
1796                         .append(System.lineSeparator())
1797                         .append("    ")
1798                         .append(isWindows ? "chdir " : "cd ")
1799                         .append(chdir);
1800             }
1801         }
1802         commandLine.append(System.lineSeparator()).append("    ").append(executable != null ? executable : compilerId);
1803         Path pathForRelease = debugFilePath;
1804         /*
1805          * The following loop will iterate over all groups of source files compiled for the same Java release,
1806          * starting with the base release. If the project is not a multi-release project, it iterates only once.
1807          * If the compilation failed, the loop will stop after the first Java release for which an error occurred.
1808          */
1809         final int count = executor.sourcesForDebugFile.size();
1810         final int indexToShow = showBaseVersion ? 0 : count - 1;
1811         for (int i = 0; i < count; i++) {
1812             final SourcesForRelease sources = executor.sourcesForDebugFile.get(i);
1813             if (i != 0) {
1814                 String version = sources.outputForRelease.getFileName().toString();
1815                 String filename = debugFilePath.getFileName().toString();
1816                 int s = filename.lastIndexOf('.');
1817                 if (s >= 0) {
1818                     filename = filename.substring(0, s) + '-' + version + filename.substring(s);
1819                 } else {
1820                     filename = filename + '-' + version;
1821                 }
1822                 pathForRelease = debugFilePath.resolveSibling(filename);
1823             }
1824             /*
1825              * Write the `javac.args` or `javac-<version>.args` file where `<version>` is the targeted Java release.
1826              * The `-J` options need to be on the command line rather than in the file, and therefore can be written
1827              * only once.
1828              */
1829             try (BufferedWriter out = Files.newBufferedWriter(pathForRelease)) {
1830                 configuration.setRelease(sources.getReleaseString());
1831                 configuration.format((i == indexToShow) ? commandLine : null, out);
1832                 for (Map.Entry<PathType, Collection<Path>> entry : sources.dependencySnapshot.entrySet()) {
1833                     writeOption(out, entry.getKey(), entry.getValue());
1834                 }
1835                 for (Map.Entry<String, Set<Path>> root : sources.roots.entrySet()) {
1836                     String moduleName = root.getKey();
1837                     writeOption(out, SourcePathType.valueOf(moduleName), root.getValue());
1838                 }
1839                 out.write("-d " + QUOTE);
1840                 out.write(relativize(sources.outputForRelease).toString());
1841                 out.write(QUOTE);
1842                 out.newLine();
1843                 for (final Path file : sources.files) {
1844                     out.write(QUOTE);
1845                     out.write(relativize(file).toString());
1846                     out.write(QUOTE);
1847                     out.newLine();
1848                 }
1849             }
1850         }
1851         Path path = relativize(showBaseVersion ? debugFilePath : pathForRelease);
1852         tipForCommandLineCompilation = commandLine.append(" @").append(path).toString();
1853     }
1854 
1855     /**
1856      * Writes the paths for the given Java compiler option.
1857      * Used for the {@code *.args} debug file, because files will be written between quotes.
1858      *
1859      * @param out where to write
1860      * @param type the type of path to write as a compiler option
1861      * @param files the paths associated to the specified option
1862      * @throws IOException in an error occurred while writing to the output
1863      */
1864     private void writeOption(BufferedWriter out, PathType type, Collection<Path> files) throws IOException {
1865         if (!files.isEmpty()) {
1866             files = files.stream().map(this::relativize).toList();
1867             String[] options = type.option(files);
1868             for (int i = 0; i < options.length; i++) {
1869                 String element = options[i];
1870                 if (i == 0) {
1871                     out.write(element);
1872                 } else {
1873                     out.write(' ');
1874                     out.write(QUOTE);
1875                     out.write(element);
1876                     out.write(QUOTE);
1877                 }
1878             }
1879             out.newLine();
1880         }
1881     }
1882 
1883     /**
1884      * Makes the given file relative to the base directory if the path is inside the project directory tree.
1885      * The check for the project directory tree (starting from the root of all sub-projects) is for avoiding
1886      * to relativize the paths to JAR files in the Maven local repository for example.
1887      *
1888      * @param  file  the path to make relative to the base directory
1889      * @return the given path, potentially relative to the base directory
1890      */
1891     private Path relativize(Path file) {
1892         final Path dir = basedir;
1893         if (dir != null) { // Should never be null, but it has been observed with some Maven versions.
1894             Path root = project.getRootDirectory();
1895             if (root != null && file.startsWith(root)) {
1896                 try {
1897                     file = dir.relativize(file);
1898                 } catch (IllegalArgumentException e) {
1899                     // Ignore, keep the absolute path.
1900                 }
1901             }
1902         }
1903         return file;
1904     }
1905 }