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 * <configuration> 293 * <annotationProcessorPaths> 294 * <path> 295 * <groupId>org.sample</groupId> 296 * <artifactId>sample-annotation-processor</artifactId> 297 * <version>1.2.3</version> <!-- Optional - taken from dependency management if not specified --> 298 * <!-- Optionally exclude transitive dependencies --> 299 * <exclusions> 300 * <exclusion> 301 * <groupId>org.sample</groupId> 302 * <artifactId>sample-dependency</artifactId> 303 * </exclusion> 304 * </exclusions> 305 * </path> 306 * <!-- ... more ... --> 307 * </annotationProcessorPaths> 308 * </configuration> 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 * <configuration> 615 * <jdkToolchain> 616 * <version>11</version> 617 * </jdkToolchain> 618 * ... 619 * </configuration> 620 * 621 * <configuration> 622 * <jdkToolchain> 623 * <version>1.8</version> 624 * <vendor>zulu</vendor> 625 * </jdkToolchain> 626 * ... 627 * </configuration> 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 }