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.tools.DiagnosticListener; 22 import javax.tools.JavaFileObject; 23 import javax.tools.OptionChecker; 24 25 import java.io.IOException; 26 import java.nio.file.Files; 27 import java.nio.file.Path; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Set; 31 32 import org.apache.maven.api.PathScope; 33 import org.apache.maven.api.annotations.Nonnull; 34 import org.apache.maven.api.annotations.Nullable; 35 import org.apache.maven.api.plugin.MojoException; 36 import org.apache.maven.api.plugin.annotations.Mojo; 37 import org.apache.maven.api.plugin.annotations.Parameter; 38 import org.apache.maven.api.services.MessageBuilder; 39 40 import static org.apache.maven.plugin.compiler.SourceDirectory.CLASS_FILE_SUFFIX; 41 import static org.apache.maven.plugin.compiler.SourceDirectory.JAVA_FILE_SUFFIX; 42 import static org.apache.maven.plugin.compiler.SourceDirectory.MODULE_INFO; 43 44 /** 45 * Compiles application test sources. 46 * Each instance shall be used only once, then discarded. 47 * 48 * @author <a href="mailto:jason@maven.org">Jason van Zyl</a> 49 * @author Martin Desruisseaux 50 * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html">javac Command</a> 51 * @since 2.0 52 */ 53 @Mojo(name = "testCompile", defaultPhase = "test-compile") 54 public class TestCompilerMojo extends AbstractCompilerMojo { 55 /** 56 * Whether to bypass compilation of test sources. 57 * Its use is not recommended, but quite convenient on occasion. 58 * 59 * @see CompilerMojo#skipMain 60 */ 61 @Parameter(property = "maven.test.skip") 62 protected boolean skip; 63 64 /** 65 * Specify where to place generated source files created by annotation processing. 66 * 67 * @see CompilerMojo#generatedSourcesDirectory 68 * @since 2.2 69 */ 70 @Parameter(defaultValue = "${project.build.directory}/generated-test-sources/test-annotations") 71 protected Path generatedTestSourcesDirectory; 72 73 /** 74 * A set of inclusion filters for the compiler. 75 * 76 * @see CompilerMojo#includes 77 */ 78 @Parameter 79 protected Set<String> testIncludes; 80 81 /** 82 * A set of exclusion filters for the compiler. 83 * 84 * @see CompilerMojo#excludes 85 */ 86 @Parameter 87 protected Set<String> testExcludes; 88 89 /** 90 * A set of exclusion filters for the incremental calculation. 91 * Updated files, if excluded by this filter, will not cause the project to be rebuilt. 92 * 93 * @see CompilerMojo#incrementalExcludes 94 * @since 3.11 95 */ 96 @Parameter 97 protected Set<String> testIncrementalExcludes; 98 99 /** 100 * The {@code --source} argument for the test Java compiler. 101 * 102 * @see CompilerMojo#source 103 * @since 2.1 104 */ 105 @Parameter(property = "maven.compiler.testSource") 106 protected String testSource; 107 108 /** 109 * The {@code --target} argument for the test Java compiler. 110 * 111 * @see CompilerMojo#target 112 * @since 2.1 113 */ 114 @Parameter(property = "maven.compiler.testTarget") 115 protected String testTarget; 116 117 /** 118 * the {@code --release} argument for the test Java compiler 119 * 120 * @see CompilerMojo#release 121 * @since 3.6 122 */ 123 @Parameter(property = "maven.compiler.testRelease") 124 protected String testRelease; 125 126 /** 127 * The arguments to be passed to the test compiler. 128 * If this parameter is specified, it replaces {@link #compilerArgs}. 129 * Otherwise, the {@code compilerArgs} parameter is used. 130 * 131 * @see CompilerMojo#compilerArgs 132 * @since 4.0.0 133 */ 134 @Parameter 135 protected List<String> testCompilerArgs; 136 137 /** 138 * The arguments to be passed to test compiler. 139 * 140 * @deprecated Replaced by {@link #testCompilerArgs} for consistency with the main phase. 141 * 142 * @since 2.1 143 */ 144 @Parameter 145 @Deprecated(since = "4.0.0") 146 protected Map<String, String> testCompilerArguments; 147 148 /** 149 * The single argument string to be passed to the test compiler. 150 * If this parameter is specified, it replaces {@link #compilerArgument}. 151 * Otherwise, the {@code compilerArgument} parameter is used. 152 * 153 * @deprecated Use {@link #testCompilerArgs} instead. 154 * 155 * @see CompilerMojo#compilerArgument 156 * @since 2.1 157 */ 158 @Parameter 159 @Deprecated(since = "4.0.0") 160 protected String testCompilerArgument; 161 162 /** 163 * The directory where compiled test classes go. 164 * This parameter should only be modified in special cases. 165 * See the {@link CompilerMojo#outputDirectory} for more information. 166 * 167 * @see CompilerMojo#outputDirectory 168 * @see #getOutputDirectory() 169 */ 170 @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true) 171 protected Path outputDirectory; 172 173 /** 174 * The output directory of the main classes. 175 * This directory will be added to the class-path or module-path. 176 * Its value should be the same as {@link CompilerMojo#outputDirectory}. 177 * 178 * @see CompilerMojo#outputDirectory 179 */ 180 @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true) 181 protected Path mainOutputDirectory; 182 183 /** 184 * Whether to place the main classes on the module path when {@code module-info} is present. 185 * When {@code false}, always places the main classes on the class path. 186 * Dependencies are also placed on the class-path, unless their type is {@code module-jar}. 187 * 188 * @since 3.11 189 * 190 * @deprecated Use {@code "claspath-jar"} dependency type instead, and avoid {@code module-info.java} in tests. 191 */ 192 @Deprecated(since = "4.0.0") 193 @Parameter(defaultValue = "true") 194 protected boolean useModulePath = true; 195 196 /** 197 * Whether a {@code module-info.java} file is defined in the test sources. 198 * In such case, it has precedence over the {@code module-info.java} in main sources. 199 * This is defined for compatibility with Maven 3, but not recommended. 200 * 201 * <p>This field exists in this class only for transferring this information 202 * to {@link ToolExecutorForTest#hasTestModuleInfo}, which is the class that 203 * needs this information.</p> 204 * 205 * @deprecated Avoid {@code module-info.java} in tests. 206 */ 207 @Deprecated(since = "4.0.0") 208 transient boolean hasTestModuleInfo; 209 210 /** 211 * Whether a {@code module-info.java} file is defined in the main sources. 212 */ 213 private transient boolean hasMainModuleInfo; 214 215 /** 216 * The file where to dump the command-line when debug is activated or when the compilation failed. 217 * For example, if the value is {@code "javac-test"}, then the Java compiler can be launched 218 * from the command-line by typing {@code javac @target/javac-test.args}. 219 * The debug file will contain the compiler options together with the list of source files to compile. 220 * 221 * <p>By default, this debug file is written only if the compilation of test code failed. 222 * The writing of the debug files can be forced by setting the {@link #verbose} flag to {@code true} 223 * or by specifying the {@code --verbose} option to Maven on the command-line.</p> 224 * 225 * @see CompilerMojo#debugFileName 226 * @since 3.10.0 227 */ 228 @Parameter(defaultValue = "javac-test.args") 229 protected String debugFileName; 230 231 /** 232 * Creates a new compiler <abbr>MOJO</abbr> for the tests. 233 */ 234 public TestCompilerMojo() { 235 super(PathScope.TEST_COMPILE); 236 } 237 238 /** 239 * Runs the Java compiler on the test source code. 240 * If {@link #skip} is {@code true}, then this method logs a message and does nothing else. 241 * Otherwise, this method executes the steps described in the method of the parent class. 242 * 243 * @throws MojoException if the compiler cannot be run. 244 */ 245 @Override 246 public void execute() throws MojoException { 247 if (skip) { 248 logger.info("Not compiling test sources"); 249 return; 250 } 251 super.execute(); 252 } 253 254 /** 255 * Parses the parameters declared in the <abbr>MOJO</abbr>. 256 * 257 * @param compiler the tools to use for verifying the validity of options 258 * @return the options after validation 259 */ 260 @Override 261 @SuppressWarnings("deprecation") 262 public Options parseParameters(final OptionChecker compiler) { 263 Options configuration = super.parseParameters(compiler); 264 configuration.addUnchecked(isAbsent(testCompilerArgs) ? compilerArgs : testCompilerArgs); 265 if (testCompilerArguments != null) { 266 for (Map.Entry<String, String> entry : testCompilerArguments.entrySet()) { 267 configuration.addUnchecked(List.of(entry.getKey(), entry.getValue())); 268 } 269 } 270 configuration.addUnchecked(testCompilerArgument == null ? compilerArgument : testCompilerArgument); 271 return configuration; 272 } 273 274 /** 275 * {@return the path where to place generated source files created by annotation processing on the test classes} 276 */ 277 @Nullable 278 @Override 279 protected Path getGeneratedSourcesDirectory() { 280 return generatedTestSourcesDirectory; 281 } 282 283 /** 284 * {@return the inclusion filters for the compiler, or an empty set for all Java source files} 285 */ 286 @Override 287 protected Set<String> getIncludes() { 288 return (testIncludes != null) ? testIncludes : Set.of(); 289 } 290 291 /** 292 * {@return the exclusion filters for the compiler, or an empty set if none} 293 */ 294 @Override 295 protected Set<String> getExcludes() { 296 return (testExcludes != null) ? testExcludes : Set.of(); 297 } 298 299 /** 300 * {@return the exclusion filters for the incremental calculation, or an empty set if none} 301 */ 302 @Override 303 protected Set<String> getIncrementalExcludes() { 304 return (testIncrementalExcludes != null) ? testIncrementalExcludes : Set.of(); 305 } 306 307 /** 308 * If a different source version has been specified for the tests, returns that version. 309 * Otherwise returns the same source version as the main code. 310 * 311 * @return the {@code --source} argument for the Java compiler 312 */ 313 @Nullable 314 @Override 315 protected String getSource() { 316 return testSource == null ? source : testSource; 317 } 318 319 /** 320 * If a different target version has been specified for the tests, returns that version. 321 * Otherwise returns the same target version as the main code. 322 * 323 * @return the {@code --target} argument for the Java compiler 324 */ 325 @Nullable 326 @Override 327 protected String getTarget() { 328 return testTarget == null ? target : testTarget; 329 } 330 331 /** 332 * If a different release version has been specified for the tests, returns that version. 333 * Otherwise returns the same release version as the main code. 334 * 335 * @return the {@code --release} argument for the Java compiler 336 */ 337 @Nullable 338 @Override 339 protected String getRelease() { 340 return testRelease == null ? release : testRelease; 341 } 342 343 /** 344 * {@return the destination directory for test class files} 345 */ 346 @Nonnull 347 @Override 348 protected Path getOutputDirectory() { 349 return outputDirectory; 350 } 351 352 /** 353 * {@return the file where to dump the command-line when debug is activated or when the compilation failed} 354 * 355 * @see #debugFileName 356 */ 357 @Nullable 358 @Override 359 protected String getDebugFileName() { 360 return debugFileName; 361 } 362 363 /** 364 * {@return the module name found in the package hierarchy of given sources} 365 * We have to parse the source instead of the {@code module-info.class} file 366 * because the classes may not have been compiled yet. This is not reliable, 367 * but the use of package hierarchy for modular project should be avoided in 368 * Maven 4. 369 * 370 * @deprecated Declare modules in {@code <source>} elements instead. 371 */ 372 @Deprecated(since = "4.0.0") 373 final String moduleNameFromPackageHierarchy(List<SourceDirectory> compileSourceRoots) throws IOException { 374 for (SourceDirectory directory : compileSourceRoots) { 375 if (directory.moduleName == null) { 376 String name = parseModuleInfoName(directory.getModuleInfo().orElse(null)); 377 if (name != null) { 378 return name; 379 } 380 } 381 } 382 return null; 383 } 384 385 /** 386 * {@return whether the project has at least one {@code module-info.class} file} 387 * The {@code module-info.class} should be located in the main source code. 388 * However, this method checks also in the test source code for compatibility with Maven 3, 389 * but this practice is deprecated. 390 * 391 * @param roots root directories of the source files of the test classes to compile 392 * @throws IOException if this method needed to read a module descriptor and failed 393 */ 394 @Override 395 final boolean hasModuleDeclaration(final List<SourceDirectory> roots) throws IOException { 396 for (SourceDirectory root : roots) { 397 hasMainModuleInfo |= root.moduleName != null; 398 hasTestModuleInfo |= root.getModuleInfo().isPresent(); 399 if (hasMainModuleInfo & hasTestModuleInfo) { 400 break; 401 } 402 } 403 if (hasTestModuleInfo) { 404 MessageBuilder message = messageBuilderFactory.builder(); 405 message.a("Overwriting the ") 406 .warning(MODULE_INFO + JAVA_FILE_SUFFIX) 407 .a(" file in the test directory is deprecated. Use ") 408 .info(ModuleInfoPatch.FILENAME) 409 .a(" instead."); 410 logger.warn(message.toString()); 411 if (SUPPORT_LEGACY) { 412 return useModulePath; 413 } 414 } 415 return useModulePath && hasMainModuleInfo; 416 } 417 418 /** 419 * Creates a new task for compiling the test classes. 420 * 421 * @param listener where to send compilation warnings, or {@code null} for the Maven logger 422 * @throws MojoException if this method identifies an invalid parameter in this <abbr>MOJO</abbr> 423 * @return the task to execute for compiling the tests using the configuration in this <abbr>MOJO</abbr> 424 * @throws IOException if an error occurred while creating the output directory or scanning the source directories 425 */ 426 @Override 427 public ToolExecutor createExecutor(DiagnosticListener<? super JavaFileObject> listener) throws IOException { 428 Path mainModulePath = mainOutputDirectory.resolve(MODULE_INFO + CLASS_FILE_SUFFIX); 429 if (Files.isRegularFile(mainModulePath)) { 430 hasMainModuleInfo = true; 431 } else { 432 mainModulePath = null; 433 } 434 return new ToolExecutorForTest(this, listener, mainModulePath); 435 } 436 }