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