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