View Javadoc
1   package org.apache.maven.plugin.compiler;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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   * Compiles application test sources.
54   *
55   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
56   * @version $Id$
57   * @since 2.0
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       * Set this to 'true' to bypass compilation of test sources.
66       * Its use is NOT RECOMMENDED, but quite convenient on occasion.
67       */
68      @Parameter ( property = "maven.test.skip" )
69      private boolean skip;
70  
71      /**
72       * The source directories containing the test-source to be compiled.
73       */
74      @Parameter ( defaultValue = "${project.testCompileSourceRoots}", readonly = true, required = true )
75      private List<String> compileSourceRoots;
76  
77      /**
78       * The directory where compiled test classes go.
79       */
80      @Parameter ( defaultValue = "${project.build.testOutputDirectory}", required = true, readonly = true )
81      private File outputDirectory;
82  
83      /**
84       * A list of inclusion filters for the compiler.
85       */
86      @Parameter
87      private Set<String> testIncludes = new HashSet<String>();
88  
89      /**
90       * A list of exclusion filters for the compiler.
91       */
92      @Parameter
93      private Set<String> testExcludes = new HashSet<String>();
94  
95      /**
96       * The -source argument for the test Java compiler.
97       *
98       * @since 2.1
99       */
100     @Parameter ( property = "maven.compiler.testSource" )
101     private String testSource;
102 
103     /**
104      * The -target argument for the test Java compiler.
105      *
106      * @since 2.1
107      */
108     @Parameter ( property = "maven.compiler.testTarget" )
109     private String testTarget;
110 
111     /**
112      * the -release argument for the test Java compiler
113      * 
114      * @since 3.6
115      */
116     @Parameter ( property = "maven.compiler.testRelease" )
117     private String testRelease;
118 
119     /**
120      * <p>
121      * Sets the arguments to be passed to test compiler (prepending a dash) if fork is set to true.
122      * </p>
123      * <p>
124      * This is because the list of valid arguments passed to a Java compiler
125      * varies based on the compiler version.
126      * </p>
127      *
128      * @since 2.1
129      */
130     @Parameter
131     private Map<String, String> testCompilerArguments;
132 
133     /**
134      * <p>
135      * Sets the unformatted argument string to be passed to test compiler if fork is set to true.
136      * </p>
137      * <p>
138      * This is because the list of valid arguments passed to a Java compiler
139      * varies based on the compiler version.
140      * </p>
141      *
142      * @since 2.1
143      */
144     @Parameter
145     private String testCompilerArgument;
146 
147     /**
148      * <p>
149      * Specify where to place generated source files created by annotation processing.
150      * Only applies to JDK 1.6+
151      * </p>
152      *
153      * @since 2.2
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         // Go through the source files to respect includes/excludes
223         for ( File sourceFile : sourceFiles )
224         {
225             // @todo verify if it is the root of a sourcedirectory?
226             if ( "module-info.java".equals( sourceFile.getName() ) ) 
227             {
228                 testModuleDescriptorJavaFile = sourceFile;
229                 break;
230             }
231         }
232 
233         // Get additional information from the main module descriptor, if available
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         // Get additional information from the test module descriptor, if available
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                 // No main binaries available? Means we're a test-only project.
366                 if ( !mainOutputDirectory.exists() )
367                 {
368                     return;
369                 }
370                 // very odd
371                 // Means that main sources must be compiled with -modulesource and -Xmodule:<moduleName>
372                 // However, this has a huge impact since you can't simply use it as a classpathEntry 
373                 // due to extra folder in between
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         // it's not defined if we get the ending with or without the dot '.'
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 }