org.gradle.api.internal.tasks.compile.ApiGroovyCompiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2012 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.gradle.api.internal.tasks.compile;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyShell;
import groovy.lang.GroovySystem;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.control.messages.SimpleMessage;
import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit;
import org.codehaus.groovy.tools.javac.JavaCompiler;
import org.codehaus.groovy.tools.javac.JavaCompilerFactory;
import org.gradle.api.GradleException;
import org.gradle.api.internal.classloading.GroovySystemLoader;
import org.gradle.api.internal.classloading.GroovySystemLoaderFactory;
import org.gradle.api.internal.file.collections.ImmutableFileCollection;
import org.gradle.api.tasks.WorkResult;
import org.gradle.api.tasks.WorkResults;
import org.gradle.internal.classloader.ClassLoaderUtils;
import org.gradle.internal.classloader.DefaultClassLoaderFactory;
import org.gradle.internal.classloader.FilteringClassLoader;
import org.gradle.internal.classpath.DefaultClassPath;
import org.gradle.internal.concurrent.CompositeStoppable;
import org.gradle.language.base.internal.compile.Compiler;
import org.gradle.util.VersionNumber;
import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.gradle.internal.FileUtils.hasExtension;
public class ApiGroovyCompiler implements org.gradle.language.base.internal.compile.Compiler, Serializable {
private final Compiler javaCompiler;
public ApiGroovyCompiler(Compiler javaCompiler) {
this.javaCompiler = javaCompiler;
}
@Override
public WorkResult execute(final GroovyJavaJointCompileSpec spec) {
GroovySystemLoaderFactory groovySystemLoaderFactory = new GroovySystemLoaderFactory();
ClassLoader compilerClassLoader = this.getClass().getClassLoader();
GroovySystemLoader compilerGroovyLoader = groovySystemLoaderFactory.forClassLoader(compilerClassLoader);
CompilerConfiguration configuration = new CompilerConfiguration();
configuration.setVerbose(spec.getGroovyCompileOptions().isVerbose());
configuration.setSourceEncoding(spec.getGroovyCompileOptions().getEncoding());
configuration.setTargetBytecode(spec.getTargetCompatibility());
configuration.setTargetDirectory(spec.getDestinationDir());
canonicalizeValues(spec.getGroovyCompileOptions().getOptimizationOptions());
if (spec.getGroovyCompileOptions().getConfigurationScript() != null) {
applyConfigurationScript(spec.getGroovyCompileOptions().getConfigurationScript(), configuration);
}
try {
configuration.setOptimizationOptions(spec.getGroovyCompileOptions().getOptimizationOptions());
} catch (NoSuchMethodError ignored) { /* method was only introduced in Groovy 1.8 */ }
Map jointCompilationOptions = new HashMap();
final File stubDir = spec.getGroovyCompileOptions().getStubDir();
stubDir.mkdirs();
jointCompilationOptions.put("stubDir", stubDir);
jointCompilationOptions.put("keepStubs", spec.getGroovyCompileOptions().isKeepStubs());
configuration.setJointCompilationOptions(jointCompilationOptions);
ClassLoader classPathLoader;
VersionNumber version = parseGroovyVersion();
if (version.compareTo(VersionNumber.parse("2.0")) < 0) {
// using a transforming classloader is only required for older buggy Groovy versions
classPathLoader = new GroovyCompileTransformingClassLoader(getExtClassLoader(), DefaultClassPath.of(spec.getCompileClasspath()));
} else {
classPathLoader = new DefaultClassLoaderFactory().createIsolatedClassLoader(DefaultClassPath.of(spec.getCompileClasspath()));
}
GroovyClassLoader compileClasspathClassLoader = new GroovyClassLoader(classPathLoader, null);
GroovySystemLoader compileClasspathLoader = groovySystemLoaderFactory.forClassLoader(classPathLoader);
FilteringClassLoader.Spec groovyCompilerClassLoaderSpec = new FilteringClassLoader.Spec();
groovyCompilerClassLoaderSpec.allowPackage("org.codehaus.groovy");
groovyCompilerClassLoaderSpec.allowPackage("groovy");
// Disallow classes from Groovy Jar that reference external classes. Such classes must be loaded from astTransformClassLoader,
// or a NoClassDefFoundError will occur. Essentially this is drawing a line between the Groovy compiler and the Groovy
// library, albeit only for selected classes that run a high risk of being statically referenced from a transform.
groovyCompilerClassLoaderSpec.disallowClass("groovy.util.GroovyTestCase");
groovyCompilerClassLoaderSpec.disallowPackage("groovy.servlet");
FilteringClassLoader groovyCompilerClassLoader = new FilteringClassLoader(GroovyClassLoader.class.getClassLoader(), groovyCompilerClassLoaderSpec);
// AST transforms need their own class loader that shares compiler classes with the compiler itself
final GroovyClassLoader astTransformClassLoader = new GroovyClassLoader(groovyCompilerClassLoader, null);
// can't delegate to compileClasspathLoader because this would result in ASTTransformation interface
// (which is implemented by the transform class) being loaded by compileClasspathClassLoader (which is
// where the transform class is loaded from)
for (File file : spec.getCompileClasspath()) {
astTransformClassLoader.addClasspath(file.getPath());
}
JavaAwareCompilationUnit unit = new JavaAwareCompilationUnit(configuration, compileClasspathClassLoader) {
@Override
public GroovyClassLoader getTransformLoader() {
return astTransformClassLoader;
}
};
final boolean shouldProcessAnnotations = shouldProcessAnnotations(spec);
if (shouldProcessAnnotations) {
// If an annotation processor is detected, we need to force Java stub generation, so the we can process annotations on Groovy classes
// We are forcing stub generation by tricking the groovy compiler into thinking there are java files to compile.
// All java files are just passed to the compile method of the JavaCompiler and aren't processed internally by the Groovy Compiler.
// Since we're maintaining our own list of Java files independent of what's passed by the Groovy compiler, adding a non-existent java file
// to the sources won't cause any issues.
unit.addSources(new File[]{new File("ForceStubGeneration.java")});
}
// Sort source files to work around https://issues.apache.org/jira/browse/GROOVY-7966
File[] sortedSourceFiles = Iterables.toArray(spec.getSourceFiles(), File.class);
Arrays.sort(sortedSourceFiles);
unit.addSources(sortedSourceFiles);
unit.setCompilerFactory(new JavaCompilerFactory() {
@Override
public JavaCompiler createCompiler(final CompilerConfiguration config) {
return new JavaCompiler() {
@Override
public void compile(List files, CompilationUnit cu) {
if (shouldProcessAnnotations) {
// In order for the Groovy stubs to have annotation processors invoked against them, they must be compiled as source.
// Classes compiled as a result of being on the -sourcepath do not have the annotation processor run against them
spec.setSourceFiles(Iterables.concat(spec.getSourceFiles(), ImmutableFileCollection.of(stubDir).getAsFileTree()));
} else {
// When annotation processing isn't required, it's better to add the Groovy stubs as part of the source path.
// This allows compilations to complete faster, because only the Groovy stubs that are needed by the java source are compiled.
ImmutableList.Builder sourcepathBuilder = ImmutableList.builder();
sourcepathBuilder.add(stubDir);
if (spec.getCompileOptions().getSourcepath() != null) {
sourcepathBuilder.addAll(spec.getCompileOptions().getSourcepath());
}
spec.getCompileOptions().setSourcepath(sourcepathBuilder.build());
}
spec.setSourceFiles(Iterables.filter(spec.getSourceFiles(), new Predicate() {
@Override
public boolean apply(File file) {
return hasExtension(file, ".java");
}
}));
try {
javaCompiler.execute(spec);
} catch (CompilationFailedException e) {
cu.getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(), cu));
}
}
};
}
});
try {
unit.compile();
} catch (org.codehaus.groovy.control.CompilationFailedException e) {
System.err.println(e.getMessage());
// Explicit flush, System.err is an auto-flushing PrintWriter unless it is replaced.
System.err.flush();
throw new CompilationFailedException();
} finally {
// Remove compile and AST types from the Groovy loader
compilerGroovyLoader.discardTypesFrom(classPathLoader);
compilerGroovyLoader.discardTypesFrom(astTransformClassLoader);
//Discard the compile loader
compileClasspathLoader.shutdown();
CompositeStoppable.stoppable(classPathLoader, astTransformClassLoader).stop();
}
return WorkResults.didWork(true);
}
private boolean shouldProcessAnnotations(GroovyJavaJointCompileSpec spec) {
return spec.getGroovyCompileOptions().isJavaAnnotationProcessing() && !spec.getAnnotationProcessorPath().isEmpty();
}
private void applyConfigurationScript(File configScript, CompilerConfiguration configuration) {
VersionNumber version = parseGroovyVersion();
if (version.compareTo(VersionNumber.parse("2.1")) < 0) {
throw new GradleException("Using a Groovy compiler configuration script requires Groovy 2.1+ but found Groovy " + version + "");
}
Binding binding = new Binding();
binding.setVariable("configuration", configuration);
CompilerConfiguration configuratorConfig = new CompilerConfiguration();
ImportCustomizer customizer = new ImportCustomizer();
customizer.addStaticStars("org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder");
configuratorConfig.addCompilationCustomizers(customizer);
GroovyShell shell = new GroovyShell(binding, configuratorConfig);
try {
shell.evaluate(configScript);
} catch (Exception e) {
throw new GradleException("Could not execute Groovy compiler configuration script: " + configScript.getAbsolutePath(), e);
}
}
private VersionNumber parseGroovyVersion() {
String version;
try {
version = GroovySystem.getVersion();
} catch (NoSuchMethodError e) {
// for Groovy <1.6, we need to call org.codehaus.groovy.runtime.InvokerHelper#getVersion
try {
Class> ih = Class.forName("org.codehaus.groovy.runtime.InvokerHelper");
Method getVersion = ih.getDeclaredMethod("getVersion");
version = (String) getVersion.invoke(ih);
} catch (Exception e1) {
throw new GradleException("Unable to determine Groovy version.", e1);
}
}
return VersionNumber.parse(version);
}
// Make sure that map only contains Boolean.TRUE and Boolean.FALSE values and no other Boolean instances.
// This is necessary because:
// 1. serialization/deserialization of the compile spec doesn't preserve Boolean.TRUE/Boolean.FALSE but creates new instances
// 1. org.codehaus.groovy.classgen.asm.WriterController makes identity comparisons
private void canonicalizeValues(Map options) {
for (String key : options.keySet()) {
// unboxing and boxing does the trick
boolean value = options.get(key);
options.put(key, value);
}
}
private ClassLoader getExtClassLoader() {
return ClassLoaderUtils.getPlatformClassLoader();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy