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     final LocationManager locationManager = new LocationManager();
133 
134     private List<String> classpathElements;
135 
136     private List<String> modulepathElements;
137     
138     private Map<String, JavaModuleDescriptor> pathElements;
139 
140     protected List<String> getCompileSourceRoots()
141     {
142         return compileSourceRoots;
143     }
144 
145     @Override
146     protected List<String> getClasspathElements()
147     {
148         return classpathElements;
149     }
150 
151     @Override
152     protected List<String> getModulepathElements()
153     {
154         return modulepathElements;
155     }
156 
157     @Override
158     protected Map<String, JavaModuleDescriptor> getPathElements()
159     {
160         return pathElements;
161     }
162     
163     protected File getOutputDirectory()
164     {
165         File dir;
166         if ( !multiReleaseOutput )
167         {
168             dir = outputDirectory;
169         }
170         else
171         {
172             dir = new File( outputDirectory, "META-INF/versions/" + release );
173         }
174         return dir;
175     }
176 
177     public void execute()
178         throws MojoExecutionException, CompilationFailureException
179     {
180         if ( skipMain )
181         {
182             getLog().info( "Not compiling main sources" );
183             return;
184         }
185         
186         if ( multiReleaseOutput && release == null )
187         {
188             throw new MojoExecutionException( "When using 'multiReleaseOutput' the release must be set" );
189         }
190 
191         super.execute();
192 
193         if ( outputDirectory.isDirectory() )
194         {
195             projectArtifact.setFile( outputDirectory );
196         }
197     }
198 
199     @Override
200     protected void preparePaths( Set<File> sourceFiles )
201     {
202         //assert compilePath != null;
203 
204         File moduleDescriptorPath = null;
205 
206         boolean hasModuleDescriptor = false;
207         for ( File sourceFile : sourceFiles )
208         {
209             if ( "module-info.java".equals( sourceFile.getName() ) )
210             {
211                 moduleDescriptorPath = sourceFile;
212                 hasModuleDescriptor = true;
213                 break;
214             }
215         }
216 
217         if ( hasModuleDescriptor )
218         {
219             // For now only allow named modules. Once we can create a graph with ASM we can specify exactly the modules
220             // and we can detect if auto modules are used. In that case, MavenProject.setFile() should not be used, so
221             // you cannot depend on this project and so it won't be distributed.
222 
223             modulepathElements = new ArrayList<>( compilePath.size() );
224             classpathElements = new ArrayList<>( compilePath.size() );
225             pathElements = new LinkedHashMap<>( compilePath.size() );
226 
227             ResolvePathsResult<File> resolvePathsResult;
228             try
229             {
230                 Collection<File> dependencyArtifacts = getCompileClasspathElements( getProject() );
231                 
232                 ResolvePathsRequest<File> request =
233                     ResolvePathsRequest.ofFiles( dependencyArtifacts )
234                                        .setMainModuleDescriptor( moduleDescriptorPath );
235                 
236                 Toolchain toolchain = getToolchain();
237                 if ( toolchain instanceof DefaultJavaToolChain )
238                 {
239                     request.setJdkHome( new File( ( (DefaultJavaToolChain) toolchain ).getJavaHome() ) );
240                 }
241 
242                 resolvePathsResult = locationManager.resolvePaths( request );
243                 
244                 for ( Entry<File, Exception> pathException : resolvePathsResult.getPathExceptions().entrySet() )
245                 {
246                     Throwable cause = pathException.getValue();
247                     while ( cause.getCause() != null )
248                     {
249                         cause = cause.getCause();
250                     }
251                     String fileName = pathException.getKey().getName();
252                     getLog().warn( "Can't extract module name from " + fileName + ": " + cause.getMessage() );
253                 }
254                 
255                 JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor();
256 
257                 detectFilenameBasedAutomodules( resolvePathsResult, moduleDescriptor );
258                 
259                 for ( Map.Entry<File, JavaModuleDescriptor> entry : resolvePathsResult.getPathElements().entrySet() )
260                 {
261                     pathElements.put( entry.getKey().getPath(), entry.getValue() );
262                 }
263 
264                 if ( compilerArgs == null )
265                 {
266                     compilerArgs = new ArrayList<>();
267                 }
268 
269                 for ( File file : resolvePathsResult.getClasspathElements() )
270                 {
271                     classpathElements.add( file.getPath() );
272                     
273                     if ( multiReleaseOutput )
274                     {
275                         if ( getOutputDirectory().toPath().startsWith( file.getPath() ) )
276                         {
277                             compilerArgs.add( "--patch-module" );
278                             compilerArgs.add( String.format( "%s=%s", moduleDescriptor.name(), file.getPath() ) );
279                         }
280                     }
281                 }
282                 
283                 for ( File file : resolvePathsResult.getModulepathElements().keySet() )
284                 {
285                     modulepathElements.add( file.getPath() );
286                 }
287                 
288                 compilerArgs.add( "--module-version" );
289                 compilerArgs.add( getProject().getVersion() );
290                 
291             }
292             catch ( IOException e )
293             {
294                 getLog().warn( e.getMessage() );
295             }
296         }
297         else
298         {
299             classpathElements = new ArrayList<>();
300             for ( File element : getCompileClasspathElements( getProject() ) )
301             {
302                 classpathElements.add( element.getPath() );
303             }
304             modulepathElements = Collections.emptyList();
305         }
306     }
307 
308     private void detectFilenameBasedAutomodules( final ResolvePathsResult<File> resolvePathsResult,
309             final JavaModuleDescriptor moduleDescriptor )
310     {
311         List<String> automodulesDetected = new ArrayList<>();
312         for ( Entry<File, ModuleNameSource> entry : resolvePathsResult.getModulepathElements().entrySet() )
313         {
314             if ( ModuleNameSource.FILENAME.equals( entry.getValue() ) )
315             {
316                 automodulesDetected.add( entry.getKey().getName() );
317             }
318         }
319 
320         if ( !automodulesDetected.isEmpty() )
321         {
322             final String message = "Required filename-based automodules detected: "
323                     +  automodulesDetected + ". "
324                     + "Please don't publish this project to a public artifact repository!";
325 
326             if ( moduleDescriptor.exports().isEmpty() )
327             {
328                 // application
329                 getLog().info( message );
330             }
331             else
332             {
333                 // library
334                 writeBoxedWarning( message );
335             }
336         }
337     }
338     
339     private List<File> getCompileClasspathElements( MavenProject project )
340     {
341         // 3 is outputFolder + 2 preserved for multirelease  
342         List<File> list = new ArrayList<>( project.getArtifacts().size() + 3 );
343 
344         if ( multiReleaseOutput )
345         {
346             File versionsFolder = new File( project.getBuild().getOutputDirectory(), "META-INF/versions" );
347             
348             // in reverse order
349             for ( int version = Integer.parseInt( getRelease() ) - 1; version >= 9 ; version-- )
350             {
351                 File versionSubFolder = new File( versionsFolder, String.valueOf( version ) );
352                 if ( versionSubFolder.exists() )
353                 {
354                     list.add( versionSubFolder );
355                 }
356             }
357         }
358 
359         list.add( new File( project.getBuild().getOutputDirectory() ) );
360 
361         for ( Artifact a : project.getArtifacts() )
362         {
363             list.add( a.getFile() );
364         }
365         return list;
366     }
367     
368     protected SourceInclusionScanner getSourceInclusionScanner( int staleMillis )
369     {
370         if ( includes.isEmpty() && excludes.isEmpty() )
371         {
372             return new StaleSourceScanner( staleMillis );
373         }
374 
375         if ( includes.isEmpty() )
376         {
377             includes.add( "**/*.java" );
378         }
379 
380         return new StaleSourceScanner( staleMillis, includes, excludes );
381     }
382 
383     protected SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding )
384     {
385         // it's not defined if we get the ending with or without the dot '.'
386         String defaultIncludePattern = "**/*" + ( inputFileEnding.startsWith( "." ) ? "" : "." ) + inputFileEnding;
387 
388         if ( includes.isEmpty() )
389         {
390             includes.add( defaultIncludePattern );
391         }
392 
393         return new SimpleSourceInclusionScanner( includes, excludes );
394     }
395 
396     protected String getSource()
397     {
398         return source;
399     }
400 
401     protected String getTarget()
402     {
403         return target;
404     }
405 
406     @Override
407     protected String getRelease()
408     {
409         return release;
410     }
411 
412     protected String getCompilerArgument()
413     {
414         return compilerArgument;
415     }
416 
417     protected Map<String, String> getCompilerArguments()
418     {
419         return compilerArguments;
420     }
421 
422     protected File getGeneratedSourcesDirectory()
423     {
424         return generatedSourcesDirectory;
425     }
426 
427     private void writeBoxedWarning( String message )
428     {
429         String line = StringUtils.repeat( "*", message.length() + 4 );
430         getLog().warn( line );
431         getLog().warn( "* " + MessageUtils.buffer().strong( message )  + " *" );
432         getLog().warn( line );
433     }
434 }