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 javax.tools.OptionChecker;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.lang.module.ModuleDescriptor;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.TreeMap;
34  
35  import org.apache.maven.api.JavaPathType;
36  import org.apache.maven.api.PathType;
37  import org.apache.maven.api.ProducedArtifact;
38  import org.apache.maven.api.ProjectScope;
39  import org.apache.maven.api.annotations.Nonnull;
40  import org.apache.maven.api.annotations.Nullable;
41  import org.apache.maven.api.plugin.MojoException;
42  import org.apache.maven.api.plugin.annotations.Mojo;
43  import org.apache.maven.api.plugin.annotations.Parameter;
44  
45  import static org.apache.maven.plugin.compiler.SourceDirectory.CLASS_FILE_SUFFIX;
46  import static org.apache.maven.plugin.compiler.SourceDirectory.JAVA_FILE_SUFFIX;
47  import static org.apache.maven.plugin.compiler.SourceDirectory.MODULE_INFO;
48  
49  /**
50   * Compiles application sources.
51   * Each instance shall be used only once, then discarded.
52   *
53   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
54   * @author Martin Desruisseaux
55   * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html">javac Command</a>
56   * @since 2.0
57   */
58  @Mojo(name = "compile", defaultPhase = "compile")
59  public class CompilerMojo extends AbstractCompilerMojo {
60      /**
61       * Set this to {@code true} to bypass compilation of main sources.
62       * Its use is not recommended, but quite convenient on occasion.
63       */
64      @Parameter(property = "maven.main.skip")
65      protected boolean skipMain;
66  
67      /**
68       * The source directories containing the sources to be compiled.
69       * If {@code null} or empty, the directory will be obtained from the project manager.
70       */
71      @Parameter
72      protected List<String> compileSourceRoots;
73  
74      /**
75       * Specify where to place generated source files created by annotation processing.
76       *
77       * @since 2.2
78       */
79      @Parameter(defaultValue = "${project.build.directory}/generated-sources/annotations")
80      protected Path generatedSourcesDirectory;
81  
82      /**
83       * A set of inclusion filters for the compiler.
84       */
85      @Parameter
86      protected Set<String> includes;
87  
88      /**
89       * A set of exclusion filters for the compiler.
90       */
91      @Parameter
92      protected Set<String> excludes;
93  
94      /**
95       * A set of exclusion filters for the incremental calculation.
96       * Updated source files, if excluded by this filter, will not cause the project to be rebuilt.
97       *
98       * <h4>Limitation</h4>
99       * In the current implementation, those exclusion filters are applied for added or removed files,
100      * but not yet for removed files.
101      *
102      * @since 3.11
103      */
104     @Parameter
105     protected Set<String> incrementalExcludes;
106 
107     /**
108      * The directory for compiled classes.
109      */
110     @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
111     protected Path outputDirectory;
112 
113     /**
114      * Projects main artifact.
115      */
116     @Parameter(defaultValue = "${project.mainArtifact}", readonly = true, required = true)
117     protected ProducedArtifact projectArtifact;
118 
119     /**
120      * When set to {@code true}, the classes will be placed in {@code META-INF/versions/${release}}.
121      * <p>
122      * <strong>Note:</strong> A jar is only a multi-release jar if {@code META-INF/MANIFEST.MF} contains
123      * {@code Multi-Release: true}. 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 multi-release jar using the {@link #outputDirectory}.
126      * </p>
127      *
128      * @since 3.7.1
129      *
130      * @deprecated Replaced by specifying the release version together with the source directory.
131      */
132     @Parameter
133     @Deprecated(since = "4.0.0")
134     protected boolean multiReleaseOutput;
135 
136     /**
137      * The file where to dump the command-line when debug is activated or when the compilation failed.
138      * For example, if the value is {@code "javac"}, then the Java compiler can be launched from the
139      * command-line by typing {@code javac @target/javac.args}.
140      * The debug file will contain the compiler options together with the list of source files to compile.
141      *
142      * @since 3.10.0
143      */
144     @Parameter(defaultValue = "javac.args")
145     protected String debugFileName;
146 
147     /**
148      * Creates a new compiler MOJO.
149      */
150     public CompilerMojo() {
151         super(false);
152     }
153 
154     /**
155      * Runs the Java compiler on the main source code.
156      *
157      * @throws MojoException if the compiler cannot be run.
158      */
159     @Override
160     public void execute() throws MojoException {
161         if (skipMain) {
162             logger.info("Not compiling main sources");
163             return;
164         }
165         super.execute();
166         @SuppressWarnings("LocalVariableHidesMemberVariable")
167         Path outputDirectory = getOutputDirectory();
168         if (Files.isDirectory(outputDirectory) && projectArtifact != null) {
169             artifactManager.setPath(projectArtifact, outputDirectory);
170         }
171     }
172 
173     /**
174      * Parses the parameters declared in the MOJO.
175      *
176      * @param  compiler  the tools to use for verifying the validity of options
177      * @return the options after validation
178      */
179     @Override
180     @SuppressWarnings("deprecation")
181     protected Options acceptParameters(final OptionChecker compiler) {
182         Options compilerConfiguration = super.acceptParameters(compiler);
183         compilerConfiguration.addUnchecked(compilerArgs);
184         compilerConfiguration.addUnchecked(compilerArgument);
185         return compilerConfiguration;
186     }
187 
188     /**
189      * {@return the root directories of Java source files to compile}.
190      * It can be a parameter specified to the compiler plugin,
191      * or otherwise the value provided by the project manager.
192      */
193     @Nonnull
194     @Override
195     protected List<Path> getCompileSourceRoots() {
196         List<Path> sources;
197         if (compileSourceRoots == null || compileSourceRoots.isEmpty()) {
198             sources = projectManager.getCompileSourceRoots(project, ProjectScope.MAIN);
199         } else {
200             sources = compileSourceRoots.stream().map(Paths::get).toList();
201         }
202         return sources;
203     }
204 
205     /**
206      * {@return the path where to place generated source files created by annotation processing on the main classes}.
207      */
208     @Nullable
209     @Override
210     protected Path getGeneratedSourcesDirectory() {
211         return generatedSourcesDirectory;
212     }
213 
214     /**
215      * {@return the inclusion filters for the compiler, or an empty set for all Java source files}.
216      */
217     @Override
218     protected Set<String> getIncludes() {
219         return (includes != null) ? includes : Set.of();
220     }
221 
222     /**
223      * {@return the exclusion filters for the compiler, or an empty set if none}.
224      */
225     @Override
226     protected Set<String> getExcludes() {
227         return (excludes != null) ? excludes : Set.of();
228     }
229 
230     /**
231      * {@return the exclusion filters for the incremental calculation, or an empty set if none}.
232      */
233     @Override
234     protected Set<String> getIncrementalExcludes() {
235         return (incrementalExcludes != null) ? incrementalExcludes : Set.of();
236     }
237 
238     /**
239      * {@return the destination directory for main class files}.
240      * If {@link #multiReleaseOutput} is true <em>(deprecated)</em>,
241      * the output will be in a {@code META-INF/versions} subdirectory.
242      */
243     @Nonnull
244     @Override
245     protected Path getOutputDirectory() {
246         if (SUPPORT_LEGACY && multiReleaseOutput && release != null) {
247             return outputDirectory.resolve(Path.of("META-INF", "versions", release));
248         }
249         return outputDirectory;
250     }
251 
252     /**
253      * {@return the file where to dump the command-line when debug is activated or when the compilation failed}.
254      */
255     @Nullable
256     @Override
257     protected String getDebugFileName() {
258         return debugFileName;
259     }
260 
261     /**
262      * If compiling a multi-release JAR in the old deprecated way, add the previous versions to the path.
263      *
264      * @param addTo where to add dependencies
265      * @param hasModuleDeclaration whether the main sources have or should have a {@code module-info} file
266      * @throws IOException if this method needs to walk through directories and that operation failed
267      *
268      * @deprecated For compatibility with the previous way to build multi-releases JAR file.
269      */
270     @Override
271     @Deprecated(since = "4.0.0")
272     protected void addImplicitDependencies(Map<PathType, List<Path>> addTo, boolean hasModuleDeclaration)
273             throws IOException {
274         if (SUPPORT_LEGACY && multiReleaseOutput) {
275             var paths = new TreeMap<Integer, Path>();
276             Path root = outputDirectory.resolve(Path.of("META-INF", "versions"));
277             Files.walk(root, 1).forEach((path) -> {
278                 int version;
279                 if (path.equals(root)) {
280                     path = outputDirectory;
281                     version = 0;
282                 } else {
283                     try {
284                         version = Integer.parseInt(path.getFileName().toString());
285                     } catch (NumberFormatException e) {
286                         throw new CompilationFailureException("Invalid version number for " + path, e);
287                     }
288                 }
289                 if (paths.put(version, path) != null) {
290                     throw new CompilationFailureException("Duplicated version number for " + path);
291                 }
292             });
293             /*
294              * Find the module name. If many module-info classes are found,
295              * the most basic one (with lowest Java release number) is taken.
296              */
297             String moduleName = null;
298             for (Path path : paths.values()) {
299                 path = path.resolve(MODULE_INFO + CLASS_FILE_SUFFIX);
300                 if (Files.exists(path)) {
301                     try (InputStream in = Files.newInputStream(path)) {
302                         moduleName = ModuleDescriptor.read(in).name();
303                     }
304                     break;
305                 }
306             }
307             /*
308              * If no module name was found in the classes compiled for previous Java releases,
309              * search in the source files for the Java release of the current compilation unit.
310              */
311             if (moduleName == null) {
312                 for (Path path : getCompileSourceRoots()) {
313                     moduleName = parseModuleInfoName(path.resolve(MODULE_INFO + JAVA_FILE_SUFFIX));
314                     if (moduleName != null) {
315                         break;
316                     }
317                 }
318             }
319             var pathType = (moduleName != null) ? JavaPathType.patchModule(moduleName) : JavaPathType.CLASSES;
320             addTo.computeIfAbsent(pathType, (key) -> new ArrayList<>())
321                     .addAll(paths.descendingMap().values());
322         }
323     }
324 }