AbstractCompileMojo.java
/*
* Copyright (C) 2011 the original author or authors.
*
* Licensed 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.codehaus.gmavenplus.mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.gmavenplus.model.IncludeClasspath;
import java.io.File;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.gmavenplus.model.GroovyCompileConfiguration;
import org.codehaus.gmavenplus.util.ForkedGroovyCompiler;
import org.codehaus.gmavenplus.util.GroovyCompiler;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* The base compile mojo, which all compile mojos extend.
*
* @author Keegan Witt
*/
public abstract class AbstractCompileMojo extends AbstractGroovySourcesMojo {
/**
* The encoding of source files.
*/
@Parameter(defaultValue = "${project.build.sourceEncoding}")
protected String sourceEncoding;
/**
* The Groovy compiler bytecode compatibility. One of
* <ul>
* <li>1.4 (or 4)</li>
* <li>1.5 (or 5)</li>
* <li>1.6 (or 6)</li>
* <li>1.7 (or 7)</li>
* <li>1.8 (or 8)</li>
* <li>9 (or 1.9)</li>
* <li>10</li>
* <li>11</li>
* <li>12</li>
* <li>13</li>
* <li>14</li>
* <li>15</li>
* <li>16</li>
* <li>17</li>
* <li>18</li>
* <li>19</li>
* <li>20</li>
* <li>21</li>
* <li>22</li>
* <li>23</li>
* <li>24</li>
* <li>25</li>
* </ul>
* Using 1.6 (or 6) or 1.7 (or 7) requires Groovy >= 2.1.3.
* Using 1.8 (or 8) requires Groovy >= 2.3.3.
* Using 9 (or 1.9) requires Groovy >= 2.5.3, or Groovy >= 2.6.0 alpha 4, or Groovy >= 3.0.0 alpha 2.
* Using 9 (or 1.9) with invokedynamic requires Groovy >= 2.5.3, or Groovy >= 3.0.0 alpha 2, but not any 2.6 versions.
* Using 10, 11, or 12 requires Groovy >= 2.5.3, or Groovy >= 3.0.0 alpha 4, but not any 2.6 versions.
* Using 13 requires Groovy >= 2.5.7, or Groovy >= 3.0.0-beta-1, but not any 2.6 versions.
* Using 14 requires Groovy >= 3.0.0 beta-2.
* Using 15 requires Groovy >= 3.0.3.
* Using 16 requires Groovy >= 3.0.6.
* Using 17 requires Groovy >= 3.0.8 or Groovy > 4.0.0-alpha-3.
* Using 18 requires Groovy > 4.0.0-beta-1.
* Using 19 requires Groovy > 4.0.2.
* Using 20 requires Groovy > 4.0.6.
* Using 21 requires Groovy > 4.0.11.
* Using 22 requires Groovy > 4.0.16 or Groovy > 5.0.0-alpha-3.
* Using 23 requires Groovy > 4.0.21 or Groovy > 5.0.0-alpha-8.
* Using 24 requires Groovy > 4.0.24 or Groovy > 5.0.0-alpha-11.
* Using 25 requires Groovy > 4.0.27 or Groovy > 5.0.0-alpha-13.
*/
@Parameter(property = "maven.compiler.target", defaultValue = "1.8")
protected String targetBytecode;
/**
* Whether to check that the version of Groovy used is able to use the requested <code>targetBytecode</code>.
*
* @since 1.9.0
*/
@Parameter(property = "skipBytecodeCheck", defaultValue = "false")
protected boolean skipBytecodeCheck;
/**
* Whether Groovy compiler should be set to debug.
*/
@Parameter(defaultValue = "false")
protected boolean debug;
/**
* Whether Groovy compiler should be set to verbose.
*/
@Parameter(defaultValue = "false")
protected boolean verbose;
/**
* Groovy compiler warning level. Should be one of:
* <dl>
* <dt>0</dt>
* <dd>None</dd>
* <dt>1</dt>
* <dd>Likely Errors</dd>
* <dt>2</dt>
* <dd>Possible Errors</dd>
* <dt>3</dt>
* <dd>Paranoia</dd>
* </dl>
*/
@Parameter(defaultValue = "1")
protected int warningLevel;
/**
* Groovy compiler error tolerance (the number of non-fatal errors (per unit) that should be tolerated before compilation is aborted).
*/
@Parameter(defaultValue = "0")
protected int tolerance;
/**
* Whether to support invokeDynamic (requires Java 7 or greater and Groovy indy 2.0.0-beta-3 or greater).
* Has no effect for Groovy 4, as it is always enabled.
*/
@Parameter(defaultValue = "false")
protected boolean invokeDynamic;
/**
* Whether to enable Groovy's parallel parsing. Requires Groovy 3.0.5.
* Is enabled by default for Groovy 4.0.0-alpha-1 or newer.
*
* @since 1.11.0
*/
@Parameter
protected Boolean parallelParsing = null;
/**
* A <a href="http://groovy-lang.org/dsls.html#compilation-customizers">script</a> for tweaking the configuration options
* (requires Groovy 2.1.0-beta-1 or greater). Note that its encoding must match your source encoding.
*/
@Parameter
protected File configScript;
/**
* Generate metadata for reflection on method parameter names using the functionality provided by JEP 118
* (requires Java 8 or greater and Groovy 2.5.0-alpha-1 or greater).
*/
@Parameter(defaultValue = "false")
protected boolean parameters;
/**
* What classpath to include. One of
* <ul>
* <li>PROJECT_ONLY</li>
* <li>PROJECT_AND_PLUGIN</li>
* <li>PLUGIN_ONLY</li>
* </ul>
* Uses the same scope as the required dependency resolution of this mojo. Use only if you know what you're doing.
*
* @since 1.8.0
*/
@Parameter(defaultValue = "PROJECT_ONLY")
protected IncludeClasspath includeClasspath;
/**
* Whether the bytecode version has preview features enabled (JEP 12).
* Requires Groovy >= 3.0.0-beta-1 or Groovy >= 2.5.7, but not any 2.6 versions and Java >= 12.
*
* @since 1.7.1
*/
@Parameter(defaultValue = "false")
protected boolean previewFeatures;
/**
* The ToolchainManager.
*/
@Component
protected ToolchainManager toolchainManager;
/**
* whether to fork the compilation.
*/
@Parameter(defaultValue = "false")
protected boolean fork;
/**
* Performs compilation of compile mojos.
*
* @param sources the sources to compile
* @param classpath the classpath to use for compilation
* @param compileOutputDirectory the directory to write the compiled class files to
* @throws ClassNotFoundException when a class needed for compilation cannot be found
* @throws InstantiationException when a class needed for compilation cannot be instantiated
* @throws IllegalAccessException when a method needed for compilation cannot be accessed
* @throws InvocationTargetException when a reflection invocation needed for compilation cannot be completed
* @throws MalformedURLException when a classpath element provides a malformed URL
*/
@SuppressWarnings({"rawtypes"})
protected synchronized void doCompile(final Set<File> sources, final List classpath, final File compileOutputDirectory)
throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, MalformedURLException {
GroovyCompileConfiguration configuration = new GroovyCompileConfiguration(sources, classpath, compileOutputDirectory);
configuration.setIncludeClasspath(includeClasspath);
configuration.setSkipBytecodeCheck(skipBytecodeCheck);
configuration.setDebug(debug);
configuration.setVerbose(verbose);
configuration.setWarningLevel(warningLevel);
configuration.setTolerance(tolerance);
configuration.setInvokeDynamic(invokeDynamic);
configuration.setParallelParsing(parallelParsing);
configuration.setConfigScript(configScript);
configuration.setParameters(parameters);
configuration.setPreviewFeatures(previewFeatures);
configuration.setSourceEncoding(sourceEncoding);
configuration.setTargetBytecode(targetBytecode);
Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", session);
if (toolchain != null) {
getLog().info("Toolchain in gmavenplus-plugin: " + toolchain);
performForkedCompilation(configuration, toolchain.findTool("java"));
} else if (fork) {
String javaExecutable = getJavaExecutable();
getLog().info("Forking compilation using " + javaExecutable);
performForkedCompilation(configuration, javaExecutable);
} else {
performInProcessCompilation(configuration, classpath);
}
}
protected void performInProcessCompilation(GroovyCompileConfiguration configuration, List<?> classpath) throws MalformedURLException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
setupClassWrangler(classpath, includeClasspath);
logPluginClasspath();
classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
if (!groovyVersionSupportsAction()) {
getLog().error("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support compilation. The minimum version of Groovy required is " + minGroovyVersion + ". Skipping compiling.");
return;
}
GroovyCompiler compiler = new GroovyCompiler(classWrangler, getLog());
compiler.compile(configuration);
}
protected void performForkedCompilation(GroovyCompileConfiguration configuration, String javaExecutable) {
if (javaExecutable == null) {
getLog().warn("Unable to find 'java' executable for toolchain. Falling back to in-process compilation.");
try {
performInProcessCompilation(configuration, configuration.getClasspath());
} catch (Exception e) {
throw new RuntimeException("Compilation failed", e);
}
return;
}
try {
File configFile = File.createTempFile("gmavenplus-compile-config", ".ser");
configFile.deleteOnExit();
try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(configFile.toPath()))) {
oos.writeObject(configuration);
}
List<String> command = new ArrayList<>();
command.add(javaExecutable);
command.add("-cp");
command.add(buildForkClasspath());
command.add(ForkedGroovyCompiler.class.getName());
command.add(configFile.getAbsolutePath());
getLog().info("Forking compilation using " + javaExecutable);
getLog().debug("Command: " + command);
ProcessBuilder pb = new ProcessBuilder(command);
pb.inheritIO();
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Groovy compilation failed with exit code " + exitCode);
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Unable to fork compilation", e);
}
}
protected String buildForkClasspath() {
StringBuilder cp = new StringBuilder();
// Add plugin artifact
cp.append(pluginDescriptor.getPluginArtifact().getFile().getAbsolutePath());
// Add plugin dependencies
for (org.apache.maven.artifact.Artifact artifact : pluginDescriptor.getArtifacts()) {
cp.append(File.pathSeparator);
cp.append(artifact.getFile().getAbsolutePath());
}
// Add maven-plugin-api jar which is 'provided' so not in getArtifacts()
try {
Class<?> logClass = org.apache.maven.plugin.logging.Log.class;
java.security.CodeSource codeSource = logClass.getProtectionDomain().getCodeSource();
if (codeSource != null) {
String logJar = new File(codeSource.getLocation().toURI()).getAbsolutePath();
cp.append(File.pathSeparator).append(logJar);
}
} catch (Exception e) {
getLog().warn("Could not find maven-plugin-api jar to add to fork classpath", e);
}
return cp.toString();
}
}