/*
* 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.codehaus.gmavenplus.model.Version;
import org.codehaus.gmavenplus.util.ClassWrangler;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.security.CodeSource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.codehaus.gmavenplus.util.ReflectionUtils.*;
/**
* The base compile mojo, which all compile mojos extend.
*
* @author Keegan Witt
* @since 1.0-beta-1
*/
public abstract class AbstractCompileMojo extends AbstractGroovySourcesMojo {
/**
* Groovy 2.3.3 version.
*/
protected static final Version GROOVY_2_3_3 = new Version(2, 3, 3);
/**
* Groovy 2.1.3 version.
*/
protected static final Version GROOVY_2_1_3 = new Version(2, 1, 3);
/**
* Groovy 2.1.0 beta-1 version.
*/
protected static final Version GROOVY_2_1_0_BETA1 = new Version(2, 1, 0, "beta-1");
/**
* Groovy 2.0.0 beta-3 version.
*/
protected static final Version GROOVY_2_0_0_BETA3 = new Version(2, 0, 0, "beta-3");
/**
* Groovy 1.6.0 version.
*/
protected static final Version GROOVY_1_6_0 = new Version(1, 6, 0);
/**
* The location for the compiled classes.
*
* @parameter default-value="${project.build.outputDirectory}"
*/
protected File outputDirectory;
/**
* The location for the compiled test classes.
*
* @parameter default-value="${project.build.testOutputDirectory}"
*/
protected File testOutputDirectory;
/**
* The encoding of source files.
*
* @parameter default-value="${project.build.sourceEncoding}"
*/
protected String sourceEncoding;
/**
* The Groovy compiler bytecode compatibility. One of
* <ul>
* <li>1.4</li>
* <li>1.5</li>
* <li>1.6</li>
* <li>1.7</li>
* <li>1.8</li>
* </ul>
* Using 1.6 or 1.7 requires Groovy >= 2.1.3, and using 1.8 requires Groovy >= 2.3.3.
*
* @parameter property="maven.compiler.target" default-value="1.5"
*/
protected String targetBytecode;
/**
* Whether Groovy compiler should be set to debug.
*
* @parameter default-value="false"
*/
protected boolean debug;
/**
* Whether Groovy compiler should be set to verbose.
*
* @parameter default-value="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 default-value="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 default-value="0"
*/
protected int tolerance;
/**
* Whether to support invokeDynamic (requires Java 7 or greater and Groovy
* indy 2.0.0-beta-3 or greater).
*
* @parameter property="invokeDynamic" default-value="false"
*/
protected boolean invokeDynamic;
/**
* 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 property="configScript"
*/
protected File configScript;
/**
* 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("unchecked")
protected synchronized void doCompile(final Set<File> sources, final List classpath, final File compileOutputDirectory)
throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, MalformedURLException {
classWrangler = new ClassWrangler(classpath, getLog());
logPluginClasspath();
classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
if (sources == null || sources.isEmpty()) {
getLog().info("No sources specified for compilation. Skipping.");
return;
}
if (groovyVersionSupportsAction()) {
verifyGroovyVersionSupportsTargetBytecode();
} else {
getLog().error("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support compilation. The minimum version of Groovy required is " + minGroovyVersion + ". Skipping compiling.");
return;
}
// get classes we need with reflection
Class<?> compilerConfigurationClass = classWrangler.getClass("org.codehaus.groovy.control.CompilerConfiguration");
Class<?> compilationUnitClass = classWrangler.getClass("org.codehaus.groovy.control.CompilationUnit");
Class<?> groovyClassLoaderClass = classWrangler.getClass("groovy.lang.GroovyClassLoader");
// setup compile options
Object compilerConfiguration = setupCompilerConfiguration(compileOutputDirectory, compilerConfigurationClass);
Object groovyClassLoader = invokeConstructor(findConstructor(groovyClassLoaderClass, ClassLoader.class, compilerConfigurationClass), classWrangler.getClassLoader(), compilerConfiguration);
Object transformLoader = invokeConstructor(findConstructor(groovyClassLoaderClass, ClassLoader.class), classWrangler.getClassLoader());
// add Groovy sources
Object compilationUnit = setupCompilationUnit(sources, compilerConfigurationClass, compilationUnitClass, groovyClassLoaderClass, compilerConfiguration, groovyClassLoader, transformLoader);
// compile the classes
invokeMethod(findMethod(compilationUnitClass, "compile"), compilationUnit);
// log compiled classes
List classes = (List) invokeMethod(findMethod(compilationUnitClass, "getClasses"), compilationUnit);
getLog().info("Compiled " + classes.size() + " file" + (classes.size() > 1 || classes.size() == 0 ? "s" : "") + ".");
}
/**
* Sets up the CompilationUnit to use for compilation.
*
* @param sources the sources to compile
* @param compilerConfigurationClass the CompilerConfiguration class
* @param compilationUnitClass the CompilationUnit class
* @param groovyClassLoaderClass the GroovyClassLoader class
* @param compilerConfiguration the CompilerConfiguration
* @param groovyClassLoader the GroovyClassLoader
* @param transformLoader the GroovyClassLoader to use for transformation
* @return the CompilationUnit
* @throws InstantiationException when a class needed for setting up compilation unit cannot be instantiated
* @throws IllegalAccessException when a method needed for setting up compilation unit cannot be accessed
* @throws InvocationTargetException when a reflection invocation needed for setting up compilation unit cannot be completed
*/
protected Object setupCompilationUnit(final Set<File> sources, final Class<?> compilerConfigurationClass, final Class<?> compilationUnitClass, final Class<?> groovyClassLoaderClass, final Object compilerConfiguration, final Object groovyClassLoader, final Object transformLoader) throws InvocationTargetException, IllegalAccessException, InstantiationException {
Object compilationUnit;
if (groovyAtLeast(GROOVY_1_6_0)) {
compilationUnit = invokeConstructor(findConstructor(compilationUnitClass, compilerConfigurationClass, CodeSource.class, groovyClassLoaderClass, groovyClassLoaderClass), compilerConfiguration, null, groovyClassLoader, transformLoader);
} else {
compilationUnit = invokeConstructor(findConstructor(compilationUnitClass, compilerConfigurationClass, CodeSource.class, groovyClassLoaderClass), compilerConfiguration, null, groovyClassLoader);
}
getLog().debug("Adding Groovy to compile:");
Method addSourceMethod = findMethod(compilationUnitClass, "addSource", File.class);
for (File source : sources) {
getLog().debug(" " + source);
invokeMethod(addSourceMethod, compilationUnit, source);
}
return compilationUnit;
}
/**
* Sets up the CompilationConfiguration to use for compilation.
*
* @param compileOutputDirectory the directory to write the compiled classes to
* @param compilerConfigurationClass the CompilerConfiguration class
* @return the CompilerConfiguration
* @throws ClassNotFoundException when a class needed for setting up CompilerConfiguration cannot be found
* @throws InstantiationException when a class needed for setting up CompilerConfiguration cannot be instantiated
* @throws IllegalAccessException when a method needed for setting up CompilerConfiguration cannot be accessed
* @throws InvocationTargetException when a reflection invocation needed for setting up CompilerConfiguration cannot be completed
*/
@SuppressWarnings("unchecked")
protected Object setupCompilerConfiguration(final File compileOutputDirectory, final Class<?> compilerConfigurationClass) throws InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
Object compilerConfiguration = invokeConstructor(findConstructor(compilerConfigurationClass));
if (configScript != null) {
if (groovyAtLeast(GROOVY_2_1_0_BETA1) && configScript.exists()) {
Class<?> bindingClass = classWrangler.getClass("groovy.lang.Binding");
Class<?> importCustomizerClass = classWrangler.getClass("org.codehaus.groovy.control.customizers.ImportCustomizer");
Class<?> groovyShellClass = classWrangler.getClass("groovy.lang.GroovyShell");
Object binding = invokeConstructor(findConstructor(bindingClass));
invokeMethod(findMethod(bindingClass, "setVariable", String.class, Object.class), binding, "configuration", compilerConfiguration);
Object shellCompilerConfiguration = invokeConstructor(findConstructor(compilerConfigurationClass));
Object importCustomizer = invokeConstructor(findConstructor(importCustomizerClass));
invokeMethod(findMethod(importCustomizerClass, "addStaticStar", String.class), importCustomizer, "org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder");
List compilationCustomizers = (List) invokeMethod(findMethod(compilerConfigurationClass, "getCompilationCustomizers"), shellCompilerConfiguration);
compilationCustomizers.add(importCustomizer);
Object shell = invokeConstructor(findConstructor(groovyShellClass, bindingClass, compilerConfigurationClass), binding, shellCompilerConfiguration);
getLog().debug("Using configuration script " + configScript + " for compilation.");
invokeMethod(findMethod(groovyShellClass, "evaluate", File.class), shell, configScript);
} else {
getLog().warn("Requested to use configScript, but your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support it (must be " + GROOVY_2_1_0_BETA1 + " or newer). Ignoring configScript parameter.");
}
}
invokeMethod(findMethod(compilerConfigurationClass, "setDebug", boolean.class), compilerConfiguration, debug);
invokeMethod(findMethod(compilerConfigurationClass, "setVerbose", boolean.class), compilerConfiguration, verbose);
invokeMethod(findMethod(compilerConfigurationClass, "setWarningLevel", int.class), compilerConfiguration, warningLevel);
invokeMethod(findMethod(compilerConfigurationClass, "setTolerance", int.class), compilerConfiguration, tolerance);
invokeMethod(findMethod(compilerConfigurationClass, "setTargetBytecode", String.class), compilerConfiguration, targetBytecode);
if (sourceEncoding != null) {
invokeMethod(findMethod(compilerConfigurationClass, "setSourceEncoding", String.class), compilerConfiguration, sourceEncoding);
}
invokeMethod(findMethod(compilerConfigurationClass, "setTargetDirectory", String.class), compilerConfiguration, compileOutputDirectory.getAbsolutePath());
if (invokeDynamic) {
if (groovyAtLeast(GROOVY_2_0_0_BETA3)) {
if (classWrangler.isGroovyIndy()) {
if (isJavaSupportIndy()) {
Map<String, Boolean> optimizationOptions = (Map<String, Boolean>) invokeMethod(findMethod(compilerConfigurationClass, "getOptimizationOptions"), compilerConfiguration);
optimizationOptions.put("indy", true);
optimizationOptions.put("int", false);
} else {
getLog().warn("Requested to use to use invokedynamic, but your Java version (" + getJavaVersionString() + ") doesn't support it. Ignoring invokeDynamic parameter.");
}
} else {
getLog().warn("Requested to use invokedynamic, but your Groovy version doesn't support it (must use have indy classifier). Ignoring invokeDynamic parameter.");
}
} else {
getLog().warn("Requested to use invokeDynamic, but your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support it (must be " + GROOVY_2_0_0_BETA3 + " or newer). Ignoring invokeDynamic parameter.");
}
}
return compilerConfiguration;
}
/**
* Throws an exception if targetBytecode is not supported with this version of Groovy.
*/
protected void verifyGroovyVersionSupportsTargetBytecode() {
if ("1.9".equals(targetBytecode)) {
throw new IllegalArgumentException("Target bytecode 1.9 is not yet supported.");
} else if ("1.8".equals(targetBytecode)) {
if (groovyOlderThan(GROOVY_2_3_3)) {
throw new IllegalArgumentException("Target bytecode 1.8 requires Groovy " + GROOVY_2_3_3 + ".");
}
} else if ("1.7".equals(targetBytecode) || "1.6".equals(targetBytecode)) {
if (groovyOlderThan(GROOVY_2_1_3)) {
throw new IllegalArgumentException("Target bytecode 1.6 and 1.7 require Groovy " + GROOVY_2_1_3 + ".");
}
} else if (!"1.5".equals(targetBytecode) && !"1.4".equals(targetBytecode)) {
throw new IllegalArgumentException("Unrecognized target bytecode.");
}
}
}