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 java.io.IOException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Optional;
33 import java.util.Set;
34 import java.util.stream.Collectors;
35 import java.util.stream.Stream;
36
37 import org.apache.maven.api.JavaToolchain;
38 import org.apache.maven.api.PathScope;
39 import org.apache.maven.api.ProjectScope;
40 import org.apache.maven.api.Toolchain;
41 import org.apache.maven.api.plugin.MojoException;
42 import org.apache.maven.api.plugin.annotations.Mojo;
43 import org.apache.maven.api.plugin.annotations.Parameter;
44 import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
45 import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
46 import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
47 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
48 import org.codehaus.plexus.languages.java.jpms.LocationManager;
49 import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
50 import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
51
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 @Parameter(property = "maven.test.skip")
69 private boolean skip;
70
71
72
73
74 @Parameter
75 private List<String> compileSourceRoots;
76
77
78
79
80 @Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
81 private Path mainOutputDirectory;
82
83
84
85
86
87
88
89
90
91 @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true)
92 private Path outputDirectory;
93
94
95
96
97 @Parameter
98 private Set<String> testIncludes = new HashSet<>();
99
100
101
102
103 @Parameter
104 private Set<String> testExcludes = new HashSet<>();
105
106
107
108
109
110 @Parameter
111 private Set<String> testIncrementalExcludes = new HashSet<>();
112
113
114
115
116
117
118 @Parameter(property = "maven.compiler.testSource")
119 private String testSource;
120
121
122
123
124
125
126 @Parameter(property = "maven.compiler.testTarget")
127 private String testTarget;
128
129
130
131
132
133
134 @Parameter(property = "maven.compiler.testRelease")
135 private String testRelease;
136
137
138
139
140
141
142
143
144
145
146
147
148 @Parameter
149 private Map<String, String> testCompilerArguments;
150
151
152
153
154
155
156
157
158
159
160
161
162 @Parameter
163 private String testCompilerArgument;
164
165
166
167
168
169
170
171
172
173 @Parameter(defaultValue = "${project.build.directory}/generated-test-sources/test-annotations")
174 private Path generatedTestSourcesDirectory;
175
176
177
178
179
180
181
182
183
184
185 @Parameter(defaultValue = "true")
186 private boolean useModulePath;
187
188 @Parameter
189 private List<String> testPath;
190
191
192
193
194
195 @Parameter(defaultValue = "javac-test")
196 private String debugFileName;
197
198 final LocationManager locationManager = new LocationManager();
199
200 private Map<String, JavaModuleDescriptor> pathElements;
201
202 private List<String> classpathElements;
203
204 private List<String> modulepathElements;
205
206 public void execute() throws MojoException {
207 if (skip) {
208 getLog().info("Not compiling test sources");
209 return;
210 }
211 super.execute();
212 }
213
214 protected List<Path> getCompileSourceRoots() {
215 if (compileSourceRoots == null || compileSourceRoots.isEmpty()) {
216 return projectManager.getCompileSourceRoots(getProject(), ProjectScope.TEST);
217 } else {
218 return compileSourceRoots.stream().map(Paths::get).collect(Collectors.toList());
219 }
220 }
221
222 @Override
223 protected Map<String, JavaModuleDescriptor> getPathElements() {
224 return pathElements;
225 }
226
227 protected List<String> getClasspathElements() {
228 return classpathElements;
229 }
230
231 @Override
232 protected List<String> getModulepathElements() {
233 return modulepathElements;
234 }
235
236 protected Path getOutputDirectory() {
237 return outputDirectory;
238 }
239
240 @Override
241 protected void preparePaths(Set<Path> sourceFiles) {
242 List<String> testPath = this.testPath;
243 if (testPath == null) {
244 Stream<String> s1 = Stream.of(outputDirectory.toString(), mainOutputDirectory.toString());
245 Stream<String> s2 = session.resolveDependencies(getProject(), PathScope.TEST_COMPILE).stream()
246 .map(Path::toString);
247 testPath = Stream.concat(s1, s2).collect(Collectors.toList());
248 }
249
250 Path mainOutputDirectory = Paths.get(getProject().getBuild().getOutputDirectory());
251
252 Path mainModuleDescriptorClassFile = mainOutputDirectory.resolve("module-info.class");
253 JavaModuleDescriptor mainModuleDescriptor = null;
254
255 Path testModuleDescriptorJavaFile = Paths.get("module-info.java");
256 JavaModuleDescriptor testModuleDescriptor = null;
257
258
259 for (Path sourceFile : sourceFiles) {
260
261 if ("module-info.java".equals(sourceFile.getFileName().toString())) {
262 testModuleDescriptorJavaFile = sourceFile;
263 break;
264 }
265 }
266
267
268 if (Files.exists(mainModuleDescriptorClassFile)) {
269 ResolvePathsResult<String> result;
270
271 try {
272 ResolvePathsRequest<String> request = ResolvePathsRequest.ofStrings(testPath)
273 .setIncludeStatic(true)
274 .setMainModuleDescriptor(
275 mainModuleDescriptorClassFile.toAbsolutePath().toString());
276
277 Optional<Toolchain> toolchain = getToolchain();
278 if (toolchain.isPresent() && toolchain.get() instanceof JavaToolchain) {
279 request.setJdkHome(((JavaToolchain) toolchain.get()).getJavaHome());
280 }
281
282 result = locationManager.resolvePaths(request);
283
284 for (Entry<String, Exception> pathException :
285 result.getPathExceptions().entrySet()) {
286 Throwable cause = pathException.getValue();
287 while (cause.getCause() != null) {
288 cause = cause.getCause();
289 }
290 String fileName =
291 Paths.get(pathException.getKey()).getFileName().toString();
292 getLog().warn("Can't extract module name from " + fileName + ": " + cause.getMessage());
293 }
294 } catch (IOException e) {
295 throw new RuntimeException(e);
296 }
297
298 mainModuleDescriptor = result.getMainModuleDescriptor();
299
300 pathElements = new LinkedHashMap<>(result.getPathElements().size());
301 pathElements.putAll(result.getPathElements());
302
303 modulepathElements = new ArrayList<>(result.getModulepathElements().keySet());
304 classpathElements = new ArrayList<>(result.getClasspathElements());
305 }
306
307
308 if (Files.exists(testModuleDescriptorJavaFile)) {
309 ResolvePathsResult<String> result;
310
311 try {
312 ResolvePathsRequest<String> request = ResolvePathsRequest.ofStrings(testPath)
313 .setMainModuleDescriptor(
314 testModuleDescriptorJavaFile.toAbsolutePath().toString());
315
316 Optional<Toolchain> toolchain = getToolchain();
317 if (toolchain.isPresent() && toolchain.get() instanceof JavaToolchain) {
318 request.setJdkHome(((JavaToolchain) toolchain.get()).getJavaHome());
319 }
320
321 result = locationManager.resolvePaths(request);
322 } catch (IOException e) {
323 throw new RuntimeException(e);
324 }
325
326 testModuleDescriptor = result.getMainModuleDescriptor();
327 }
328
329 if (release != null && !release.isEmpty()) {
330 if (Integer.parseInt(release) < 9) {
331 pathElements = Collections.emptyMap();
332 modulepathElements = Collections.emptyList();
333 classpathElements = testPath;
334 return;
335 }
336 } else if (Double.parseDouble(getTarget()) < Double.parseDouble(MODULE_INFO_TARGET)) {
337 pathElements = Collections.emptyMap();
338 modulepathElements = Collections.emptyList();
339 classpathElements = testPath;
340 return;
341 }
342
343 if (testModuleDescriptor != null) {
344 modulepathElements = testPath;
345 classpathElements = Collections.emptyList();
346
347 if (mainModuleDescriptor != null) {
348 if (getLog().isDebugEnabled()) {
349 getLog().debug("Main and test module descriptors exist:");
350 getLog().debug(" main module = " + mainModuleDescriptor.name());
351 getLog().debug(" test module = " + testModuleDescriptor.name());
352 }
353
354 if (testModuleDescriptor.name().equals(mainModuleDescriptor.name())) {
355 if (compilerArgs == null) {
356 compilerArgs = new ArrayList<>();
357 }
358 compilerArgs.add("--patch-module");
359
360 StringBuilder patchModuleValue = new StringBuilder();
361 patchModuleValue.append(testModuleDescriptor.name());
362 patchModuleValue.append('=');
363
364 for (Path root : projectManager.getCompileSourceRoots(getProject(), ProjectScope.MAIN)) {
365 if (Files.exists(root)) {
366 patchModuleValue.append(root).append(PS);
367 }
368 }
369
370 compilerArgs.add(patchModuleValue.toString());
371 } else {
372 getLog().debug("Black-box testing - all is ready to compile");
373 }
374 } else {
375
376 if (!Files.exists(mainOutputDirectory)) {
377 return;
378 }
379
380
381
382
383 throw new UnsupportedOperationException(
384 "Can't compile test sources " + "when main sources are missing a module descriptor");
385 }
386 } else {
387 if (mainModuleDescriptor != null) {
388 if (compilerArgs == null) {
389 compilerArgs = new ArrayList<>();
390 }
391 compilerArgs.add("--patch-module");
392
393 StringBuilder patchModuleValue = new StringBuilder(mainModuleDescriptor.name())
394 .append('=')
395 .append(mainOutputDirectory)
396 .append(PS);
397 for (Path root : getCompileSourceRoots()) {
398 patchModuleValue.append(root).append(PS);
399 }
400
401 compilerArgs.add(patchModuleValue.toString());
402
403 compilerArgs.add("--add-reads");
404 compilerArgs.add(mainModuleDescriptor.name() + "=ALL-UNNAMED");
405 } else {
406 modulepathElements = Collections.emptyList();
407 classpathElements = testPath;
408 }
409 }
410 }
411
412 protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis) {
413 SourceInclusionScanner scanner;
414
415 if (testIncludes.isEmpty() && testExcludes.isEmpty() && testIncrementalExcludes.isEmpty()) {
416 scanner = new StaleSourceScanner(staleMillis);
417 } else {
418 if (testIncludes.isEmpty()) {
419 testIncludes.add("**/*.java");
420 }
421 Set<String> excludesIncr = new HashSet<>(testExcludes);
422 excludesIncr.addAll(this.testIncrementalExcludes);
423 scanner = new StaleSourceScanner(staleMillis, testIncludes, excludesIncr);
424 }
425
426 return scanner;
427 }
428
429 protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding) {
430 SourceInclusionScanner scanner;
431
432
433 String defaultIncludePattern = "**/*" + (inputFileEnding.startsWith(".") ? "" : ".") + inputFileEnding;
434
435 if (testIncludes.isEmpty() && testExcludes.isEmpty() && testIncrementalExcludes.isEmpty()) {
436 testIncludes = Collections.singleton(defaultIncludePattern);
437 scanner = new SimpleSourceInclusionScanner(testIncludes, Collections.emptySet());
438 } else {
439 if (testIncludes.isEmpty()) {
440 testIncludes.add(defaultIncludePattern);
441 }
442 Set<String> excludesIncr = new HashSet<>(testExcludes);
443 excludesIncr.addAll(this.testIncrementalExcludes);
444 scanner = new SimpleSourceInclusionScanner(testIncludes, excludesIncr);
445 }
446
447 return scanner;
448 }
449
450 protected String getSource() {
451 return testSource == null ? source : testSource;
452 }
453
454 protected String getTarget() {
455 return testTarget == null ? target : testTarget;
456 }
457
458 @Override
459 protected String getRelease() {
460 return testRelease == null ? release : testRelease;
461 }
462
463 protected String getCompilerArgument() {
464 return testCompilerArgument == null ? compilerArgument : testCompilerArgument;
465 }
466
467 protected Path getGeneratedSourcesDirectory() {
468 return generatedTestSourcesDirectory;
469 }
470
471 @Override
472 protected String getDebugFileName() {
473 return debugFileName;
474 }
475
476 @Override
477 protected boolean isTestCompile() {
478 return true;
479 }
480
481 @Override
482 protected Set<String> getIncludes() {
483 return testIncludes;
484 }
485
486 @Override
487 protected Set<String> getExcludes() {
488 return testExcludes;
489 }
490 }