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