1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.plugin.compiler;
20  
21  import javax.tools.JavaCompiler;
22  import javax.tools.OptionChecker;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.lang.module.ModuleDescriptor;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.util.ArrayList;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.StringJoiner;
36  
37  import org.apache.maven.api.Dependency;
38  import org.apache.maven.api.JavaPathType;
39  import org.apache.maven.api.PathType;
40  import org.apache.maven.api.ProjectScope;
41  import org.apache.maven.api.annotations.Nonnull;
42  import org.apache.maven.api.annotations.Nullable;
43  import org.apache.maven.api.plugin.MojoException;
44  import org.apache.maven.api.plugin.annotations.Mojo;
45  import org.apache.maven.api.plugin.annotations.Parameter;
46  import org.apache.maven.api.services.DependencyResolverResult;
47  import org.apache.maven.api.services.MessageBuilder;
48  
49  import static org.apache.maven.plugin.compiler.SourceDirectory.CLASS_FILE_SUFFIX;
50  import static org.apache.maven.plugin.compiler.SourceDirectory.JAVA_FILE_SUFFIX;
51  import static org.apache.maven.plugin.compiler.SourceDirectory.MODULE_INFO;
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  @Mojo(name = "testCompile", defaultPhase = "test-compile")
63  public class TestCompilerMojo extends AbstractCompilerMojo {
64      
65  
66  
67  
68  
69  
70      @Parameter(property = "maven.test.skip")
71      protected boolean skip;
72  
73      
74  
75  
76  
77  
78      @Parameter
79      protected List<String> compileSourceRoots;
80  
81      
82  
83  
84  
85  
86  
87      @Parameter(defaultValue = "${project.build.directory}/generated-test-sources/test-annotations")
88      protected Path generatedTestSourcesDirectory;
89  
90      
91  
92  
93  
94  
95      @Parameter
96      protected Set<String> testIncludes;
97  
98      
99  
100 
101 
102 
103     @Parameter
104     protected Set<String> testExcludes;
105 
106     
107 
108 
109 
110 
111 
112 
113     @Parameter
114     protected Set<String> testIncrementalExcludes;
115 
116     
117 
118 
119 
120 
121 
122     @Parameter(property = "maven.compiler.testSource")
123     protected String testSource;
124 
125     
126 
127 
128 
129 
130 
131     @Parameter(property = "maven.compiler.testTarget")
132     protected String testTarget;
133 
134     
135 
136 
137 
138 
139 
140     @Parameter(property = "maven.compiler.testRelease")
141     protected String testRelease;
142 
143     
144 
145 
146 
147 
148 
149 
150 
151     @Parameter
152     protected List<String> testCompilerArgs;
153 
154     
155 
156 
157 
158 
159 
160 
161     @Parameter
162     @Deprecated(since = "4.0.0")
163     protected Map<String, String> testCompilerArguments;
164 
165     
166 
167 
168 
169 
170 
171 
172 
173 
174 
175     @Parameter
176     @Deprecated(since = "4.0.0")
177     protected String testCompilerArgument;
178 
179     
180 
181 
182 
183 
184 
185 
186     @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true)
187     protected Path outputDirectory;
188 
189     
190 
191 
192 
193 
194 
195 
196 
197     @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
198     protected Path mainOutputDirectory;
199 
200     
201 
202 
203 
204 
205 
206 
207 
208 
209     @Deprecated(since = "4.0.0")
210     @Parameter(defaultValue = "true")
211     protected boolean useModulePath = true;
212 
213     
214 
215 
216 
217 
218 
219 
220 
221     private String moduleName;
222 
223     
224 
225 
226 
227 
228     private boolean hasTestModuleInfo;
229 
230     
231 
232 
233 
234     private boolean overwriteMainModuleInfo;
235 
236     
237 
238 
239 
240 
241 
242 
243 
244 
245     @Parameter(defaultValue = "javac-test.args")
246     protected String debugFileName;
247 
248     
249 
250 
251     public TestCompilerMojo() {
252         super(true);
253     }
254 
255     
256 
257 
258 
259 
260     @Override
261     public void execute() throws MojoException {
262         if (skip) {
263             logger.info("Not compiling test sources");
264             return;
265         }
266         super.execute();
267     }
268 
269     
270 
271 
272 
273 
274 
275     @Override
276     @SuppressWarnings("deprecation")
277     protected Options acceptParameters(final OptionChecker compiler) {
278         Options compilerConfiguration = super.acceptParameters(compiler);
279         compilerConfiguration.addUnchecked(
280                 testCompilerArgs == null || testCompilerArgs.isEmpty() ? compilerArgs : testCompilerArgs);
281         if (testCompilerArguments != null) {
282             for (Map.Entry<String, String> entry : testCompilerArguments.entrySet()) {
283                 compilerConfiguration.addUnchecked(List.of(entry.getKey(), entry.getValue()));
284             }
285         }
286         compilerConfiguration.addUnchecked(testCompilerArgument == null ? compilerArgument : testCompilerArgument);
287         return compilerConfiguration;
288     }
289 
290     
291 
292 
293     @Nonnull
294     @Override
295     protected List<Path> getCompileSourceRoots() {
296         if (compileSourceRoots == null || compileSourceRoots.isEmpty()) {
297             return projectManager.getCompileSourceRoots(project, ProjectScope.TEST);
298         } else {
299             return compileSourceRoots.stream().map(Paths::get).toList();
300         }
301     }
302 
303     
304 
305 
306     @Nullable
307     @Override
308     protected Path getGeneratedSourcesDirectory() {
309         return generatedTestSourcesDirectory;
310     }
311 
312     
313 
314 
315     @Override
316     protected Set<String> getIncludes() {
317         return (testIncludes != null) ? testIncludes : Set.of();
318     }
319 
320     
321 
322 
323     @Override
324     protected Set<String> getExcludes() {
325         return (testExcludes != null) ? testExcludes : Set.of();
326     }
327 
328     
329 
330 
331     @Override
332     protected Set<String> getIncrementalExcludes() {
333         return (testIncrementalExcludes != null) ? testIncrementalExcludes : Set.of();
334     }
335 
336     
337 
338 
339 
340 
341 
342     @Nullable
343     @Override
344     protected String getSource() {
345         return testSource == null ? source : testSource;
346     }
347 
348     
349 
350 
351 
352 
353 
354     @Nullable
355     @Override
356     protected String getTarget() {
357         return testTarget == null ? target : testTarget;
358     }
359 
360     
361 
362 
363 
364 
365 
366     @Nullable
367     @Override
368     protected String getRelease() {
369         return testRelease == null ? release : testRelease;
370     }
371 
372     
373 
374 
375     @Nonnull
376     @Override
377     protected Path getOutputDirectory() {
378         return outputDirectory;
379     }
380 
381     
382 
383 
384     @Nullable
385     @Override
386     protected String getDebugFileName() {
387         return debugFileName;
388     }
389 
390     
391 
392 
393 
394 
395 
396     private String getMainModuleName() throws IOException {
397         if (moduleName == null) {
398             Path file = mainOutputDirectory.resolve(MODULE_INFO + CLASS_FILE_SUFFIX);
399             if (Files.isRegularFile(file)) {
400                 try (InputStream in = Files.newInputStream(file)) {
401                     moduleName = ModuleDescriptor.read(in).name();
402                 }
403             } else {
404                 moduleName = "";
405             }
406         }
407         return moduleName;
408     }
409 
410     
411 
412 
413 
414 
415 
416     private String getTestModuleName(List<SourceDirectory> compileSourceRoots) throws IOException {
417         for (SourceDirectory directory : compileSourceRoots) {
418             if (directory.moduleName != null) {
419                 return directory.moduleName;
420             }
421             String name = parseModuleInfoName(directory.getModuleInfo().orElse(null));
422             if (name != null) {
423                 return name;
424             }
425         }
426         return null;
427     }
428 
429     
430 
431 
432 
433 
434 
435 
436     @Override
437     final boolean hasModuleDeclaration(final List<SourceDirectory> roots) throws IOException {
438         hasTestModuleInfo = super.hasModuleDeclaration(roots);
439         if (hasTestModuleInfo) {
440             MessageBuilder message = messageBuilderFactory.builder();
441             message.a("Overwriting the ")
442                     .warning(MODULE_INFO + JAVA_FILE_SUFFIX)
443                     .a(" file in the test directory is deprecated. Use ")
444                     .info("--add-reads")
445                     .a(", ")
446                     .info("--add-modules")
447                     .a(" and related options instead.");
448             logger.warn(message.toString());
449             if (SUPPORT_LEGACY) {
450                 return useModulePath;
451             }
452         }
453         return useModulePath && !getMainModuleName().isEmpty();
454     }
455 
456     
457 
458 
459 
460 
461 
462     @Override
463     protected void addImplicitDependencies(Map<PathType, List<Path>> addTo, boolean hasModuleDeclaration) {
464         var pathType = hasModuleDeclaration ? JavaPathType.MODULES : JavaPathType.CLASSES;
465         if (Files.exists(mainOutputDirectory)) {
466             addTo.computeIfAbsent(pathType, (key) -> new ArrayList<>()).add(mainOutputDirectory);
467         }
468     }
469 
470     
471 
472 
473 
474 
475 
476 
477 
478 
479 
480     @Override
481     final void addSourceDirectories(Map<PathType, List<Path>> addTo, List<SourceDirectory> compileSourceRoots)
482             throws IOException {
483         for (SourceDirectory dir : compileSourceRoots) {
484             String moduleToPatch = dir.moduleName;
485             if (moduleToPatch == null) {
486                 moduleToPatch = getMainModuleName();
487                 if (moduleToPatch.isEmpty()) {
488                     continue; 
489                 }
490                 if (SUPPORT_LEGACY) {
491                     String testModuleName = getTestModuleName(compileSourceRoots);
492                     if (testModuleName != null) {
493                         overwriteMainModuleInfo = testModuleName.equals(getMainModuleName());
494                         if (!overwriteMainModuleInfo) {
495                             continue; 
496                         }
497                     }
498                 }
499             }
500             addTo.computeIfAbsent(JavaPathType.patchModule(moduleToPatch), (key) -> new ArrayList<>())
501                     .add(dir.root);
502         }
503     }
504 
505     
506 
507 
508 
509 
510 
511 
512 
513     @Override
514     @SuppressWarnings({"checkstyle:MissingSwitchDefault", "fallthrough"})
515     protected void addModuleOptions(DependencyResolverResult dependencies, Options addTo) throws IOException {
516         if (SUPPORT_LEGACY && useModulePath && hasTestModuleInfo) {
517             
518 
519 
520 
521             return;
522         }
523         final var done = new HashSet<String>(); 
524         final var addModules = new StringJoiner(",");
525         StringJoiner addReads = null;
526         boolean hasUnnamed = false;
527         for (Map.Entry<Dependency, Path> entry : dependencies.getDependencies().entrySet()) {
528             boolean compile = false;
529             switch (entry.getKey().getScope()) {
530                 case TEST:
531                 case TEST_ONLY:
532                     compile = true;
533                     
534                 case TEST_RUNTIME:
535                     if (compile) {
536                         
537                         if (addReads == null) {
538                             addReads = new StringJoiner(",", getMainModuleName() + "=", "");
539                         }
540                     }
541                     Path path = entry.getValue();
542                     String name = dependencies.getModuleName(path).orElse(null);
543                     if (name == null) {
544                         hasUnnamed = true;
545                     } else if (done.add(name)) {
546                         addModules.add(name);
547                         if (compile) {
548                             addReads.add(name);
549                         }
550                         
551 
552 
553 
554 
555                         dependencies.getModuleDescriptor(path).ifPresent((descriptor) -> {
556                             for (ModuleDescriptor.Requires r : descriptor.requires()) {
557                                 done.add(r.name());
558                             }
559                         });
560                     }
561                     break;
562             }
563         }
564         if (!done.isEmpty()) {
565             addTo.addIfNonBlank("--add-modules", addModules.toString());
566         }
567         if (addReads != null) {
568             if (hasUnnamed) {
569                 addReads.add("ALL-UNNAMED");
570             }
571             addTo.addIfNonBlank("--add-reads", addReads.toString());
572         }
573     }
574 
575     
576 
577 
578 
579 
580 
581     @Override
582     final CompilationTaskSources[] toCompilationTasks(final SourcesForRelease unit) {
583         if (!(SUPPORT_LEGACY && useModulePath && hasTestModuleInfo && overwriteMainModuleInfo)) {
584             return super.toCompilationTasks(unit);
585         }
586         CompilationTaskSources moduleInfo = null;
587         final List<Path> files = unit.files;
588         for (int i = files.size(); --i >= 0; ) {
589             if (SourceDirectory.isModuleInfoSource(files.get(i))) {
590                 moduleInfo = new CompilationTaskSources(List.of(files.remove(i)));
591                 if (files.isEmpty()) {
592                     return new CompilationTaskSources[] {moduleInfo};
593                 }
594                 break;
595             }
596         }
597         var task = new CompilationTaskSources(files) {
598             
599 
600 
601 
602 
603             @Override
604             boolean compile(JavaCompiler.CompilationTask task) throws IOException {
605                 try (unit) {
606                     unit.substituteModuleInfos(mainOutputDirectory, outputDirectory);
607                     return super.compile(task);
608                 }
609             }
610         };
611         if (moduleInfo != null) {
612             return new CompilationTaskSources[] {moduleInfo, task};
613         } else {
614             return new CompilationTaskSources[] {task};
615         }
616     }
617 }