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