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