| /* |
| * 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.io.InputStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.nio.charset.Charset; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import org.apache.maven.artifact.handler.ArtifactHandler; |
| import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; |
| import org.apache.maven.execution.MavenExecutionRequest; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.plugin.AbstractMojo; |
| import org.apache.maven.plugin.MojoExecution; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.shared.incremental.IncrementalBuildHelper; |
| import org.apache.maven.shared.incremental.IncrementalBuildHelperRequest; |
| import org.apache.maven.shared.utils.ReaderFactory; |
| import org.apache.maven.shared.utils.StringUtils; |
| import org.apache.maven.shared.utils.io.DirectoryScanResult; |
| import org.apache.maven.shared.utils.io.DirectoryScanner; |
| import org.apache.maven.shared.utils.io.FileUtils; |
| import org.apache.maven.shared.utils.logging.MessageBuilder; |
| import org.apache.maven.shared.utils.logging.MessageUtils; |
| import org.apache.maven.toolchain.Toolchain; |
| import org.apache.maven.toolchain.ToolchainManager; |
| import org.codehaus.plexus.compiler.Compiler; |
| import org.codehaus.plexus.compiler.CompilerConfiguration; |
| import org.codehaus.plexus.compiler.CompilerException; |
| import org.codehaus.plexus.compiler.CompilerMessage; |
| import org.codehaus.plexus.compiler.CompilerOutputStyle; |
| import org.codehaus.plexus.compiler.CompilerResult; |
| import org.codehaus.plexus.compiler.manager.CompilerManager; |
| import org.codehaus.plexus.compiler.manager.NoSuchCompilerException; |
| import org.codehaus.plexus.compiler.util.scan.InclusionScanException; |
| import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; |
| import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping; |
| import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping; |
| import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; |
| import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor; |
| import org.codehaus.plexus.languages.java.version.JavaVersion; |
| import org.eclipse.aether.RepositorySystem; |
| import org.eclipse.aether.artifact.Artifact; |
| import org.eclipse.aether.artifact.DefaultArtifact; |
| import org.eclipse.aether.collection.CollectRequest; |
| import org.eclipse.aether.graph.Dependency; |
| import org.eclipse.aether.graph.Exclusion; |
| import org.eclipse.aether.resolution.ArtifactResult; |
| import org.eclipse.aether.resolution.DependencyRequest; |
| import org.eclipse.aether.resolution.DependencyResult; |
| import org.eclipse.aether.util.artifact.JavaScopes; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.Opcodes; |
| |
| /** |
| * TODO: At least one step could be optimized, currently the plugin will do two |
| * scans of all the source code if the compiler has to have the entire set of |
| * sources. This is currently the case for at least the C# compiler and most |
| * likely all the other .NET compilers too. |
| * |
| * @author others |
| * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> |
| * @since 2.0 |
| */ |
| public abstract class AbstractCompilerMojo extends AbstractMojo { |
| protected static final String PS = System.getProperty("path.separator"); |
| |
| private static final String INPUT_FILES_LST_FILENAME = "inputFiles.lst"; |
| |
| static final String DEFAULT_SOURCE = "1.8"; |
| |
| static final String DEFAULT_TARGET = "1.8"; |
| |
| // Used to compare with older targets |
| static final String MODULE_INFO_TARGET = "1.9"; |
| |
| // ---------------------------------------------------------------------- |
| // Configurables |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Indicates whether the build will continue even if there are compilation errors. |
| * |
| * @since 2.0.2 |
| */ |
| @Parameter(property = "maven.compiler.failOnError", defaultValue = "true") |
| private boolean failOnError = true; |
| |
| /** |
| * Indicates whether the build will continue even if there are compilation warnings. |
| * |
| * @since 3.6 |
| */ |
| @Parameter(property = "maven.compiler.failOnWarning", defaultValue = "false") |
| private boolean failOnWarning; |
| |
| /** |
| * Set to <code>true</code> to include debugging information in the compiled class files. |
| */ |
| @Parameter(property = "maven.compiler.debug", defaultValue = "true") |
| private boolean debug = true; |
| |
| /** |
| * Set to <code>true</code> to generate metadata for reflection on method parameters. |
| * @since 3.6.2 |
| */ |
| @Parameter(property = "maven.compiler.parameters", defaultValue = "false") |
| private boolean parameters; |
| |
| /** |
| * Set to <code>true</code> to Enable preview language features of the java compiler |
| * @since 3.10.1 |
| */ |
| @Parameter(property = "maven.compiler.enablePreview", defaultValue = "false") |
| private boolean enablePreview; |
| |
| /** |
| * Set to <code>true</code> to show messages about what the compiler is doing. |
| */ |
| @Parameter(property = "maven.compiler.verbose", defaultValue = "false") |
| private boolean verbose; |
| |
| /** |
| * Sets whether to show source locations where deprecated APIs are used. |
| */ |
| @Parameter(property = "maven.compiler.showDeprecation", defaultValue = "false") |
| private boolean showDeprecation; |
| |
| /** |
| * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods. |
| * @deprecated This property is a no-op in {@code javac}. |
| */ |
| @Deprecated |
| @Parameter(property = "maven.compiler.optimize", defaultValue = "false") |
| private boolean optimize; |
| |
| /** |
| * Set to <code>false</code> to disable warnings during compilation. |
| */ |
| @Parameter(property = "maven.compiler.showWarnings", defaultValue = "true") |
| private boolean showWarnings; |
| |
| /** |
| * <p>The -source argument for the Java compiler.</p> |
| * |
| * <p><b>NOTE: </b></p> |
| * <p>Since 3.8.0 the default value has changed from 1.5 to 1.6</p> |
| * <p>Since 3.9.0 the default value has changed from 1.6 to 1.7</p> |
| * <p>Since 3.11.0 the default value has changed from 1.7 to 1.8</p> |
| */ |
| @Parameter(property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE) |
| protected String source; |
| |
| /** |
| * <p>The -target argument for the Java compiler.</p> |
| * |
| * <p><b>NOTE: </b></p> |
| * <p>Since 3.8.0 the default value has changed from 1.5 to 1.6</p> |
| * <p>Since 3.9.0 the default value has changed from 1.6 to 1.7</p> |
| * <p>Since 3.11.0 the default value has changed from 1.7 to 1.8</p> |
| */ |
| @Parameter(property = "maven.compiler.target", defaultValue = DEFAULT_TARGET) |
| protected String target; |
| |
| /** |
| * The -release argument for the Java compiler, supported since Java9 |
| * |
| * @since 3.6 |
| */ |
| @Parameter(property = "maven.compiler.release") |
| protected String release; |
| |
| /** |
| * The -encoding argument for the Java compiler. |
| * |
| * @since 2.1 |
| */ |
| @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}") |
| private String encoding; |
| |
| /** |
| * Sets the granularity in milliseconds of the last modification |
| * date for testing whether a source needs recompilation. |
| */ |
| @Parameter(property = "lastModGranularityMs", defaultValue = "0") |
| private int staleMillis; |
| |
| /** |
| * The compiler id of the compiler to use. See this |
| * <a href="non-javac-compilers.html">guide</a> for more information. |
| */ |
| @Parameter(property = "maven.compiler.compilerId", defaultValue = "javac") |
| private String compilerId; |
| |
| /** |
| * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>. |
| */ |
| @Parameter(property = "maven.compiler.compilerVersion") |
| private String compilerVersion; |
| |
| /** |
| * Allows running the compiler in a separate process. |
| * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable. |
| */ |
| @Parameter(property = "maven.compiler.fork", defaultValue = "false") |
| private boolean fork; |
| |
| /** |
| * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m" |
| * if {@link #fork} is set to <code>true</code>. |
| * |
| * @since 2.0.1 |
| */ |
| @Parameter(property = "maven.compiler.meminitial") |
| private String meminitial; |
| |
| /** |
| * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m" |
| * if {@link #fork} is set to <code>true</code>. |
| * |
| * @since 2.0.1 |
| */ |
| @Parameter(property = "maven.compiler.maxmem") |
| private String maxmem; |
| |
| /** |
| * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>. |
| */ |
| @Parameter(property = "maven.compiler.executable") |
| private String executable; |
| |
| /** |
| * <p> |
| * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+ |
| * If not set, both compilation and annotation processing are performed at the same time. |
| * </p> |
| * <p>Allowed values are:</p> |
| * <ul> |
| * <li><code>none</code> - no annotation processing is performed.</li> |
| * <li><code>only</code> - only annotation processing is done, no compilation.</li> |
| * </ul> |
| * |
| * @since 2.2 |
| */ |
| @Parameter |
| private String proc; |
| |
| /** |
| * <p> |
| * Names of annotation processors to run. Only applies to JDK 1.6+ |
| * If not set, the default annotation processors discovery process applies. |
| * </p> |
| * |
| * @since 2.2 |
| */ |
| @Parameter |
| private String[] annotationProcessors; |
| |
| /** |
| * <p> |
| * Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation |
| * processors only in those classpath elements. If omitted, the default classpath is used to detect annotation |
| * processors. The detection itself depends on the configuration of {@code annotationProcessors}. |
| * </p> |
| * <p> |
| * Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier, |
| * type). Transitive dependencies are added automatically. Exclusions are supported as well. Example: |
| * </p> |
| * |
| * <pre> |
| * <configuration> |
| * <annotationProcessorPaths> |
| * <path> |
| * <groupId>org.sample</groupId> |
| * <artifactId>sample-annotation-processor</artifactId> |
| * <version>1.2.3</version> |
| * <!-- Optionally exclude transitive dependencies --> |
| * <exclusions> |
| * <exclusion> |
| * <groupId>org.sample</groupId> |
| * <artifactId>sample-dependency</artifactId> |
| * </exclusion> |
| * </exclusions> |
| * </path> |
| * <!-- ... more ... --> |
| * </annotationProcessorPaths> |
| * </configuration> |
| * </pre> |
| * |
| * <b>Note:</b> Exclusions are supported from version 3.11.0. |
| * |
| * @since 3.5 |
| */ |
| @Parameter |
| private List<DependencyCoordinate> annotationProcessorPaths; |
| |
| /** |
| * <p> |
| * Sets the arguments to be passed to the compiler (prepending a dash). |
| * </p> |
| * <p> |
| * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version. |
| * </p> |
| * <p> |
| * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}. |
| * </p> |
| * <p> |
| * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following: |
| * </p> |
| * |
| * <pre> |
| * <compilerArguments> |
| * <Xmaxerrs>1000</Xmaxerrs> |
| * <Xlint/> |
| * <Xlint:-path/> |
| * <Averbose>true</Averbose> |
| * </compilerArguments> |
| * </pre> |
| * |
| * @since 2.0.1 |
| * @deprecated use {@link #compilerArgs} instead. |
| */ |
| @Parameter |
| @Deprecated |
| protected Map<String, String> compilerArguments; |
| |
| /** |
| * <p> |
| * Sets the arguments to be passed to the compiler. |
| * </p> |
| * <p> |
| * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}. |
| * </p> |
| * Example: |
| * <pre> |
| * <compilerArgs> |
| * <arg>-Xmaxerrs</arg> |
| * <arg>1000</arg> |
| * <arg>-Xlint</arg> |
| * <arg>-J-Duser.language=en_us</arg> |
| * </compilerArgs> |
| * </pre> |
| * |
| * @since 3.1 |
| */ |
| @Parameter |
| protected List<String> compilerArgs; |
| |
| /** |
| * <p> |
| * Sets the unformatted single argument string to be passed to the compiler. To pass multiple arguments such as |
| * <code>-Xmaxerrs 1000</code> (which are actually two arguments) you have to use {@link #compilerArguments}. |
| * </p> |
| * <p> |
| * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version. |
| * </p> |
| * <p> |
| * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}. |
| * </p> |
| */ |
| @Parameter |
| protected String compilerArgument; |
| |
| /** |
| * Sets the name of the output file when compiling a set of |
| * sources to a single file. |
| * <p/> |
| * expression="${project.build.finalName}" |
| */ |
| @Parameter |
| private String outputFileName; |
| |
| /** |
| * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a |
| * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>. |
| * If debug level is not specified, by default, nothing will be appended to <code>-g</code>. |
| * If debug is not turned on, this attribute will be ignored. |
| * |
| * @since 2.1 |
| */ |
| @Parameter(property = "maven.compiler.debuglevel") |
| private String debuglevel; |
| |
| /** |
| * Keyword to be appended to the <code>-implicit:</code> command-line switch. |
| * |
| * @since 3.10.2 |
| */ |
| @Parameter(property = "maven.compiler.implicit") |
| private String implicit; |
| |
| /** |
| * |
| */ |
| @Component |
| private ToolchainManager toolchainManager; |
| |
| /** |
| * <p> |
| * Specify the requirements for this jdk toolchain for using a different {@code javac} than the one of the JRE used |
| * by Maven. This overrules the toolchain selected by the |
| * <a href="https://maven.apache.org/plugins/maven-toolchains-plugin/">maven-toolchain-plugin</a>. |
| * </p> |
| * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html"> Guide to Toolchains</a> for more |
| * info) |
| * |
| * <pre> |
| * <configuration> |
| * <jdkToolchain> |
| * <version>11</version> |
| * </jdkToolchain> |
| * ... |
| * </configuration> |
| * |
| * <configuration> |
| * <jdkToolchain> |
| * <version>1.8</version> |
| * <vendor>zulu</vendor> |
| * </jdkToolchain> |
| * ... |
| * </configuration> |
| * </pre> |
| * <strong>note:</strong> requires at least Maven 3.3.1 |
| * |
| * @since 3.6 |
| */ |
| @Parameter |
| private Map<String, String> jdkToolchain; |
| |
| // ---------------------------------------------------------------------- |
| // Read-only parameters |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * The directory to run the compiler from if fork is true. |
| */ |
| @Parameter(defaultValue = "${basedir}", required = true, readonly = true) |
| private File basedir; |
| |
| /** |
| * The target directory of the compiler if fork is true. |
| */ |
| @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true) |
| private File buildDirectory; |
| |
| /** |
| * Plexus compiler manager. |
| */ |
| @Component |
| private CompilerManager compilerManager; |
| |
| /** |
| * The current build session instance. This is used for toolchain manager API calls. |
| */ |
| @Parameter(defaultValue = "${session}", readonly = true, required = true) |
| private MavenSession session; |
| |
| /** |
| * The current project instance. This is used for propagating generated-sources paths as compile/testCompile source |
| * roots. |
| */ |
| @Parameter(defaultValue = "${project}", readonly = true, required = true) |
| private MavenProject project; |
| |
| /** |
| * Strategy to re use javacc class created: |
| * <ul> |
| * <li><code>reuseCreated</code> (default): will reuse already created but in case of multi-threaded builds, each |
| * thread will have its own instance</li> |
| * <li><code>reuseSame</code>: the same Javacc class will be used for each compilation even for multi-threaded build |
| * </li> |
| * <li><code>alwaysNew</code>: a new Javacc class will be created for each compilation</li> |
| * </ul> |
| * Note this parameter value depends on the os/jdk you are using, but the default value should work on most of env. |
| * |
| * @since 2.5 |
| */ |
| @Parameter(defaultValue = "${reuseCreated}", property = "maven.compiler.compilerReuseStrategy") |
| private String compilerReuseStrategy = "reuseCreated"; |
| |
| /** |
| * @since 2.5 |
| */ |
| @Parameter(defaultValue = "false", property = "maven.compiler.skipMultiThreadWarning") |
| private boolean skipMultiThreadWarning; |
| |
| /** |
| * compiler can now use javax.tools if available in your current jdk, you can disable this feature |
| * using -Dmaven.compiler.forceJavacCompilerUse=true or in the plugin configuration |
| * |
| * @since 3.0 |
| */ |
| @Parameter(defaultValue = "false", property = "maven.compiler.forceJavacCompilerUse") |
| private boolean forceJavacCompilerUse; |
| |
| /** |
| * @since 3.0 needed for storing the status for the incremental build support. |
| */ |
| @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true) |
| private MojoExecution mojoExecution; |
| |
| /** |
| * File extensions to check timestamp for incremental build. |
| * Default contains only <code>class</code> and <code>jar</code>. |
| * |
| * @since 3.1 |
| */ |
| @Parameter |
| private List<String> fileExtensions; |
| |
| /** |
| * <p>to enable/disable incremental compilation feature.</p> |
| * <p>This leads to two different modes depending on the underlying compiler. The default javac compiler does the |
| * following:</p> |
| * <ul> |
| * <li>true <strong>(default)</strong> in this mode the compiler plugin determines whether any JAR files the |
| * current module depends on have changed in the current build run; or any source file was added, removed or |
| * changed since the last compilation. If this is the case, the compiler plugin recompiles all sources.</li> |
| * <li>false <strong>(not recommended)</strong> this only compiles source files which are newer than their |
| * corresponding class files, namely which have changed since the last compilation. This does not |
| * recompile other classes which use the changed class, potentially leaving them with references to methods that no |
| * longer exist, leading to errors at runtime.</li> |
| * </ul> |
| * |
| * @since 3.1 |
| */ |
| @Parameter(defaultValue = "true", property = "maven.compiler.useIncrementalCompilation") |
| private boolean useIncrementalCompilation = true; |
| |
| /** |
| * Package info source files that only contain javadoc and no annotation on the package |
| * can lead to no class file being generated by the compiler. This causes a file miss |
| * on the next compilations and forces an unnecessary recompilation. The default value |
| * of <code>true</code> causes an empty class file to be generated. This behavior can |
| * be changed by setting this parameter to <code>false</code>. |
| * |
| * @since 3.10 |
| */ |
| @Parameter(defaultValue = "true", property = "maven.compiler.createMissingPackageInfoClass") |
| private boolean createMissingPackageInfoClass = true; |
| |
| @Parameter(defaultValue = "false", property = "maven.compiler.showCompilationChanges") |
| private boolean showCompilationChanges = false; |
| /** |
| * Resolves the artifacts needed. |
| */ |
| @Component |
| private RepositorySystem repositorySystem; |
| |
| /** |
| * Artifact handler manager. |
| */ |
| @Component |
| private ArtifactHandlerManager artifactHandlerManager; |
| |
| protected abstract SourceInclusionScanner getSourceInclusionScanner(int staleMillis); |
| |
| protected abstract SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding); |
| |
| protected abstract List<String> getClasspathElements(); |
| |
| protected abstract List<String> getModulepathElements(); |
| |
| protected abstract Map<String, JavaModuleDescriptor> getPathElements(); |
| |
| protected abstract List<String> getCompileSourceRoots(); |
| |
| protected abstract void preparePaths(Set<File> sourceFiles); |
| |
| protected abstract File getOutputDirectory(); |
| |
| protected abstract String getSource(); |
| |
| protected abstract String getTarget(); |
| |
| protected abstract String getRelease(); |
| |
| protected abstract String getCompilerArgument(); |
| |
| protected abstract Map<String, String> getCompilerArguments(); |
| |
| protected abstract File getGeneratedSourcesDirectory(); |
| |
| protected abstract String getDebugFileName(); |
| |
| protected final MavenProject getProject() { |
| return project; |
| } |
| |
| private boolean targetOrReleaseSet; |
| |
| @Override |
| public void execute() throws MojoExecutionException, CompilationFailureException { |
| // ---------------------------------------------------------------------- |
| // Look up the compiler. This is done before other code than can |
| // cause the mojo to return before the lookup is done possibly resulting |
| // in misconfigured POMs still building. |
| // ---------------------------------------------------------------------- |
| |
| Compiler compiler; |
| |
| getLog().debug("Using compiler '" + compilerId + "'."); |
| |
| try { |
| compiler = compilerManager.getCompiler(compilerId); |
| } catch (NoSuchCompilerException e) { |
| throw new MojoExecutionException("No such compiler '" + e.getCompilerId() + "'."); |
| } |
| |
| // -----------toolchains start here ---------------------------------- |
| // use the compilerId as identifier for toolchains as well. |
| Toolchain tc = getToolchain(); |
| if (tc != null) { |
| getLog().info("Toolchain in maven-compiler-plugin: " + tc); |
| if (executable != null) { |
| getLog().warn("Toolchains are ignored, 'executable' parameter is set to " + executable); |
| } else { |
| fork = true; |
| // TODO somehow shaky dependency between compilerId and tool executable. |
| executable = tc.findTool(compilerId); |
| } |
| } |
| // ---------------------------------------------------------------------- |
| // |
| // ---------------------------------------------------------------------- |
| |
| List<String> compileSourceRoots = removeEmptyCompileSourceRoots(getCompileSourceRoots()); |
| |
| if (compileSourceRoots.isEmpty()) { |
| getLog().info("No sources to compile"); |
| |
| return; |
| } |
| |
| // Verify that target or release is set |
| if (!targetOrReleaseSet) { |
| MessageBuilder mb = MessageUtils.buffer() |
| .a("No explicit value set for target or release! ") |
| .a("To ensure the same result even after upgrading this plugin, please add ") |
| .newline() |
| .newline(); |
| |
| writePlugin(mb); |
| |
| getLog().warn(mb.toString()); |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Create the compiler configuration |
| // ---------------------------------------------------------------------- |
| |
| CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); |
| |
| compilerConfiguration.setOutputLocation(getOutputDirectory().getAbsolutePath()); |
| |
| compilerConfiguration.setOptimize(optimize); |
| |
| compilerConfiguration.setDebug(debug); |
| |
| compilerConfiguration.setDebugFileName(getDebugFileName()); |
| |
| compilerConfiguration.setImplicitOption(implicit); |
| |
| if (debug && StringUtils.isNotEmpty(debuglevel)) { |
| String[] split = StringUtils.split(debuglevel, ","); |
| for (String aSplit : split) { |
| if (!(aSplit.equalsIgnoreCase("none") |
| || aSplit.equalsIgnoreCase("lines") |
| || aSplit.equalsIgnoreCase("vars") |
| || aSplit.equalsIgnoreCase("source"))) { |
| throw new IllegalArgumentException("The specified debug level: '" + aSplit + "' is unsupported. " |
| + "Legal values are 'none', 'lines', 'vars', and 'source'."); |
| } |
| } |
| compilerConfiguration.setDebugLevel(debuglevel); |
| } |
| |
| compilerConfiguration.setParameters(parameters); |
| |
| compilerConfiguration.setEnablePreview(enablePreview); |
| |
| compilerConfiguration.setVerbose(verbose); |
| |
| compilerConfiguration.setShowWarnings(showWarnings); |
| |
| compilerConfiguration.setFailOnWarning(failOnWarning); |
| |
| if (failOnWarning && !showWarnings) { |
| getLog().warn("The property failOnWarning is set to true, but showWarnings is set to false."); |
| getLog().warn("With compiler's warnings silenced the failOnWarning has no effect."); |
| } |
| |
| compilerConfiguration.setShowDeprecation(showDeprecation); |
| |
| compilerConfiguration.setSourceVersion(getSource()); |
| |
| compilerConfiguration.setTargetVersion(getTarget()); |
| |
| compilerConfiguration.setReleaseVersion(getRelease()); |
| |
| compilerConfiguration.setProc(proc); |
| |
| File generatedSourcesDirectory = getGeneratedSourcesDirectory(); |
| compilerConfiguration.setGeneratedSourcesDirectory( |
| generatedSourcesDirectory != null ? generatedSourcesDirectory.getAbsoluteFile() : null); |
| |
| if (generatedSourcesDirectory != null) { |
| if (!generatedSourcesDirectory.exists()) { |
| generatedSourcesDirectory.mkdirs(); |
| } |
| |
| String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath(); |
| |
| compileSourceRoots.add(generatedSourcesPath); |
| |
| if (isTestCompile()) { |
| getLog().debug("Adding " + generatedSourcesPath + " to test-compile source roots:\n " |
| + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n ")); |
| |
| project.addTestCompileSourceRoot(generatedSourcesPath); |
| |
| getLog().debug("New test-compile source roots:\n " |
| + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n ")); |
| } else { |
| getLog().debug("Adding " + generatedSourcesPath + " to compile source roots:\n " |
| + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n ")); |
| |
| project.addCompileSourceRoot(generatedSourcesPath); |
| |
| getLog().debug("New compile source roots:\n " |
| + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n ")); |
| } |
| } |
| |
| compilerConfiguration.setSourceLocations(compileSourceRoots); |
| |
| compilerConfiguration.setAnnotationProcessors(annotationProcessors); |
| |
| compilerConfiguration.setProcessorPathEntries(resolveProcessorPathEntries()); |
| |
| compilerConfiguration.setSourceEncoding(encoding); |
| |
| compilerConfiguration.setFork(fork); |
| |
| if (fork) { |
| if (!StringUtils.isEmpty(meminitial)) { |
| String value = getMemoryValue(meminitial); |
| |
| if (value != null) { |
| compilerConfiguration.setMeminitial(value); |
| } else { |
| getLog().info("Invalid value for meminitial '" + meminitial + "'. Ignoring this option."); |
| } |
| } |
| |
| if (!StringUtils.isEmpty(maxmem)) { |
| String value = getMemoryValue(maxmem); |
| |
| if (value != null) { |
| compilerConfiguration.setMaxmem(value); |
| } else { |
| getLog().info("Invalid value for maxmem '" + maxmem + "'. Ignoring this option."); |
| } |
| } |
| } |
| |
| compilerConfiguration.setExecutable(executable); |
| |
| compilerConfiguration.setWorkingDirectory(basedir); |
| |
| compilerConfiguration.setCompilerVersion(compilerVersion); |
| |
| compilerConfiguration.setBuildDirectory(buildDirectory); |
| |
| compilerConfiguration.setOutputFileName(outputFileName); |
| |
| if (CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals(this.compilerReuseStrategy)) { |
| compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.AlwaysNew); |
| } else if (CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy() |
| .equals(this.compilerReuseStrategy)) { |
| if (getRequestThreadCount() > 1) { |
| if (!skipMultiThreadWarning) { |
| getLog().warn("You are in a multi-thread build and compilerReuseStrategy is set to reuseSame." |
| + " This can cause issues in some environments (os/jdk)!" |
| + " Consider using reuseCreated strategy." |
| + System.getProperty("line.separator") |
| + "If your env is fine with reuseSame, you can skip this warning with the " |
| + "configuration field skipMultiThreadWarning " |
| + "or -Dmaven.compiler.skipMultiThreadWarning=true"); |
| } |
| } |
| compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseSame); |
| } else { |
| |
| compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseCreated); |
| } |
| |
| getLog().debug("CompilerReuseStrategy: " |
| + compilerConfiguration.getCompilerReuseStrategy().getStrategy()); |
| |
| compilerConfiguration.setForceJavacCompilerUse(forceJavacCompilerUse); |
| |
| boolean canUpdateTarget; |
| |
| IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper(mojoExecution, session); |
| |
| final Set<File> sources; |
| |
| IncrementalBuildHelperRequest incrementalBuildHelperRequest = null; |
| |
| if (useIncrementalCompilation) { |
| getLog().debug("useIncrementalCompilation enabled"); |
| try { |
| canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration); |
| |
| sources = getCompileSources(compiler, compilerConfiguration); |
| |
| preparePaths(sources); |
| |
| incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles(sources); |
| |
| DirectoryScanResult dsr = computeInputFileTreeChanges(incrementalBuildHelper, sources); |
| |
| boolean idk = compiler.getCompilerOutputStyle() |
| .equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) |
| && !canUpdateTarget; |
| boolean dependencyChanged = isDependencyChanged(); |
| boolean sourceChanged = isSourceChanged(compilerConfiguration, compiler); |
| boolean inputFileTreeChanged = hasInputFileTreeChanged(dsr); |
| // CHECKSTYLE_OFF: LineLength |
| if (idk || dependencyChanged || sourceChanged || inputFileTreeChanged) |
| // CHECKSTYLE_ON: LineLength |
| { |
| String cause = idk |
| ? "idk" |
| : (dependencyChanged ? "dependency" : (sourceChanged ? "source" : "input tree")); |
| getLog().info("Changes detected - recompiling the module! :" + cause); |
| if (showCompilationChanges) { |
| for (String fileAdded : dsr.getFilesAdded()) { |
| getLog().info("\t+ " + fileAdded); |
| } |
| for (String fileRemoved : dsr.getFilesRemoved()) { |
| getLog().info("\t- " + fileRemoved); |
| } |
| } |
| |
| compilerConfiguration.setSourceFiles(sources); |
| } else { |
| getLog().info("Nothing to compile - all classes are up to date"); |
| |
| return; |
| } |
| } catch (CompilerException e) { |
| throw new MojoExecutionException("Error while computing stale sources.", e); |
| } |
| } else { |
| getLog().debug("useIncrementalCompilation disabled"); |
| |
| Set<File> staleSources; |
| try { |
| staleSources = |
| computeStaleSources(compilerConfiguration, compiler, getSourceInclusionScanner(staleMillis)); |
| |
| canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration); |
| |
| if (compiler.getCompilerOutputStyle().equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) |
| && !canUpdateTarget) { |
| getLog().info("RESCANNING!"); |
| // TODO: This second scan for source files is sub-optimal |
| String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration); |
| |
| staleSources = computeStaleSources( |
| compilerConfiguration, compiler, getSourceInclusionScanner(inputFileEnding)); |
| } |
| |
| } catch (CompilerException e) { |
| throw new MojoExecutionException("Error while computing stale sources.", e); |
| } |
| |
| if (staleSources.isEmpty()) { |
| getLog().info("Nothing to compile - all classes are up to date"); |
| |
| return; |
| } |
| |
| compilerConfiguration.setSourceFiles(staleSources); |
| |
| try { |
| // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath |
| sources = getCompileSources(compiler, compilerConfiguration); |
| |
| if (getLog().isDebugEnabled()) { |
| getLog().debug("#sources: " + sources.size()); |
| for (File file : sources) { |
| getLog().debug(file.getPath()); |
| } |
| } |
| |
| preparePaths(sources); |
| } catch (CompilerException e) { |
| throw new MojoExecutionException("Error while computing stale sources.", e); |
| } |
| } |
| |
| // Dividing pathElements of classPath and modulePath is based on sourceFiles |
| compilerConfiguration.setClasspathEntries(getClasspathElements()); |
| |
| compilerConfiguration.setModulepathEntries(getModulepathElements()); |
| |
| compilerConfiguration.setIncludes(getIncludes()); |
| |
| compilerConfiguration.setExcludes(getExcludes()); |
| |
| Map<String, String> effectiveCompilerArguments = getCompilerArguments(); |
| |
| String effectiveCompilerArgument = getCompilerArgument(); |
| |
| if ((effectiveCompilerArguments != null) || (effectiveCompilerArgument != null) || (compilerArgs != null)) { |
| if (effectiveCompilerArguments != null) { |
| for (Map.Entry<String, String> me : effectiveCompilerArguments.entrySet()) { |
| String key = me.getKey(); |
| String value = me.getValue(); |
| if (!key.startsWith("-")) { |
| key = "-" + key; |
| } |
| |
| if (key.startsWith("-A") && StringUtils.isNotEmpty(value)) { |
| compilerConfiguration.addCompilerCustomArgument(key + "=" + value, null); |
| } else { |
| compilerConfiguration.addCompilerCustomArgument(key, value); |
| } |
| } |
| } |
| if (!StringUtils.isEmpty(effectiveCompilerArgument)) { |
| compilerConfiguration.addCompilerCustomArgument(effectiveCompilerArgument, null); |
| } |
| if (compilerArgs != null) { |
| for (String arg : compilerArgs) { |
| compilerConfiguration.addCompilerCustomArgument(arg, null); |
| } |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Dump configuration |
| // ---------------------------------------------------------------------- |
| if (getLog().isDebugEnabled()) { |
| getLog().debug("Classpath:"); |
| |
| for (String s : getClasspathElements()) { |
| getLog().debug(" " + s); |
| } |
| |
| if (!getModulepathElements().isEmpty()) { |
| getLog().debug("Modulepath:"); |
| for (String s : getModulepathElements()) { |
| getLog().debug(" " + s); |
| } |
| } |
| |
| getLog().debug("Source roots:"); |
| |
| for (String root : getCompileSourceRoots()) { |
| getLog().debug(" " + root); |
| } |
| |
| try { |
| if (fork) { |
| if (compilerConfiguration.getExecutable() != null) { |
| getLog().debug("Excutable: "); |
| getLog().debug(" " + compilerConfiguration.getExecutable()); |
| } |
| } |
| |
| String[] cl = compiler.createCommandLine(compilerConfiguration); |
| if (cl != null && cl.length > 0) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(cl[0]); |
| for (int i = 1; i < cl.length; i++) { |
| sb.append(" "); |
| sb.append(cl[i]); |
| } |
| getLog().debug("Command line options:"); |
| getLog().debug(sb); |
| } |
| } catch (CompilerException ce) { |
| getLog().debug(ce); |
| } |
| } |
| |
| List<String> jpmsLines = new ArrayList<>(); |
| |
| // See http://openjdk.java.net/jeps/261 |
| final List<String> runtimeArgs = Arrays.asList( |
| "--upgrade-module-path", "--add-exports", "--add-reads", "--add-modules", "--limit-modules"); |
| |
| // Custom arguments are all added as keys to an ordered Map |
| Iterator<Map.Entry<String, String>> entryIter = |
| compilerConfiguration.getCustomCompilerArgumentsEntries().iterator(); |
| while (entryIter.hasNext()) { |
| Map.Entry<String, String> entry = entryIter.next(); |
| |
| if (runtimeArgs.contains(entry.getKey())) { |
| jpmsLines.add(entry.getKey()); |
| |
| String value = entry.getValue(); |
| if (value == null) { |
| entry = entryIter.next(); |
| value = entry.getKey(); |
| } |
| jpmsLines.add(value); |
| } else if ("--patch-module".equals(entry.getKey())) { |
| String value = entry.getValue(); |
| if (value == null) { |
| entry = entryIter.next(); |
| value = entry.getKey(); |
| } |
| |
| String[] values = value.split("="); |
| |
| StringBuilder patchModule = new StringBuilder(values[0]); |
| patchModule.append('='); |
| |
| Set<String> patchModules = new LinkedHashSet<>(); |
| Set<Path> sourceRoots = new HashSet<>(getCompileSourceRoots().size()); |
| for (String sourceRoot : getCompileSourceRoots()) { |
| sourceRoots.add(Paths.get(sourceRoot)); |
| } |
| |
| String[] files = values[1].split(PS); |
| |
| for (String file : files) { |
| Path filePath = Paths.get(file); |
| if (getOutputDirectory().toPath().equals(filePath)) { |
| patchModules.add("_"); // this jar |
| } else if (getOutputDirectory().toPath().startsWith(filePath)) { |
| // multirelease, can be ignored |
| continue; |
| } else if (sourceRoots.contains(filePath)) { |
| patchModules.add("_"); // this jar |
| } else { |
| JavaModuleDescriptor descriptor = getPathElements().get(file); |
| |
| if (descriptor == null) { |
| if (Files.isDirectory(filePath)) { |
| patchModules.add(file); |
| } else { |
| getLog().warn("Can't locate " + file); |
| } |
| } else if (!values[0].equals(descriptor.name())) { |
| patchModules.add(descriptor.name()); |
| } |
| } |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| if (!patchModules.isEmpty()) { |
| for (String mod : patchModules) { |
| if (sb.length() > 0) { |
| sb.append(", "); |
| } |
| // use 'invalid' separator to ensure values are transformed |
| sb.append(mod); |
| } |
| |
| jpmsLines.add("--patch-module"); |
| jpmsLines.add(patchModule + sb.toString()); |
| } |
| } |
| } |
| |
| if (!jpmsLines.isEmpty()) { |
| Path jpmsArgs = Paths.get(getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args"); |
| try { |
| Files.createDirectories(jpmsArgs.getParent()); |
| |
| Files.write(jpmsArgs, jpmsLines, Charset.defaultCharset()); |
| } catch (IOException e) { |
| getLog().warn(e.getMessage()); |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Compile! |
| // ---------------------------------------------------------------------- |
| |
| if (StringUtils.isEmpty(compilerConfiguration.getSourceEncoding())) { |
| getLog().warn("File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING |
| + ", i.e. build is platform dependent!"); |
| } |
| |
| CompilerResult compilerResult; |
| |
| if (useIncrementalCompilation) { |
| incrementalBuildHelperRequest.outputDirectory(getOutputDirectory()); |
| |
| incrementalBuildHelper.beforeRebuildExecution(incrementalBuildHelperRequest); |
| |
| getLog().debug("incrementalBuildHelper#beforeRebuildExecution"); |
| } |
| |
| try { |
| compilerResult = compiler.performCompile(compilerConfiguration); |
| } catch (Exception e) { |
| // TODO: don't catch Exception |
| throw new MojoExecutionException("Fatal error compiling", e); |
| } |
| |
| if (createMissingPackageInfoClass |
| && compilerResult.isSuccess() |
| && compiler.getCompilerOutputStyle() == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) { |
| try { |
| SourceMapping sourceMapping = getSourceMapping(compilerConfiguration, compiler); |
| createMissingPackageInfoClasses(compilerConfiguration, sourceMapping, sources); |
| } catch (Exception e) { |
| getLog().warn("Error creating missing package info classes", e); |
| } |
| } |
| |
| if (useIncrementalCompilation) { |
| if (incrementalBuildHelperRequest.getOutputDirectory().exists()) { |
| getLog().debug("incrementalBuildHelper#afterRebuildExecution"); |
| // now scan the same directory again and create a diff |
| incrementalBuildHelper.afterRebuildExecution(incrementalBuildHelperRequest); |
| } else { |
| getLog().debug( |
| "skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist"); |
| } |
| } |
| |
| List<CompilerMessage> warnings = new ArrayList<>(); |
| List<CompilerMessage> errors = new ArrayList<>(); |
| List<CompilerMessage> others = new ArrayList<>(); |
| for (CompilerMessage message : compilerResult.getCompilerMessages()) { |
| if (message.getKind() == CompilerMessage.Kind.ERROR) { |
| errors.add(message); |
| } else if (message.getKind() == CompilerMessage.Kind.WARNING |
| || message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING) { |
| warnings.add(message); |
| } else { |
| others.add(message); |
| } |
| } |
| |
| if (failOnError && !compilerResult.isSuccess()) { |
| for (CompilerMessage message : others) { |
| assert message.getKind() != CompilerMessage.Kind.ERROR |
| && message.getKind() != CompilerMessage.Kind.WARNING |
| && message.getKind() != CompilerMessage.Kind.MANDATORY_WARNING; |
| getLog().info(message.toString()); |
| } |
| if (!warnings.isEmpty()) { |
| getLog().info("-------------------------------------------------------------"); |
| getLog().warn("COMPILATION WARNING : "); |
| getLog().info("-------------------------------------------------------------"); |
| for (CompilerMessage warning : warnings) { |
| getLog().warn(warning.toString()); |
| } |
| getLog().info(warnings.size() + ((warnings.size() > 1) ? " warnings " : " warning")); |
| getLog().info("-------------------------------------------------------------"); |
| } |
| |
| if (!errors.isEmpty()) { |
| getLog().info("-------------------------------------------------------------"); |
| getLog().error("COMPILATION ERROR : "); |
| getLog().info("-------------------------------------------------------------"); |
| for (CompilerMessage error : errors) { |
| getLog().error(error.toString()); |
| } |
| getLog().info(errors.size() + ((errors.size() > 1) ? " errors " : " error")); |
| getLog().info("-------------------------------------------------------------"); |
| } |
| |
| if (!errors.isEmpty()) { |
| throw new CompilationFailureException(errors); |
| } else { |
| throw new CompilationFailureException(warnings); |
| } |
| } else { |
| for (CompilerMessage message : compilerResult.getCompilerMessages()) { |
| switch (message.getKind()) { |
| case NOTE: |
| case OTHER: |
| getLog().info(message.toString()); |
| break; |
| |
| case ERROR: |
| getLog().error(message.toString()); |
| break; |
| |
| case MANDATORY_WARNING: |
| case WARNING: |
| default: |
| getLog().warn(message.toString()); |
| break; |
| } |
| } |
| } |
| } |
| |
| private void createMissingPackageInfoClasses( |
| CompilerConfiguration compilerConfiguration, SourceMapping sourceMapping, Set<File> sources) |
| throws InclusionScanException, IOException { |
| for (File source : sources) { |
| String path = source.toString(); |
| if (path.endsWith(File.separator + "package-info.java")) { |
| for (String root : getCompileSourceRoots()) { |
| root = root + File.separator; |
| if (path.startsWith(root)) { |
| String rel = path.substring(root.length()); |
| Set<File> files = sourceMapping.getTargetFiles(getOutputDirectory(), rel); |
| for (File file : files) { |
| if (!file.exists()) { |
| File parentFile = file.getParentFile(); |
| |
| if (!parentFile.exists()) { |
| Files.createDirectories(parentFile.toPath()); |
| } |
| |
| byte[] bytes = generatePackage(compilerConfiguration, rel); |
| Files.write(file.toPath(), bytes); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private byte[] generatePackage(CompilerConfiguration compilerConfiguration, String javaFile) { |
| int version = getOpcode(compilerConfiguration); |
| String internalPackageName = javaFile.substring(0, javaFile.length() - ".java".length()); |
| if (File.separatorChar != '/') { |
| internalPackageName = internalPackageName.replace(File.separatorChar, '/'); |
| } |
| ClassWriter cw = new ClassWriter(0); |
| cw.visit( |
| version, |
| Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE, |
| internalPackageName, |
| null, |
| "java/lang/Object", |
| null); |
| cw.visitSource("package-info.java", null); |
| return cw.toByteArray(); |
| } |
| |
| private int getOpcode(CompilerConfiguration compilerConfiguration) { |
| String version = compilerConfiguration.getReleaseVersion(); |
| if (version == null) { |
| version = compilerConfiguration.getTargetVersion(); |
| if (version == null) { |
| version = "1.5"; |
| } |
| } |
| if (version.startsWith("1.")) { |
| version = version.substring(2); |
| } |
| int iVersion = Integer.parseInt(version); |
| if (iVersion < 2) { |
| throw new IllegalArgumentException("Unsupported java version '" + version + "'"); |
| } |
| return iVersion - 2 + Opcodes.V1_2; |
| } |
| |
| protected boolean isTestCompile() { |
| return false; |
| } |
| |
| /** |
| * @return all source files for the compiler |
| */ |
| private Set<File> getCompileSources(Compiler compiler, CompilerConfiguration compilerConfiguration) |
| throws MojoExecutionException, CompilerException { |
| String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration); |
| if (StringUtils.isEmpty(inputFileEnding)) { |
| // see MCOMPILER-199 GroovyEclipseCompiler doesn't set inputFileEnding |
| // so we can presume it's all files from the source directory |
| inputFileEnding = ".*"; |
| } |
| SourceInclusionScanner scanner = getSourceInclusionScanner(inputFileEnding); |
| |
| SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler); |
| |
| scanner.addSourceMapping(mapping); |
| |
| Set<File> compileSources = new HashSet<>(); |
| |
| for (String sourceRoot : getCompileSourceRoots()) { |
| File rootFile = new File(sourceRoot); |
| |
| if (!rootFile.isDirectory() |
| || rootFile.getAbsoluteFile().equals(compilerConfiguration.getGeneratedSourcesDirectory())) { |
| continue; |
| } |
| |
| try { |
| compileSources.addAll(scanner.getIncludedSources(rootFile, null)); |
| } catch (InclusionScanException e) { |
| throw new MojoExecutionException( |
| "Error scanning source root: '" + sourceRoot + "' for stale files to recompile.", e); |
| } |
| } |
| |
| return compileSources; |
| } |
| |
| protected abstract Set<String> getIncludes(); |
| |
| protected abstract Set<String> getExcludes(); |
| |
| /** |
| * @param compilerConfiguration |
| * @param compiler |
| * @return <code>true</code> if at least a single source file is newer than it's class file |
| */ |
| private boolean isSourceChanged(CompilerConfiguration compilerConfiguration, Compiler compiler) |
| throws CompilerException, MojoExecutionException { |
| Set<File> staleSources = |
| computeStaleSources(compilerConfiguration, compiler, getSourceInclusionScanner(staleMillis)); |
| |
| if (getLog().isDebugEnabled() || showCompilationChanges) { |
| for (File f : staleSources) { |
| if (showCompilationChanges) { |
| getLog().info("Stale source detected: " + f.getAbsolutePath()); |
| } else { |
| getLog().debug("Stale source detected: " + f.getAbsolutePath()); |
| } |
| } |
| } |
| return !staleSources.isEmpty(); |
| } |
| |
| /** |
| * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependent |
| * |
| * @return number of thread for this build or 1 if not multi-thread build |
| */ |
| protected int getRequestThreadCount() { |
| return session.getRequest().getDegreeOfConcurrency(); |
| } |
| |
| protected Date getBuildStartTime() { |
| MavenExecutionRequest request = session.getRequest(); |
| Date buildStartTime = request == null ? new Date() : request.getStartTime(); |
| return buildStartTime == null ? new Date() : buildStartTime; |
| } |
| |
| private String getMemoryValue(String setting) { |
| String value = null; |
| |
| // Allow '128' or '128m' |
| if (isDigits(setting)) { |
| value = setting + "m"; |
| } else if ((isDigits(setting.substring(0, setting.length() - 1))) |
| && (setting.toLowerCase().endsWith("m"))) { |
| value = setting; |
| } |
| return value; |
| } |
| |
| // TODO remove the part with ToolchainManager lookup once we depend on |
| // 3.0.9 (have it as prerequisite). Define as regular component field then. |
| protected final Toolchain getToolchain() { |
| Toolchain tc = null; |
| |
| if (jdkToolchain != null) { |
| // Maven 3.3.1 has plugin execution scoped Toolchain Support |
| try { |
| Method getToolchainsMethod = toolchainManager |
| .getClass() |
| .getMethod("getToolchains", MavenSession.class, String.class, Map.class); |
| |
| @SuppressWarnings("unchecked") |
| List<Toolchain> tcs = |
| (List<Toolchain>) getToolchainsMethod.invoke(toolchainManager, session, "jdk", jdkToolchain); |
| |
| if (tcs != null && !tcs.isEmpty()) { |
| tc = tcs.get(0); |
| } |
| } catch (NoSuchMethodException |
| | SecurityException |
| | IllegalAccessException |
| | IllegalArgumentException |
| | InvocationTargetException e) { |
| // ignore |
| } |
| } |
| |
| if (tc == null) { |
| tc = toolchainManager.getToolchainFromBuildContext("jdk", session); |
| } |
| |
| return tc; |
| } |
| |
| private boolean isDigits(String string) { |
| for (int i = 0; i < string.length(); i++) { |
| if (!Character.isDigit(string.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private Set<File> computeStaleSources( |
| CompilerConfiguration compilerConfiguration, Compiler compiler, SourceInclusionScanner scanner) |
| throws MojoExecutionException, CompilerException { |
| SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler); |
| |
| File outputDirectory; |
| CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle(); |
| if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) { |
| outputDirectory = buildDirectory; |
| } else { |
| outputDirectory = getOutputDirectory(); |
| } |
| |
| scanner.addSourceMapping(mapping); |
| |
| Set<File> staleSources = new HashSet<>(); |
| |
| for (String sourceRoot : getCompileSourceRoots()) { |
| File rootFile = new File(sourceRoot); |
| |
| if (!rootFile.isDirectory()) { |
| continue; |
| } |
| |
| try { |
| staleSources.addAll(scanner.getIncludedSources(rootFile, outputDirectory)); |
| } catch (InclusionScanException e) { |
| throw new MojoExecutionException( |
| "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e); |
| } |
| } |
| |
| return staleSources; |
| } |
| |
| private SourceMapping getSourceMapping(CompilerConfiguration compilerConfiguration, Compiler compiler) |
| throws CompilerException, MojoExecutionException { |
| CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle(); |
| |
| SourceMapping mapping; |
| if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) { |
| mapping = new SuffixMapping( |
| compiler.getInputFileEnding(compilerConfiguration), |
| compiler.getOutputFileEnding(compilerConfiguration)); |
| } else if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) { |
| mapping = new SingleTargetSourceMapping( |
| compiler.getInputFileEnding(compilerConfiguration), compiler.getOutputFile(compilerConfiguration)); |
| |
| } else { |
| throw new MojoExecutionException("Unknown compiler output style: '" + outputStyle + "'."); |
| } |
| return mapping; |
| } |
| |
| /** |
| * @todo also in ant plugin. This should be resolved at some point so that it does not need to |
| * be calculated continuously - or should the plugins accept empty source roots as is? |
| */ |
| private static List<String> removeEmptyCompileSourceRoots(List<String> compileSourceRootsList) { |
| List<String> newCompileSourceRootsList = new ArrayList<>(); |
| if (compileSourceRootsList != null) { |
| // copy as I may be modifying it |
| for (String srcDir : compileSourceRootsList) { |
| if (!newCompileSourceRootsList.contains(srcDir) && new File(srcDir).exists()) { |
| newCompileSourceRootsList.add(srcDir); |
| } |
| } |
| } |
| return newCompileSourceRootsList; |
| } |
| |
| /** |
| * We just compare the timestamps of all local dependency files (inter-module dependency classpath) and the own |
| * generated classes and if we got a file which is >= the build-started timestamp, then we caught a file which |
| * got changed during this build. |
| * |
| * @return <code>true</code> if at least one single dependency has changed. |
| */ |
| protected boolean isDependencyChanged() { |
| if (session == null) { |
| // we just cannot determine it, so don't do anything beside logging |
| getLog().info("Cannot determine build start date, skipping incremental build detection."); |
| return false; |
| } |
| |
| if (fileExtensions == null || fileExtensions.isEmpty()) { |
| fileExtensions = Collections.unmodifiableList(Arrays.asList("class", "jar")); |
| } |
| |
| Date buildStartTime = getBuildStartTime(); |
| |
| List<String> pathElements = new ArrayList<>(); |
| pathElements.addAll(getClasspathElements()); |
| pathElements.addAll(getModulepathElements()); |
| |
| for (String pathElement : pathElements) { |
| File artifactPath = new File(pathElement); |
| if (artifactPath.isDirectory() || artifactPath.isFile()) { |
| if (!artifactPath.equals(getOutputDirectory()) && hasNewFile(artifactPath, buildStartTime)) { |
| if (showCompilationChanges) { |
| getLog().info("New dependency detected: " + artifactPath.getAbsolutePath()); |
| } else { |
| getLog().debug("New dependency detected: " + artifactPath.getAbsolutePath()); |
| } |
| return true; |
| } |
| } |
| } |
| |
| // obviously there was no new file detected. |
| return false; |
| } |
| |
| /** |
| * @param classPathEntry entry to check |
| * @param buildStartTime time build start |
| * @return if any changes occurred |
| */ |
| private boolean hasNewFile(File classPathEntry, Date buildStartTime) { |
| if (!classPathEntry.exists()) { |
| return false; |
| } |
| |
| if (classPathEntry.isFile()) { |
| return classPathEntry.lastModified() >= buildStartTime.getTime() |
| && fileExtensions.contains(FileUtils.getExtension(classPathEntry.getName())); |
| } |
| |
| File[] children = classPathEntry.listFiles(); |
| |
| for (File child : children) { |
| if (hasNewFile(child, buildStartTime)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private List<String> resolveProcessorPathEntries() throws MojoExecutionException { |
| if (annotationProcessorPaths == null || annotationProcessorPaths.isEmpty()) { |
| return null; |
| } |
| |
| Set<String> elements = new LinkedHashSet<>(); |
| try { |
| List<Dependency> dependencies = convertToDependencies(annotationProcessorPaths); |
| CollectRequest collectRequest = |
| new CollectRequest(dependencies, Collections.emptyList(), project.getRemoteProjectRepositories()); |
| DependencyRequest dependencyRequest = new DependencyRequest(); |
| dependencyRequest.setCollectRequest(collectRequest); |
| DependencyResult dependencyResult = |
| repositorySystem.resolveDependencies(session.getRepositorySession(), dependencyRequest); |
| |
| for (ArtifactResult resolved : dependencyResult.getArtifactResults()) { |
| elements.add(resolved.getArtifact().getFile().getAbsolutePath()); |
| } |
| return new ArrayList<>(elements); |
| } catch (Exception e) { |
| throw new MojoExecutionException( |
| "Resolution of annotationProcessorPath dependencies failed: " + e.getLocalizedMessage(), e); |
| } |
| } |
| |
| private List<Dependency> convertToDependencies(List<DependencyCoordinate> annotationProcessorPaths) { |
| List<Dependency> dependencies = new ArrayList<>(); |
| for (DependencyCoordinate annotationProcessorPath : annotationProcessorPaths) { |
| ArtifactHandler handler = artifactHandlerManager.getArtifactHandler(annotationProcessorPath.getType()); |
| Artifact artifact = new DefaultArtifact( |
| annotationProcessorPath.getGroupId(), |
| annotationProcessorPath.getArtifactId(), |
| annotationProcessorPath.getClassifier(), |
| handler.getExtension(), |
| annotationProcessorPath.getVersion()); |
| Set<Exclusion> exclusions = convertToAetherExclusions(annotationProcessorPath.getExclusions()); |
| dependencies.add(new Dependency(artifact, JavaScopes.RUNTIME, false, exclusions)); |
| } |
| return dependencies; |
| } |
| |
| private Set<Exclusion> convertToAetherExclusions(Set<DependencyExclusion> exclusions) { |
| if (exclusions == null || exclusions.isEmpty()) { |
| return Collections.emptySet(); |
| } |
| Set<Exclusion> aetherExclusions = new HashSet<>(); |
| for (DependencyExclusion exclusion : exclusions) { |
| Exclusion aetherExclusion = new Exclusion( |
| exclusion.getGroupId(), |
| exclusion.getArtifactId(), |
| exclusion.getClassifier(), |
| exclusion.getExtension()); |
| aetherExclusions.add(aetherExclusion); |
| } |
| return aetherExclusions; |
| } |
| |
| private void writePlugin(MessageBuilder mb) { |
| mb.a(" <plugin>").newline(); |
| mb.a(" <groupId>org.apache.maven.plugins</groupId>").newline(); |
| mb.a(" <artifactId>maven-compiler-plugin</artifactId>").newline(); |
| |
| String version = getMavenCompilerPluginVersion(); |
| if (version != null) { |
| mb.a(" <version>").a(version).a("</version>").newline(); |
| } |
| writeConfig(mb); |
| |
| mb.a(" </plugin>").newline(); |
| } |
| |
| private void writeConfig(MessageBuilder mb) { |
| mb.a(" <configuration>").newline(); |
| |
| if (release != null) { |
| mb.a(" <release>").a(release).a("</release>").newline(); |
| } else if (JavaVersion.JAVA_VERSION.isAtLeast("9")) { |
| String rls = target.replaceAll(".\\.", ""); |
| // when using Java9+, motivate to use release instead of source/target |
| mb.a(" <release>").a(rls).a("</release>").newline(); |
| } else { |
| mb.a(" <source>").a(source).a("</source>").newline(); |
| mb.a(" <target>").a(target).a("</target>").newline(); |
| } |
| mb.a(" </configuration>").newline(); |
| } |
| |
| private String getMavenCompilerPluginVersion() { |
| Properties pomProperties = new Properties(); |
| |
| try (InputStream is = AbstractCompilerMojo.class.getResourceAsStream( |
| "/META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties")) { |
| if (is != null) { |
| pomProperties.load(is); |
| } |
| } catch (IOException e) { |
| // noop |
| } |
| |
| return pomProperties.getProperty("version"); |
| } |
| |
| private DirectoryScanResult computeInputFileTreeChanges(IncrementalBuildHelper ibh, Set<File> inputFiles) |
| throws MojoExecutionException { |
| File mojoConfigBase = ibh.getMojoStatusDirectory(); |
| File mojoConfigFile = new File(mojoConfigBase, INPUT_FILES_LST_FILENAME); |
| |
| String[] oldInputFiles = new String[0]; |
| |
| if (mojoConfigFile.exists()) { |
| try { |
| oldInputFiles = FileUtils.fileReadArray(mojoConfigFile); |
| } catch (IOException e) { |
| throw new MojoExecutionException("Error reading old mojo status " + mojoConfigFile, e); |
| } |
| } |
| |
| String[] inputFileNames = inputFiles.stream().map(File::getAbsolutePath).toArray(String[]::new); |
| |
| DirectoryScanResult dsr = DirectoryScanner.diffFiles(oldInputFiles, inputFileNames); |
| |
| try { |
| FileUtils.fileWriteArray(mojoConfigFile, inputFileNames); |
| } catch (IOException e) { |
| throw new MojoExecutionException("Error while storing the mojo status", e); |
| } |
| |
| return dsr; |
| } |
| |
| private boolean hasInputFileTreeChanged(DirectoryScanResult dsr) { |
| return (dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0); |
| } |
| |
| public void setTarget(String target) { |
| this.target = target; |
| targetOrReleaseSet = true; |
| } |
| |
| public void setRelease(String release) { |
| this.release = release; |
| targetOrReleaseSet = true; |
| } |
| |
| final String getImplicit() { |
| return implicit; |
| } |
| } |