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.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.compiler.module.ModuleInfoParser;
34  import org.apache.maven.plugins.annotations.Component;
35  import org.apache.maven.plugins.annotations.LifecyclePhase;
36  import org.apache.maven.plugins.annotations.Mojo;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.plugins.annotations.ResolutionScope;
39  import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
40  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
41  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
42  
43  /**
44   * Compiles application test sources.
45   *
46   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
47   * @version $Id: TestCompilerMojo.html 1015972 2017-07-25 23:24:13Z olamy $
48   * @since 2.0
49   */
50  @Mojo( name = "testCompile", defaultPhase = LifecyclePhase.TEST_COMPILE, threadSafe = true,
51                  requiresDependencyResolution = ResolutionScope.TEST )
52  public class TestCompilerMojo
53      extends AbstractCompilerMojo
54  {
55      protected static final String PS = System.getProperty( "path.separator" );
56      
57      /**
58       * Set this to 'true' to bypass compilation of test sources.
59       * Its use is NOT RECOMMENDED, but quite convenient on occasion.
60       */
61      @Parameter ( property = "maven.test.skip" )
62      private boolean skip;
63  
64      /**
65       * The source directories containing the test-source to be compiled.
66       */
67      @Parameter ( defaultValue = "${project.testCompileSourceRoots}", readonly = true, required = true )
68      private List<String> compileSourceRoots;
69  
70      /**
71       * The directory where compiled test classes go.
72       */
73      @Parameter ( defaultValue = "${project.build.testOutputDirectory}", required = true, readonly = true )
74      private File outputDirectory;
75  
76      /**
77       * A list of inclusion filters for the compiler.
78       */
79      @Parameter
80      private Set<String> testIncludes = new HashSet<String>();
81  
82      /**
83       * A list of exclusion filters for the compiler.
84       */
85      @Parameter
86      private Set<String> testExcludes = new HashSet<String>();
87  
88      /**
89       * The -source argument for the test Java compiler.
90       *
91       * @since 2.1
92       */
93      @Parameter ( property = "maven.compiler.testSource" )
94      private String testSource;
95  
96      /**
97       * The -target argument for the test Java compiler.
98       *
99       * @since 2.1
100      */
101     @Parameter ( property = "maven.compiler.testTarget" )
102     private String testTarget;
103 
104     /**
105      * the -release argument for the test Java compiler
106      * 
107      * @since 3.6
108      */
109     @Parameter ( property = "maven.compiler.testRelease" )
110     private String testRelease;
111 
112     /**
113      * <p>
114      * Sets the arguments to be passed to test compiler (prepending a dash) if fork is set to true.
115      * </p>
116      * <p>
117      * This is because the list of valid arguments passed to a Java compiler
118      * varies based on the compiler version.
119      * </p>
120      *
121      * @since 2.1
122      */
123     @Parameter
124     private Map<String, String> testCompilerArguments;
125 
126     /**
127      * <p>
128      * Sets the unformatted argument string to be passed to test compiler if fork is set to true.
129      * </p>
130      * <p>
131      * This is because the list of valid arguments passed to a Java compiler
132      * varies based on the compiler version.
133      * </p>
134      *
135      * @since 2.1
136      */
137     @Parameter
138     private String testCompilerArgument;
139 
140     /**
141      * <p>
142      * Specify where to place generated source files created by annotation processing.
143      * Only applies to JDK 1.6+
144      * </p>
145      *
146      * @since 2.2
147      */
148     @Parameter ( defaultValue = "${project.build.directory}/generated-test-sources/test-annotations" )
149     private File generatedTestSourcesDirectory;
150 
151     @Parameter( defaultValue = "${project.compileClasspathElements}", readonly = true )
152     private List<String> compilePath;
153 
154     @Parameter( defaultValue = "${project.testClasspathElements}", readonly = true )
155     private List<String> testPath;
156     
157     @Component
158     private Map<String, ModuleInfoParser> moduleInfoParsers;
159 
160     private List<String> classpathElements;
161 
162     private List<String> modulepathElements;
163 
164     public void execute()
165         throws MojoExecutionException, CompilationFailureException
166     {
167         if ( skip )
168         {
169             getLog().info( "Not compiling test sources" );
170             return;
171         }
172         super.execute();
173     }
174 
175     protected List<String> getCompileSourceRoots()
176     {
177         return compileSourceRoots;
178     }
179 
180     protected List<String> getClasspathElements()
181     {
182         return classpathElements;
183     }
184     
185     @Override
186     protected List<String> getModulepathElements()
187     {
188         return modulepathElements;
189     }
190 
191     protected File getOutputDirectory()
192     {
193         return outputDirectory;
194     }
195 
196     @Override
197     protected void preparePaths( Set<File> sourceFiles )
198     {
199         File mainOutputDirectory = new File( getProject().getBuild().getOutputDirectory() );
200 
201         boolean hasMainModuleDescriptor = new File( mainOutputDirectory, "module-info.class" ).exists();
202         
203         boolean hasTestModuleDescriptor = false;
204         
205         // Go through the source files to respect includes/excludes 
206         for ( File sourceFile : sourceFiles )
207         {
208             // @todo verify if it is the root of a sourcedirectory?
209             if ( "module-info.java".equals( sourceFile.getName() ) ) 
210             {
211                 hasTestModuleDescriptor = true;
212                 break;
213             }
214         }
215         
216         List<String> testScopedElements = new ArrayList<String>( testPath );
217         testScopedElements.removeAll( compilePath );
218         
219         if ( release != null )
220         {
221             if ( Integer.valueOf( release ) < 9 )
222             {
223                 modulepathElements = Collections.emptyList();
224                 classpathElements = testPath;
225                 return;
226             }
227         }
228         else if ( Double.valueOf( getTarget() ) < Double.valueOf( MODULE_INFO_TARGET ) )
229         {
230             modulepathElements = Collections.emptyList();
231             classpathElements = testPath;
232             return;
233         }
234             
235         if ( hasTestModuleDescriptor )
236         {
237             modulepathElements = testPath;
238             classpathElements = Collections.emptyList();
239 
240             if ( hasMainModuleDescriptor )
241             {
242                 // maybe some extra analysis required
243             }
244             else
245             {
246                 // very odd
247                 // Means that main sources must be compiled with -modulesource and -Xmodule:<moduleName>
248                 // However, this has a huge impact since you can't simply use it as a classpathEntry 
249                 // due to extra folder in between
250                 throw new UnsupportedOperationException( "Can't compile test sources "
251                     + "when main sources are missing a module descriptor" );
252             }
253         }
254         else
255         {
256             if ( hasMainModuleDescriptor )
257             {
258                 modulepathElements = compilePath;
259                 classpathElements = testScopedElements;
260                 if ( compilerArgs == null )
261                 {
262                     compilerArgs = new ArrayList<String>();
263                 }
264                 
265                 String moduleName = getModuleName( mainOutputDirectory );
266                 
267                 compilerArgs.add( "--patch-module" );
268                 
269                 StringBuilder addReadsValue = new StringBuilder( moduleName )
270                                 .append( '=' )
271                                 .append( mainOutputDirectory )
272                                 .append( PS );
273                 for ( String root : compileSourceRoots )
274                 {
275                     addReadsValue.append( root ).append( PS );
276                 }
277                 
278                 compilerArgs.add( addReadsValue.toString() );
279                 
280                 compilerArgs.add( "--add-reads" );
281                 compilerArgs.add( moduleName + "=ALL-UNNAMED" );
282             }
283             else
284             {
285                 modulepathElements = Collections.emptyList();
286                 classpathElements = testPath;
287             }
288         }
289     }
290 
291     private String getModuleName( File mainOutputDirectory )
292     {
293         String moduleName = null;
294         
295         Map<String, Exception> exceptionMap = new LinkedHashMap<String, Exception>( moduleInfoParsers.size() ); 
296         
297         List<String> parserKeys = Arrays.asList( "reflect", "asm" );
298         
299         // The class format is still changing, for that reason provide multiple strategies to parse module-info 
300         for ( String parserKey: parserKeys )
301         {
302             ModuleInfoParser parser = moduleInfoParsers.get( parserKey );
303 
304             try
305             {
306                 moduleName = parser.getModuleDescriptor( mainOutputDirectory  ).name();
307                 
308                 if ( moduleName != null )
309                 {
310                     break;
311                 }
312             }
313             catch ( Exception e )
314             {
315                 exceptionMap.put( parserKey, e );
316             }
317         }
318         
319         if ( moduleName == null )
320         {
321             getLog().error( "Failed to parse module-info:" );
322             
323             for ( Map.Entry<String, Exception> exception : exceptionMap.entrySet() )
324             {
325                 getLog().error( "With " + exception.getKey() + ": " + exception.getValue().getMessage() );
326             }
327             
328             throw new RuntimeException( "Failed to parse module-info" );
329         }
330         return moduleName;
331     }
332 
333     protected SourceInclusionScanner getSourceInclusionScanner( int staleMillis )
334     {
335         SourceInclusionScanner scanner;
336 
337         if ( testIncludes.isEmpty() && testExcludes.isEmpty() )
338         {
339             scanner = new StaleSourceScanner( staleMillis );
340         }
341         else
342         {
343             if ( testIncludes.isEmpty() )
344             {
345                 testIncludes.add( "**/*.java" );
346             }
347             scanner = new StaleSourceScanner( staleMillis, testIncludes, testExcludes );
348         }
349 
350         return scanner;
351     }
352 
353     protected SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding )
354     {
355         SourceInclusionScanner scanner;
356 
357         // it's not defined if we get the ending with or without the dot '.'
358         String defaultIncludePattern = "**/*" + ( inputFileEnding.startsWith( "." ) ? "" : "." ) + inputFileEnding;
359 
360         if ( testIncludes.isEmpty() && testExcludes.isEmpty() )
361         {
362             testIncludes = Collections.singleton( defaultIncludePattern );
363             scanner = new SimpleSourceInclusionScanner( testIncludes, Collections.<String>emptySet() );
364         }
365         else
366         {
367             if ( testIncludes.isEmpty() )
368             {
369                 testIncludes.add( defaultIncludePattern );
370             }
371             scanner = new SimpleSourceInclusionScanner( testIncludes, testExcludes );
372         }
373 
374         return scanner;
375     }
376 
377     protected String getSource()
378     {
379         return testSource == null ? source : testSource;
380     }
381 
382     protected String getTarget()
383     {
384         return testTarget == null ? target : testTarget;
385     }
386     
387     @Override
388     protected String getRelease()
389     {
390         return testRelease == null ? release : testRelease;
391     }
392 
393     protected String getCompilerArgument()
394     {
395         return testCompilerArgument == null ? compilerArgument : testCompilerArgument;
396     }
397 
398     protected Map<String, String> getCompilerArguments()
399     {
400         return testCompilerArguments == null ? compilerArguments : testCompilerArguments;
401     }
402 
403     protected File getGeneratedSourcesDirectory()
404     {
405         return generatedTestSourcesDirectory;
406     }
407 
408     @Override
409     protected boolean isTestCompile()
410     {
411         return true;
412     }
413 
414 }