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