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