AbstractGenerateStubsMojo.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 org.codehaus.gmavenplus.model.internal.Version;
import org.codehaus.gmavenplus.util.GroovyCompiler;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.util.List;
import java.util.Set;
/**
* The base generate stubs mojo, which all generate stubs mojos extend.
*
* @author Keegan Witt
*/
public abstract class AbstractGenerateStubsMojo extends AbstractGroovyStubSourcesMojo {
/**
* Groovy 1.8.2 version.
*/
protected static final Version GROOVY_1_8_2 = new Version(1, 8, 2);
/**
* 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.
*
* @since 1.0-beta-3
*/
@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;
/**
* 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;
/**
* The Maven ToolchainManager.
*/
@javax.inject.Inject
protected org.apache.maven.toolchain.ToolchainManager toolchainManager;
/**
* The Maven Session.
*/
@Parameter(defaultValue = "${session}", readonly = true, required = true)
protected org.apache.maven.execution.MavenSession session;
/**
* Whether to execute in a forked process.
*
* @since 4.3.0
*/
@Parameter(property = "fork", defaultValue = "false")
protected boolean fork;
/**
* Performs the stub generation on the specified source files.
*
* @param stubSources the sources to perform stub generation on
* @param classpath The classpath to use for compilation
* @param outputDirectory the directory to write the stub files to
* @throws ClassNotFoundException when a class needed for stub generation cannot be found
* @throws InstantiationException when a class needed for stub generation cannot be instantiated
* @throws IllegalAccessException when a method needed for stub generation cannot be accessed
* @throws InvocationTargetException when a reflection invocation needed for stub generation cannot be completed
* @throws MalformedURLException when a classpath element provides a malformed URL
*/
protected synchronized void doStubGeneration(final Set<File> stubSources, final List<?> classpath, final File outputDirectory) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException, MalformedURLException {
if (stubSources == null || stubSources.isEmpty()) {
getLog().info("No sources specified for stub generation. Skipping.");
return;
}
org.codehaus.gmavenplus.model.GroovyStubConfiguration configuration = new org.codehaus.gmavenplus.model.GroovyStubConfiguration(stubSources, classpath, outputDirectory);
configuration.setIncludeClasspath(includeClasspath);
configuration.setSkipBytecodeCheck(skipBytecodeCheck);
configuration.setDebug(debug);
configuration.setVerbose(verbose);
configuration.setWarningLevel(warningLevel);
configuration.setTolerance(tolerance);
configuration.setSourceEncoding(sourceEncoding);
configuration.setTargetBytecode(targetBytecode);
org.apache.maven.toolchain.Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", session);
if (toolchain != null) {
getLog().info("Toolchain in gmavenplus-plugin: " + toolchain);
performForkedStubGeneration(configuration, toolchain.findTool("java"));
} else if (fork) {
String javaExecutable = getJavaExecutable();
getLog().info("Forking stub generation using " + javaExecutable);
performForkedStubGeneration(configuration, javaExecutable);
} else {
performInProcessStubGeneration(configuration, classpath);
}
}
protected void performInProcessStubGeneration(org.codehaus.gmavenplus.model.GroovyStubConfiguration configuration, List<?> classpath) throws MalformedURLException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
setupClassWrangler(classpath, includeClasspath);
logPluginClasspath();
classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
// Note: GroovyCompiler handles minGroovyVersion check now in generateStubs
GroovyCompiler compiler = new GroovyCompiler(classWrangler, getLog());
compiler.generateStubs(configuration);
}
protected void performForkedStubGeneration(org.codehaus.gmavenplus.model.GroovyStubConfiguration configuration, String javaExecutable) throws InvocationTargetException {
try {
// Write configuration to file
File configFile = File.createTempFile("groovy-stub-config", ".ser");
configFile.deleteOnExit();
try (java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(java.nio.file.Files.newOutputStream(configFile.toPath()))) {
oos.writeObject(configuration);
}
// Build classpath for forked process (plugin + dependencies)
String forkClasspath = buildForkClasspath();
List<String> command = new java.util.ArrayList<>();
command.add(javaExecutable);
command.add("-cp");
command.add(forkClasspath);
command.add("org.codehaus.gmavenplus.util.ForkedGroovyCompiler");
command.add(configFile.getAbsolutePath());
ProcessBuilder pb = new ProcessBuilder(command);
pb.inheritIO();
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new InvocationTargetException(new RuntimeException("Forked stub generation failed with exit code " + exitCode));
}
} catch (java.io.IOException | InterruptedException e) {
throw new InvocationTargetException(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();
}
/**
* Logs the stubs that have been generated.
*
* @param outputDirectory the output directory for the stubs
*/
protected void logGeneratedStubs(File outputDirectory) {
Set<File> stubs = getStubs(outputDirectory);
getLog().info("Generated " + stubs.size() + " stub" + (stubs.size() != 1 ? "s" : "") + ".");
}
/**
* This is a fix for <a href="http://jira.codehaus.org/browse/MGROOVY-187">...</a>
* It modifies the dates of the created stubs to 1/1/1970, ensuring that the Java compiler will not overwrite perfectly
* good compiled Groovy just because it has a newer source stub. Basically, this prevents the stubs from causing a
* side effect with the Java compiler, but still allows stubs to work with JavaDoc.
*
* @param stubs the files on which to reset the modified date
*/
protected void resetStubModifiedDates(final Set<File> stubs) {
for (File stub : stubs) {
boolean success = stub.setLastModified(0L);
if (!success) {
getLog().warn("Unable to set modified time on stub " + stub.getAbsolutePath() + ".");
}
}
}
}