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