View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.compiler;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collection;
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.Map.Entry;
31  import java.util.Set;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.plugin.MojoExecutionException;
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.apache.maven.project.MavenProject;
40  import org.apache.maven.shared.utils.StringUtils;
41  import org.apache.maven.shared.utils.logging.MessageUtils;
42  import org.apache.maven.toolchain.Toolchain;
43  import org.apache.maven.toolchain.java.DefaultJavaToolChain;
44  import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
45  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
46  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
47  import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
48  import org.codehaus.plexus.languages.java.jpms.LocationManager;
49  import org.codehaus.plexus.languages.java.jpms.ModuleNameSource;
50  import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
51  import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
52  
53  /**
54   * Compiles application sources
55   *
56   * @author <a href="mailto:jason@maven.org">Jason van Zyl </a>
57   * @since 2.0
58   */
59  @Mojo(
60          name = "compile",
61          defaultPhase = LifecyclePhase.COMPILE,
62          threadSafe = true,
63          requiresDependencyResolution = ResolutionScope.COMPILE)
64  public class CompilerMojo extends AbstractCompilerMojo {
65      /**
66       * The source directories containing the sources to be compiled.
67       */
68      @Parameter(defaultValue = "${project.compileSourceRoots}", readonly = false, 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       * A list of exclusion filters for the incremental calculation.
99       * @since 3.11
100      */
101     @Parameter
102     private Set<String> incrementalExcludes = new HashSet<>();
103 
104     /**
105      * <p>
106      * Specify where to place generated source files created by annotation processing. Only applies to JDK 1.6+
107      * </p>
108      *
109      * @since 2.2
110      */
111     @Parameter(defaultValue = "${project.build.directory}/generated-sources/annotations")
112     private File generatedSourcesDirectory;
113 
114     /**
115      * Set this to 'true' to bypass compilation of main sources. Its use is NOT RECOMMENDED, but quite convenient on
116      * occasion.
117      */
118     @Parameter(property = "maven.main.skip")
119     private boolean skipMain;
120 
121     @Parameter(defaultValue = "${project.compileClasspathElements}", readonly = true, required = true)
122     private List<String> compilePath;
123 
124     /**
125      * <p>
126      * When set to {@code true}, the classes will be placed in <code>META-INF/versions/${release}</code> The release
127      * value must be set, otherwise the plugin will fail.
128      * </p>
129      * <strong>Note: </strong> A jar is only a multirelease jar if <code>META-INF/MANIFEST.MF</code> contains
130      * <code>Multi-Release: true</code>. You need to set this by configuring the <a href=
131      * "https://maven.apache.org/plugins/maven-jar-plugin/examples/manifest-customization.html">maven-jar-plugin</a>.
132      * This implies that you cannot test a multirelease jar using the outputDirectory.
133      *
134      * @since 3.7.1
135      */
136     @Parameter
137     private boolean multiReleaseOutput;
138 
139     /**
140      * when forking and debug activated the commandline used will be dumped in this file
141      * @since 3.10.0
142      */
143     @Parameter(defaultValue = "javac")
144     private String debugFileName;
145 
146     final LocationManager locationManager = new LocationManager();
147 
148     private List<String> classpathElements;
149 
150     private List<String> modulepathElements;
151 
152     private Map<String, JavaModuleDescriptor> pathElements;
153 
154     protected List<String> getCompileSourceRoots() {
155         return compileSourceRoots;
156     }
157 
158     @Override
159     protected List<String> getClasspathElements() {
160         return classpathElements;
161     }
162 
163     @Override
164     protected List<String> getModulepathElements() {
165         return modulepathElements;
166     }
167 
168     @Override
169     protected Map<String, JavaModuleDescriptor> getPathElements() {
170         return pathElements;
171     }
172 
173     protected File getOutputDirectory() {
174         File dir;
175         if (!multiReleaseOutput) {
176             dir = outputDirectory;
177         } else {
178             dir = new File(outputDirectory, "META-INF/versions/" + release);
179         }
180         return dir;
181     }
182 
183     public void execute() throws MojoExecutionException, CompilationFailureException {
184         if (skipMain) {
185             getLog().info("Not compiling main sources");
186             return;
187         }
188 
189         if (multiReleaseOutput && release == null) {
190             throw new MojoExecutionException("When using 'multiReleaseOutput' the release must be set");
191         }
192 
193         super.execute();
194 
195         if (outputDirectory.isDirectory()) {
196             projectArtifact.setFile(outputDirectory);
197         }
198     }
199 
200     @Override
201     protected Set<String> getIncludes() {
202         return includes;
203     }
204 
205     @Override
206     protected Set<String> getExcludes() {
207         return excludes;
208     }
209 
210     @Override
211     protected void preparePaths(Set<File> sourceFiles) {
212         // assert compilePath != null;
213 
214         File moduleDescriptorPath = null;
215 
216         boolean hasModuleDescriptor = false;
217         for (File sourceFile : sourceFiles) {
218             if ("module-info.java".equals(sourceFile.getName())) {
219                 moduleDescriptorPath = sourceFile;
220                 hasModuleDescriptor = true;
221                 break;
222             }
223         }
224 
225         if (hasModuleDescriptor) {
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                 Collection<File> dependencyArtifacts = getCompileClasspathElements(getProject());
237 
238                 ResolvePathsRequest<File> request = ResolvePathsRequest.ofFiles(dependencyArtifacts)
239                         .setIncludeStatic(true)
240                         .setMainModuleDescriptor(moduleDescriptorPath);
241 
242                 Toolchain toolchain = getToolchain();
243                 if (toolchain instanceof DefaultJavaToolChain) {
244                     request.setJdkHome(new File(((DefaultJavaToolChain) toolchain).getJavaHome()));
245                 }
246 
247                 resolvePathsResult = locationManager.resolvePaths(request);
248 
249                 for (Entry<File, Exception> pathException :
250                         resolvePathsResult.getPathExceptions().entrySet()) {
251                     Throwable cause = pathException.getValue();
252                     while (cause.getCause() != null) {
253                         cause = cause.getCause();
254                     }
255                     String fileName = pathException.getKey().getName();
256                     getLog().warn("Can't extract module name from " + fileName + ": " + cause.getMessage());
257                 }
258 
259                 JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor();
260 
261                 detectFilenameBasedAutomodules(resolvePathsResult, moduleDescriptor);
262 
263                 for (Map.Entry<File, JavaModuleDescriptor> entry :
264                         resolvePathsResult.getPathElements().entrySet()) {
265                     pathElements.put(entry.getKey().getPath(), entry.getValue());
266                 }
267 
268                 if (compilerArgs == null) {
269                     compilerArgs = new ArrayList<>();
270                 }
271 
272                 for (File file : resolvePathsResult.getClasspathElements()) {
273                     classpathElements.add(file.getPath());
274 
275                     if (multiReleaseOutput) {
276                         if (getOutputDirectory().toPath().startsWith(file.getPath())) {
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                     modulepathElements.add(file.getPath());
285                 }
286 
287                 compilerArgs.add("--module-version");
288                 compilerArgs.add(getProject().getVersion());
289 
290             } catch (IOException e) {
291                 getLog().warn(e.getMessage());
292             }
293         } else {
294             classpathElements = new ArrayList<>();
295             for (File element : getCompileClasspathElements(getProject())) {
296                 classpathElements.add(element.getPath());
297             }
298             modulepathElements = Collections.emptyList();
299         }
300     }
301 
302     private void detectFilenameBasedAutomodules(
303             final ResolvePathsResult<File> resolvePathsResult, final JavaModuleDescriptor moduleDescriptor) {
304         List<String> automodulesDetected = new ArrayList<>();
305         for (Entry<File, ModuleNameSource> entry :
306                 resolvePathsResult.getModulepathElements().entrySet()) {
307             if (ModuleNameSource.FILENAME.equals(entry.getValue())) {
308                 automodulesDetected.add(entry.getKey().getName());
309             }
310         }
311 
312         if (!automodulesDetected.isEmpty()) {
313             final String message = "Required filename-based automodules detected: "
314                     + automodulesDetected + ". "
315                     + "Please don't publish this project to a public artifact repository!";
316 
317             if (moduleDescriptor.exports().isEmpty()) {
318                 // application
319                 getLog().info(message);
320             } else {
321                 // library
322                 writeBoxedWarning(message);
323             }
324         }
325     }
326 
327     private List<File> getCompileClasspathElements(MavenProject project) {
328         // 3 is outputFolder + 2 preserved for multirelease
329         List<File> list = new ArrayList<>(project.getArtifacts().size() + 3);
330 
331         if (multiReleaseOutput) {
332             File versionsFolder = new File(project.getBuild().getOutputDirectory(), "META-INF/versions");
333 
334             // in reverse order
335             for (int version = Integer.parseInt(getRelease()) - 1; version >= 9; version--) {
336                 File versionSubFolder = new File(versionsFolder, String.valueOf(version));
337                 if (versionSubFolder.exists()) {
338                     list.add(versionSubFolder);
339                 }
340             }
341         }
342 
343         list.add(new File(project.getBuild().getOutputDirectory()));
344 
345         for (Artifact a : project.getArtifacts()) {
346             list.add(a.getFile());
347         }
348         return list;
349     }
350 
351     protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis) {
352         if (includes.isEmpty() && excludes.isEmpty() && incrementalExcludes.isEmpty()) {
353             return new StaleSourceScanner(staleMillis);
354         }
355 
356         if (includes.isEmpty()) {
357             includes.add("**/*.java");
358         }
359 
360         Set<String> excludesIncr = new HashSet<>(excludes);
361         excludesIncr.addAll(this.incrementalExcludes);
362         return new StaleSourceScanner(staleMillis, includes, excludesIncr);
363     }
364 
365     protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding) {
366         // it's not defined if we get the ending with or without the dot '.'
367         String defaultIncludePattern = "**/*" + (inputFileEnding.startsWith(".") ? "" : ".") + inputFileEnding;
368 
369         if (includes.isEmpty()) {
370             includes.add(defaultIncludePattern);
371         }
372         Set<String> excludesIncr = new HashSet<>(excludes);
373         excludesIncr.addAll(excludesIncr);
374         return new SimpleSourceInclusionScanner(includes, excludesIncr);
375     }
376 
377     protected String getSource() {
378         return source;
379     }
380 
381     protected String getTarget() {
382         return target;
383     }
384 
385     @Override
386     protected String getRelease() {
387         return release;
388     }
389 
390     protected String getCompilerArgument() {
391         return compilerArgument;
392     }
393 
394     protected Map<String, String> getCompilerArguments() {
395         return compilerArguments;
396     }
397 
398     protected File getGeneratedSourcesDirectory() {
399         return generatedSourcesDirectory;
400     }
401 
402     @Override
403     protected String getDebugFileName() {
404         return debugFileName;
405     }
406 
407     private void writeBoxedWarning(String message) {
408         String line = StringUtils.repeat("*", message.length() + 4);
409         getLog().warn(line);
410         getLog().warn("* " + MessageUtils.buffer().strong(message) + " *");
411         getLog().warn(line);
412     }
413 }