| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.maven.plugin.compiler; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.plugins.annotations.ResolutionScope; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.shared.utils.StringUtils; |
| import org.apache.maven.shared.utils.logging.MessageUtils; |
| import org.apache.maven.toolchain.Toolchain; |
| import org.apache.maven.toolchain.java.DefaultJavaToolChain; |
| import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner; |
| import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; |
| import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; |
| import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor; |
| import org.codehaus.plexus.languages.java.jpms.LocationManager; |
| import org.codehaus.plexus.languages.java.jpms.ModuleNameSource; |
| import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest; |
| import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult; |
| |
| /** |
| * Compiles application sources. |
| * By default uses the <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html">javac</a> compiler |
| * of the JDK used to execute Maven. This can be overwritten through <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html">Toolchains</a> |
| * or parameter {@link AbstractCompilerMojo#compilerId}. |
| * |
| * @author <a href="mailto:jason@maven.org">Jason van Zyl </a> |
| * @since 2.0 |
| * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html">javac Command</a> |
| */ |
| @Mojo( |
| name = "compile", |
| defaultPhase = LifecyclePhase.COMPILE, |
| threadSafe = true, |
| requiresDependencyResolution = ResolutionScope.COMPILE) |
| public class CompilerMojo extends AbstractCompilerMojo { |
| /** |
| * The source directories containing the sources to be compiled. |
| */ |
| @Parameter(defaultValue = "${project.compileSourceRoots}", readonly = false, required = true) |
| private List<String> compileSourceRoots; |
| |
| /** |
| * The directory for compiled classes. |
| * <p> |
| * This parameter should only be modified in special cases. One example is creating |
| * a multi-release jar with a lower bytecode level (i.e. setting it to |
| * {@code ${project.build.outputDirectory}/META-INF/versions/21} or similar) in an additional |
| * execution. |
| * <p> |
| * When the required bytecode level is available though an installed JDK or toolchain, |
| * it is recommended to use the {@code <release>} property |
| * in conjunction with the ${multiReleaseOutput} parameter instead. |
| */ |
| @Parameter( |
| property = "maven.compiler.outputDirectory", |
| defaultValue = "${project.build.outputDirectory}", |
| required = true, |
| readonly = false) |
| private File outputDirectory; |
| |
| /** |
| * Projects main artifact. |
| * |
| * @todo this is an export variable, really |
| */ |
| @Parameter(defaultValue = "${project.artifact}", readonly = true, required = true) |
| private Artifact projectArtifact; |
| |
| /** |
| * A list of inclusion filters for the compiler. |
| */ |
| @Parameter |
| private Set<String> includes = new HashSet<>(); |
| |
| /** |
| * A list of exclusion filters for the compiler. |
| */ |
| @Parameter |
| private Set<String> excludes = new HashSet<>(); |
| |
| /** |
| * A list of exclusion filters for the incremental calculation. |
| * @since 3.11 |
| */ |
| @Parameter |
| private Set<String> incrementalExcludes = new HashSet<>(); |
| |
| /** |
| * Specify where to place generated source files created by annotation processing. Only applies to JDK 1.6+ |
| * |
| * @since 2.2 |
| */ |
| @Parameter(defaultValue = "${project.build.directory}/generated-sources/annotations") |
| private File generatedSourcesDirectory; |
| |
| /** |
| * Set this to {@code true} to bypass compilation of main sources. Its use is NOT RECOMMENDED, but quite convenient on |
| * occasion. |
| */ |
| @Parameter(property = "maven.main.skip") |
| private boolean skipMain; |
| |
| @Parameter(defaultValue = "${project.compileClasspathElements}", readonly = true, required = true) |
| private List<String> compilePath; |
| |
| /** |
| * <p> |
| * When set to {@code true}, the classes will be placed in <code>META-INF/versions/${release}</code> The release |
| * value must be set, otherwise the plugin will fail. |
| * </p> |
| * <strong>Note: </strong> A jar is only a multirelease jar if <code>META-INF/MANIFEST.MF</code> contains |
| * <code>Multi-Release: true</code>. You need to set this by configuring the <a href= |
| * "https://maven.apache.org/plugins/maven-jar-plugin/examples/manifest-customization.html">maven-jar-plugin</a>. |
| * This implies that you cannot test a multirelease jar using the outputDirectory. |
| * |
| * @since 3.7.1 |
| */ |
| @Parameter |
| private boolean multiReleaseOutput; |
| |
| /** |
| * When both {@link AbstractCompilerMojo#fork} and {@link AbstractCompilerMojo#debug} are enabled the commandline arguments used |
| * will be dumped to this file. |
| * @since 3.10.0 |
| */ |
| @Parameter(defaultValue = "javac") |
| private String debugFileName; |
| |
| final LocationManager locationManager = new LocationManager(); |
| |
| private List<String> classpathElements; |
| |
| private List<String> modulepathElements; |
| |
| private Map<String, JavaModuleDescriptor> pathElements; |
| |
| @Override |
| protected List<String> getCompileSourceRoots() { |
| return compileSourceRoots; |
| } |
| |
| @Override |
| protected List<String> getClasspathElements() { |
| return classpathElements; |
| } |
| |
| @Override |
| protected List<String> getModulepathElements() { |
| return modulepathElements; |
| } |
| |
| @Override |
| protected Map<String, JavaModuleDescriptor> getPathElements() { |
| return pathElements; |
| } |
| |
| @Override |
| protected File getOutputDirectory() { |
| File dir; |
| if (!multiReleaseOutput) { |
| dir = outputDirectory; |
| } else { |
| dir = new File(outputDirectory, "META-INF/versions/" + release); |
| } |
| return dir; |
| } |
| |
| @Override |
| public void execute() throws MojoExecutionException, CompilationFailureException { |
| if (skipMain) { |
| getLog().info("Not compiling main sources"); |
| return; |
| } |
| |
| if (multiReleaseOutput && release == null) { |
| throw new MojoExecutionException("When using 'multiReleaseOutput' the release must be set"); |
| } |
| |
| super.execute(); |
| |
| if (outputDirectory.isDirectory()) { |
| File artifactFile = projectArtifact.getFile(); |
| if (artifactFile != null && !Objects.equals(artifactFile, outputDirectory)) { |
| getLog().warn("Overwriting artifact's file from " + artifactFile + " to " + outputDirectory); |
| } |
| projectArtifact.setFile(outputDirectory); |
| } |
| } |
| |
| @Override |
| protected Set<String> getIncludes() { |
| return includes; |
| } |
| |
| @Override |
| protected Set<String> getExcludes() { |
| return excludes; |
| } |
| |
| @Override |
| protected void preparePaths(Set<File> sourceFiles) { |
| // assert compilePath != null; |
| |
| Optional<Path> moduleDeclaration = getModuleDeclaration(sourceFiles); |
| |
| if (moduleDeclaration.isPresent()) { |
| // For now only allow named modules. Once we can create a graph with ASM we can specify exactly the modules |
| // and we can detect if auto modules are used. In that case, MavenProject.setFile() should not be used, so |
| // you cannot depend on this project and so it won't be distributed. |
| |
| modulepathElements = new ArrayList<>(compilePath.size()); |
| classpathElements = new ArrayList<>(compilePath.size()); |
| pathElements = new LinkedHashMap<>(compilePath.size()); |
| |
| ResolvePathsResult<File> resolvePathsResult; |
| try { |
| Collection<File> dependencyArtifacts = getCompileClasspathElements(getProject()); |
| |
| ResolvePathsRequest<File> request = ResolvePathsRequest.ofFiles(dependencyArtifacts) |
| .setIncludeStatic(true) |
| .setMainModuleDescriptor(moduleDeclaration.get().toFile()); |
| |
| Toolchain toolchain = getToolchain(); |
| if (toolchain instanceof DefaultJavaToolChain) { |
| request.setJdkHome(new File(((DefaultJavaToolChain) toolchain).getJavaHome())); |
| } |
| |
| resolvePathsResult = locationManager.resolvePaths(request); |
| |
| for (Entry<File, Exception> pathException : |
| resolvePathsResult.getPathExceptions().entrySet()) { |
| Throwable cause = pathException.getValue(); |
| while (cause.getCause() != null) { |
| cause = cause.getCause(); |
| } |
| String fileName = pathException.getKey().getName(); |
| getLog().warn("Can't extract module name from " + fileName + ": " + cause.getMessage()); |
| } |
| |
| JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor(); |
| |
| detectFilenameBasedAutomodules(resolvePathsResult, moduleDescriptor); |
| |
| for (Map.Entry<File, JavaModuleDescriptor> entry : |
| resolvePathsResult.getPathElements().entrySet()) { |
| pathElements.put(entry.getKey().getPath(), entry.getValue()); |
| } |
| |
| if (compilerArgs == null) { |
| compilerArgs = new ArrayList<>(); |
| } |
| |
| for (File file : resolvePathsResult.getClasspathElements()) { |
| classpathElements.add(file.getPath()); |
| |
| if (multiReleaseOutput) { |
| if (getOutputDirectory().toPath().startsWith(file.getPath())) { |
| compilerArgs.add("--patch-module"); |
| compilerArgs.add(String.format("%s=%s", moduleDescriptor.name(), file.getPath())); |
| } |
| } |
| } |
| |
| for (File file : resolvePathsResult.getModulepathElements().keySet()) { |
| modulepathElements.add(file.getPath()); |
| } |
| |
| compilerArgs.add("--module-version"); |
| compilerArgs.add(getProject().getVersion()); |
| |
| } catch (IOException e) { |
| getLog().warn(e.getMessage()); |
| } |
| } else { |
| classpathElements = new ArrayList<>(); |
| for (File element : getCompileClasspathElements(getProject())) { |
| classpathElements.add(element.getPath()); |
| } |
| modulepathElements = Collections.emptyList(); |
| pathElements = Collections.emptyMap(); |
| } |
| } |
| |
| private void detectFilenameBasedAutomodules( |
| final ResolvePathsResult<File> resolvePathsResult, final JavaModuleDescriptor moduleDescriptor) { |
| List<String> automodulesDetected = new ArrayList<>(); |
| for (Entry<File, ModuleNameSource> entry : |
| resolvePathsResult.getModulepathElements().entrySet()) { |
| if (ModuleNameSource.FILENAME.equals(entry.getValue())) { |
| automodulesDetected.add(entry.getKey().getName()); |
| } |
| } |
| |
| if (!automodulesDetected.isEmpty()) { |
| final String message = "Required filename-based automodules detected: " |
| + automodulesDetected + ". " |
| + "Please don't publish this project to a public artifact repository!"; |
| |
| if (moduleDescriptor.exports().isEmpty()) { |
| // application |
| getLog().info(message); |
| } else { |
| // library |
| writeBoxedWarning(message); |
| } |
| } |
| } |
| |
| private List<File> getCompileClasspathElements(MavenProject project) { |
| // 3 is outputFolder + 2 preserved for multirelease |
| List<File> list = new ArrayList<>(project.getArtifacts().size() + 3); |
| |
| if (multiReleaseOutput) { |
| File versionsFolder = new File(project.getBuild().getOutputDirectory(), "META-INF/versions"); |
| |
| // in reverse order |
| for (int version = Integer.parseInt(getRelease()) - 1; version >= 9; version--) { |
| File versionSubFolder = new File(versionsFolder, String.valueOf(version)); |
| if (versionSubFolder.exists()) { |
| list.add(versionSubFolder); |
| } |
| } |
| } |
| |
| list.add(new File(project.getBuild().getOutputDirectory())); |
| |
| for (Artifact a : project.getArtifacts()) { |
| if (a.getArtifactHandler().isAddedToClasspath()) { |
| list.add(a.getFile()); |
| } |
| } |
| return list; |
| } |
| |
| @Override |
| protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis) { |
| if (includes.isEmpty() && excludes.isEmpty() && incrementalExcludes.isEmpty()) { |
| return new StaleSourceScanner(staleMillis); |
| } |
| |
| if (includes.isEmpty()) { |
| includes.add("**/*.java"); |
| } |
| |
| Set<String> excludesIncr = new HashSet<>(excludes); |
| excludesIncr.addAll(this.incrementalExcludes); |
| return new StaleSourceScanner(staleMillis, includes, excludesIncr); |
| } |
| |
| @Override |
| protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding) { |
| // it's not defined if we get the ending with or without the dot '.' |
| String defaultIncludePattern = "**/*" + (inputFileEnding.startsWith(".") ? "" : ".") + inputFileEnding; |
| |
| if (includes.isEmpty()) { |
| includes.add(defaultIncludePattern); |
| } |
| Set<String> excludesIncr = new HashSet<>(excludes); |
| excludesIncr.addAll(excludesIncr); |
| return new SimpleSourceInclusionScanner(includes, excludesIncr); |
| } |
| |
| @Override |
| protected String getSource() { |
| return source; |
| } |
| |
| @Override |
| protected String getTarget() { |
| return target; |
| } |
| |
| @Override |
| protected String getRelease() { |
| return release; |
| } |
| |
| @Override |
| protected String getCompilerArgument() { |
| return compilerArgument; |
| } |
| |
| @Override |
| protected Map<String, String> getCompilerArguments() { |
| return compilerArguments; |
| } |
| |
| @Override |
| protected File getGeneratedSourcesDirectory() { |
| return generatedSourcesDirectory; |
| } |
| |
| @Override |
| protected String getDebugFileName() { |
| return debugFileName; |
| } |
| |
| private void writeBoxedWarning(String message) { |
| String line = StringUtils.repeat("*", message.length() + 4); |
| getLog().warn(line); |
| getLog().warn("* " + MessageUtils.buffer().strong(message) + " *"); |
| getLog().warn(line); |
| } |
| } |