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