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 protected void preparePaths(Set<File> sourceFiles) {
235 File mainOutputDirectory = new File(getProject().getBuild().getOutputDirectory());
236
237 File mainModuleDescriptorClassFile = new File(mainOutputDirectory, "module-info.class");
238 JavaModuleDescriptor mainModuleDescriptor = null;
239
240 File testModuleDescriptorJavaFile = new File("module-info.java");
241 JavaModuleDescriptor testModuleDescriptor = null;
242
243
244 for (File sourceFile : sourceFiles) {
245
246 if ("module-info.java".equals(sourceFile.getName())) {
247 testModuleDescriptorJavaFile = sourceFile;
248 break;
249 }
250 }
251
252
253 if (mainModuleDescriptorClassFile.exists()) {
254 ResolvePathsResult<String> result;
255
256 try {
257 ResolvePathsRequest<String> request = ResolvePathsRequest.ofStrings(testPath)
258 .setIncludeStatic(true)
259 .setMainModuleDescriptor(mainModuleDescriptorClassFile.getAbsolutePath());
260
261 Toolchain toolchain = getToolchain();
262 if (toolchain instanceof DefaultJavaToolChain) {
263 request.setJdkHome(((DefaultJavaToolChain) toolchain).getJavaHome());
264 }
265
266 result = locationManager.resolvePaths(request);
267
268 for (Entry<String, Exception> pathException :
269 result.getPathExceptions().entrySet()) {
270 Throwable cause = pathException.getValue();
271 while (cause.getCause() != null) {
272 cause = cause.getCause();
273 }
274 String fileName =
275 Paths.get(pathException.getKey()).getFileName().toString();
276 getLog().warn("Can't extract module name from " + fileName + ": " + cause.getMessage());
277 }
278 } catch (IOException e) {
279 throw new RuntimeException(e);
280 }
281
282 mainModuleDescriptor = result.getMainModuleDescriptor();
283
284 pathElements = new LinkedHashMap<>(result.getPathElements().size());
285 pathElements.putAll(result.getPathElements());
286
287 modulepathElements = result.getModulepathElements().keySet();
288 classpathElements = result.getClasspathElements();
289 }
290
291
292 if (testModuleDescriptorJavaFile.exists()) {
293 ResolvePathsResult<String> result;
294
295 try {
296 ResolvePathsRequest<String> request = ResolvePathsRequest.ofStrings(testPath)
297 .setMainModuleDescriptor(testModuleDescriptorJavaFile.getAbsolutePath());
298
299 Toolchain toolchain = getToolchain();
300 if (toolchain instanceof DefaultJavaToolChain) {
301 request.setJdkHome(((DefaultJavaToolChain) toolchain).getJavaHome());
302 }
303
304 result = locationManager.resolvePaths(request);
305 } catch (IOException e) {
306 throw new RuntimeException(e);
307 }
308
309 testModuleDescriptor = result.getMainModuleDescriptor();
310 }
311
312 if (!useModulePath) {
313 pathElements = Collections.emptyMap();
314 modulepathElements = Collections.emptyList();
315 classpathElements = testPath;
316 return;
317 }
318 if (StringUtils.isNotEmpty(getRelease())) {
319 if (Integer.parseInt(getRelease()) < 9) {
320 pathElements = Collections.emptyMap();
321 modulepathElements = Collections.emptyList();
322 classpathElements = testPath;
323 return;
324 }
325 } else if (Double.parseDouble(getTarget()) < Double.parseDouble(MODULE_INFO_TARGET)) {
326 pathElements = Collections.emptyMap();
327 modulepathElements = Collections.emptyList();
328 classpathElements = testPath;
329 return;
330 }
331
332 if (testModuleDescriptor != null) {
333 modulepathElements = testPath;
334 classpathElements = Collections.emptyList();
335
336 if (mainModuleDescriptor != null) {
337 if (getLog().isDebugEnabled()) {
338 getLog().debug("Main and test module descriptors exist:");
339 getLog().debug(" main module = " + mainModuleDescriptor.name());
340 getLog().debug(" test module = " + testModuleDescriptor.name());
341 }
342
343 if (testModuleDescriptor.name().equals(mainModuleDescriptor.name())) {
344 if (compilerArgs == null) {
345 compilerArgs = new ArrayList<>();
346 }
347 compilerArgs.add("--patch-module");
348
349 StringBuilder patchModuleValue = new StringBuilder();
350 patchModuleValue.append(testModuleDescriptor.name());
351 patchModuleValue.append('=');
352
353 for (String root : getProject().getCompileSourceRoots()) {
354 if (Files.exists(Paths.get(root))) {
355 patchModuleValue.append(root).append(PS);
356 }
357 }
358
359 compilerArgs.add(patchModuleValue.toString());
360 } else {
361 getLog().debug("Black-box testing - all is ready to compile");
362 }
363 } else {
364
365 if (!mainOutputDirectory.exists()) {
366 return;
367 }
368
369
370
371
372 throw new UnsupportedOperationException(
373 "Can't compile test sources " + "when main sources are missing a module descriptor");
374 }
375 } else {
376 if (mainModuleDescriptor != null) {
377 if (compilerArgs == null) {
378 compilerArgs = new ArrayList<>();
379 }
380 compilerArgs.add("--patch-module");
381
382 StringBuilder patchModuleValue = new StringBuilder(mainModuleDescriptor.name())
383 .append('=')
384 .append(mainOutputDirectory)
385 .append(PS);
386 for (String root : compileSourceRoots) {
387 patchModuleValue.append(root).append(PS);
388 }
389
390 compilerArgs.add(patchModuleValue.toString());
391
392 compilerArgs.add("--add-reads");
393 compilerArgs.add(mainModuleDescriptor.name() + "=ALL-UNNAMED");
394 } else {
395 modulepathElements = Collections.emptyList();
396 classpathElements = testPath;
397 }
398 }
399 }
400
401 protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis) {
402 SourceInclusionScanner scanner;
403
404 if (testIncludes.isEmpty() && testExcludes.isEmpty() && testIncrementalExcludes.isEmpty()) {
405 scanner = new StaleSourceScanner(staleMillis);
406 } else {
407 if (testIncludes.isEmpty()) {
408 testIncludes.add("**/*.java");
409 }
410 Set<String> excludesIncr = new HashSet<>(testExcludes);
411 excludesIncr.addAll(this.testIncrementalExcludes);
412 scanner = new StaleSourceScanner(staleMillis, testIncludes, excludesIncr);
413 }
414
415 return scanner;
416 }
417
418 protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding) {
419 SourceInclusionScanner scanner;
420
421
422 String defaultIncludePattern = "**/*" + (inputFileEnding.startsWith(".") ? "" : ".") + inputFileEnding;
423
424 if (testIncludes.isEmpty() && testExcludes.isEmpty() && testIncrementalExcludes.isEmpty()) {
425 testIncludes = Collections.singleton(defaultIncludePattern);
426 scanner = new SimpleSourceInclusionScanner(testIncludes, Collections.emptySet());
427 } else {
428 if (testIncludes.isEmpty()) {
429 testIncludes.add(defaultIncludePattern);
430 }
431 Set<String> excludesIncr = new HashSet<>(testExcludes);
432 excludesIncr.addAll(this.testIncrementalExcludes);
433 scanner = new SimpleSourceInclusionScanner(testIncludes, excludesIncr);
434 }
435
436 return scanner;
437 }
438
439 protected String getSource() {
440 return testSource == null ? source : testSource;
441 }
442
443 protected String getTarget() {
444 return testTarget == null ? target : testTarget;
445 }
446
447 @Override
448 protected String getRelease() {
449 return testRelease == null ? release : testRelease;
450 }
451
452 protected String getCompilerArgument() {
453 return testCompilerArgument == null ? compilerArgument : testCompilerArgument;
454 }
455
456 protected Map<String, String> getCompilerArguments() {
457 return testCompilerArguments == null ? compilerArguments : testCompilerArguments;
458 }
459
460 protected File getGeneratedSourcesDirectory() {
461 return generatedTestSourcesDirectory;
462 }
463
464 @Override
465 protected String getDebugFileName() {
466 return debugFileName;
467 }
468
469 @Override
470 protected boolean isTestCompile() {
471 return true;
472 }
473
474 @Override
475 protected Set<String> getIncludes() {
476 return testIncludes;
477 }
478
479 @Override
480 protected Set<String> getExcludes() {
481 return testExcludes;
482 }
483 }