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 }