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