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  import java.util.Map.Entry;
33  
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugins.annotations.Component;
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.project.MavenProject;
42  import org.apache.maven.shared.utils.StringUtils;
43  import org.apache.maven.shared.utils.logging.MessageUtils;
44  import org.apache.maven.toolchain.Toolchain;
45  import org.apache.maven.toolchain.java.DefaultJavaToolChain;
46  import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
47  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
48  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
49  import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
50  import org.codehaus.plexus.languages.java.jpms.LocationManager;
51  import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
52  import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
53  import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult.ModuleNameSource;
54  
55  /**
56   * Compiles application sources
57   *
58   * @author <a href="mailto:jason@maven.org">Jason van Zyl </a>
59   * @since 2.0
60   */
61  @Mojo( name = "compile", defaultPhase = LifecyclePhase.COMPILE, threadSafe = true, 
62      requiresDependencyResolution = ResolutionScope.COMPILE )
63  public class CompilerMojo
64      extends AbstractCompilerMojo
65  {
66      /**
67       * The source directories containing the sources to be compiled.
68       */
69      @Parameter( defaultValue = "${project.compileSourceRoots}", readonly = true, required = true )
70      private List<String> compileSourceRoots;
71  
72      /**
73       * The directory for compiled classes.
74       */
75      @Parameter( defaultValue = "${project.build.outputDirectory}", required = true, readonly = true )
76      private File outputDirectory;
77  
78      /**
79       * Projects main artifact.
80       *
81       * @todo this is an export variable, really
82       */
83      @Parameter( defaultValue = "${project.artifact}", readonly = true, required = true )
84      private Artifact projectArtifact;
85  
86      /**
87       * A list of inclusion filters for the compiler.
88       */
89      @Parameter
90      private Set<String> includes = new HashSet<>();
91  
92      /**
93       * A list of exclusion filters for the compiler.
94       */
95      @Parameter
96      private Set<String> excludes = new HashSet<>();
97  
98      /**
99       * <p>
100      * Specify where to place generated source files created by annotation processing. Only applies to JDK 1.6+
101      * </p>
102      *
103      * @since 2.2
104      */
105     @Parameter( defaultValue = "${project.build.directory}/generated-sources/annotations" )
106     private File generatedSourcesDirectory;
107 
108     /**
109      * Set this to 'true' to bypass compilation of main sources. Its use is NOT RECOMMENDED, but quite convenient on
110      * occasion.
111      */
112     @Parameter( property = "maven.main.skip" )
113     private boolean skipMain;
114 
115     @Parameter( defaultValue = "${project.compileClasspathElements}", readonly = true, required = true )
116     private List<String> compilePath;
117 
118     /**
119      * When set to {@code true}, the classes will be placed in <code>META-INF/versions/${release}</code>
120      * The release value must be set, otherwise the plugin will fail. 
121      * 
122      * @since 3.7.1
123      */
124     @Parameter
125     private boolean multiReleaseOutput;
126 
127     @Component
128     private LocationManager locationManager;
129 
130     private List<String> classpathElements;
131 
132     private List<String> modulepathElements;
133     
134     private Map<String, JavaModuleDescriptor> pathElements;
135 
136     protected List<String> getCompileSourceRoots()
137     {
138         return compileSourceRoots;
139     }
140 
141     @Override
142     protected List<String> getClasspathElements()
143     {
144         return classpathElements;
145     }
146 
147     @Override
148     protected List<String> getModulepathElements()
149     {
150         return modulepathElements;
151     }
152 
153     @Override
154     protected Map<String, JavaModuleDescriptor> getPathElements()
155     {
156         return pathElements;
157     }
158     
159     protected File getOutputDirectory()
160     {
161         File dir;
162         if ( !multiReleaseOutput )
163         {
164             dir = outputDirectory;
165         }
166         else
167         {
168             dir = new File( outputDirectory, "META-INF/versions/" + release );
169         }
170         return dir;
171     }
172 
173     public void execute()
174         throws MojoExecutionException, CompilationFailureException
175     {
176         if ( skipMain )
177         {
178             getLog().info( "Not compiling main sources" );
179             return;
180         }
181         
182         if ( multiReleaseOutput && release == null )
183         {
184             throw new MojoExecutionException( "When using 'multiReleaseOutput' the release must be set" );
185         }
186 
187         super.execute();
188 
189         if ( outputDirectory.isDirectory() )
190         {
191             projectArtifact.setFile( outputDirectory );
192         }
193     }
194 
195     @Override
196     protected void preparePaths( Set<File> sourceFiles )
197     {
198         //assert compilePath != null;
199 
200         File moduleDescriptorPath = null;
201 
202         boolean hasModuleDescriptor = false;
203         for ( File sourceFile : sourceFiles )
204         {
205             if ( "module-info.java".equals( sourceFile.getName() ) )
206             {
207                 moduleDescriptorPath = sourceFile;
208                 hasModuleDescriptor = true;
209                 break;
210             }
211         }
212 
213         if ( hasModuleDescriptor )
214         {
215             // For now only allow named modules. Once we can create a graph with ASM we can specify exactly the modules
216             // and we can detect if auto modules are used. In that case, MavenProject.setFile() should not be used, so
217             // you cannot depend on this project and so it won't be distributed.
218 
219             modulepathElements = new ArrayList<>( compilePath.size() );
220             classpathElements = new ArrayList<>( compilePath.size() );
221             pathElements = new LinkedHashMap<>( compilePath.size() );
222 
223             ResolvePathsResult<File> resolvePathsResult;
224             try
225             {
226                 Collection<File> dependencyArtifacts = getCompileClasspathElements( getProject() );
227                 
228                 ResolvePathsRequest<File> request =
229                     ResolvePathsRequest.ofFiles( dependencyArtifacts )
230                                        .setMainModuleDescriptor( moduleDescriptorPath );
231                 
232                 Toolchain toolchain = getToolchain();
233                 if ( toolchain != null && toolchain instanceof DefaultJavaToolChain )
234                 {
235                     request.setJdkHome( new File( ( (DefaultJavaToolChain) toolchain ).getJavaHome() ) );
236                 }
237 
238                 resolvePathsResult = locationManager.resolvePaths( request );
239                 
240                 for ( Entry<File, Exception> pathException : resolvePathsResult.getPathExceptions().entrySet() )
241                 {
242                     Throwable cause = pathException.getValue();
243                     while ( cause.getCause() != null )
244                     {
245                         cause = cause.getCause();
246                     }
247                     String fileName = pathException.getKey().getName();
248                     getLog().warn( "Can't extract module name from " + fileName + ": " + cause.getMessage() );
249                 }
250                 
251                 JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor();
252 
253                 detectFilenameBasedAutomodules( resolvePathsResult, moduleDescriptor );
254                 
255                 for ( Map.Entry<File, JavaModuleDescriptor> entry : resolvePathsResult.getPathElements().entrySet() )
256                 {
257                     pathElements.put( entry.getKey().getPath(), entry.getValue() );
258                 }
259                 
260                 for ( File file : resolvePathsResult.getClasspathElements() )
261                 {
262                     classpathElements.add( file.getPath() );
263                     
264                     if ( multiReleaseOutput )
265                     {
266                         if ( compilerArgs == null )
267                         {
268                             compilerArgs = new ArrayList<>();
269                         }
270                         
271                         if ( getOutputDirectory().toPath().startsWith( file.getPath() ) )
272                         {
273                             compilerArgs.add( "--patch-module" );
274                             
275                             StringBuilder patchModuleValue = new StringBuilder( moduleDescriptor.name() )
276                                             .append( '=' )
277                                             .append( file.getPath() );
278                             
279                             compilerArgs.add( patchModuleValue.toString() );
280                         }
281                     }
282                 }
283                 
284                 for ( File file : resolvePathsResult.getModulepathElements().keySet() )
285                 {
286                     modulepathElements.add( file.getPath() );
287                 }
288                 
289                 if ( compilerArgs == null )
290                 {
291                     compilerArgs = new ArrayList<>();
292                 }
293                 compilerArgs.add( "--module-version" );
294                 compilerArgs.add( getProject().getVersion() );
295                 
296             }
297             catch ( IOException e )
298             {
299                 getLog().warn( e.getMessage() );
300             }
301         }
302         else
303         {
304             classpathElements = compilePath;
305             modulepathElements = Collections.emptyList();
306         }
307     }
308 
309     private void detectFilenameBasedAutomodules( final ResolvePathsResult<File> resolvePathsResult,
310             final JavaModuleDescriptor moduleDescriptor )
311     {
312         List<String> automodulesDetected = new ArrayList<>();
313         for ( Entry<File, ModuleNameSource> entry : resolvePathsResult.getModulepathElements().entrySet() )
314         {
315             if ( ModuleNameSource.FILENAME.equals( entry.getValue() ) )
316             {
317                 automodulesDetected.add( entry.getKey().getName() );
318             }
319         }
320 
321         if ( !automodulesDetected.isEmpty() )
322         {
323             final String message = "Required filename-based automodules detected: "
324                     +  automodulesDetected + ". "
325                     + "Please don't publish this project to a public artifact repository!";
326 
327             if ( moduleDescriptor.exports().isEmpty() )
328             {
329                 // application
330                 getLog().info( message );
331             }
332             else
333             {
334                 // library
335                 writeBoxedWarning( message );
336             }
337         }
338     }
339     
340     private List<File> getCompileClasspathElements( MavenProject project )
341     {
342         // 3 is outputFolder + 2 preserved for multirelease  
343         List<File> list = new ArrayList<>( project.getArtifacts().size() + 3 );
344 
345         if ( multiReleaseOutput )
346         {
347             File versionsFolder = new File( project.getBuild().getOutputDirectory(), "META-INF/versions" );
348             
349             // in reverse order
350             for ( int version = Integer.parseInt( getRelease() ) - 1; version >= 9 ; version-- )
351             {
352                 File versionSubFolder = new File( versionsFolder, String.valueOf( version ) );
353                 if ( versionSubFolder.exists() )
354                 {
355                     list.add( versionSubFolder );
356                 }
357             }
358         }
359 
360         list.add( new File( project.getBuild().getOutputDirectory() ) );
361 
362         for ( Artifact a : project.getArtifacts() )
363         {
364             list.add( a.getFile() );
365         }
366         return list;
367     }
368     
369     protected SourceInclusionScanner getSourceInclusionScanner( int staleMillis )
370     {
371         SourceInclusionScanner scanner;
372 
373         if ( includes.isEmpty() && excludes.isEmpty() )
374         {
375             scanner = new StaleSourceScanner( staleMillis );
376         }
377         else
378         {
379             if ( includes.isEmpty() )
380             {
381                 includes.add( "**/*.java" );
382             }
383             scanner = new StaleSourceScanner( staleMillis, includes, excludes );
384         }
385 
386         return scanner;
387     }
388 
389     protected SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding )
390     {
391         SourceInclusionScanner scanner;
392 
393         // it's not defined if we get the ending with or without the dot '.'
394         String defaultIncludePattern = "**/*" + ( inputFileEnding.startsWith( "." ) ? "" : "." ) + inputFileEnding;
395 
396         if ( includes.isEmpty() && excludes.isEmpty() )
397         {
398             includes = Collections.singleton( defaultIncludePattern );
399             scanner = new SimpleSourceInclusionScanner( includes, Collections.<String>emptySet() );
400         }
401         else
402         {
403             if ( includes.isEmpty() )
404             {
405                 includes.add( defaultIncludePattern );
406             }
407             scanner = new SimpleSourceInclusionScanner( includes, excludes );
408         }
409 
410         return scanner;
411     }
412 
413     protected String getSource()
414     {
415         return source;
416     }
417 
418     protected String getTarget()
419     {
420         return target;
421     }
422 
423     @Override
424     protected String getRelease()
425     {
426         return release;
427     }
428 
429     protected String getCompilerArgument()
430     {
431         return compilerArgument;
432     }
433 
434     protected Map<String, String> getCompilerArguments()
435     {
436         return compilerArguments;
437     }
438 
439     protected File getGeneratedSourcesDirectory()
440     {
441         return generatedSourcesDirectory;
442     }
443 
444     private void writeBoxedWarning( String message )
445     {
446         String line = StringUtils.repeat( "*", message.length() + 4 );
447         getLog().warn( line );
448         getLog().warn( "* " + MessageUtils.buffer().strong( message )  + " *" );
449         getLog().warn( line );
450     }
451 }