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