use of org.gradle.api.internal.classloading.GroovySystemLoader in project gradle by gradle.
the class InProcessWorkerFactory method executeInWorkerClassLoader.
private <T extends WorkSpec> DefaultWorkResult executeInWorkerClassLoader(WorkerAction<T> action, T spec, DaemonForkOptions forkOptions) {
ClassLoader actionClasspathLoader = createActionClasspathLoader(forkOptions);
GroovySystemLoader actionClasspathGroovy = groovySystemLoaderFactory.forClassLoader(actionClasspathLoader);
ClassLoader workerClassLoader = createWorkerClassLoader(actionClasspathLoader, forkOptions.getSharedPackages(), action.getClass());
ClassLoader previousContextLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(workerClassLoader);
Callable<?> worker = transferWorkerIntoWorkerClassloader(action, spec, workerClassLoader);
Object result = worker.call();
return transferResultFromWorkerClassLoader(result);
} catch (Exception e) {
throw UncheckedException.throwAsUncheckedException(e);
} finally {
// Eventually shutdown any leaky groovy runtime loaded from action classpath loader
actionClasspathGroovy.shutdown();
Thread.currentThread().setContextClassLoader(previousContextLoader);
}
}
use of org.gradle.api.internal.classloading.GroovySystemLoader in project gradle by gradle.
the class ClassPathToClassLoaderCache method withCachedClassLoader.
/**
* Provides execution of arbitrary code that consumes a cached class loader in a memory safe manner,
* that is to say making sure that concurrent calls reuse the same classloader, or that the class loader
* is retrieved from cache if available.
*
* It will also make sure that once a cached class loader is unused and removed from cache, memory cleanup
* is done.
*
* The action MUST be done on a CachedClassLoader, and not directly with the ClassLoader, in order for
* a strong reference to be kept on the cached class loader while in use. If we don't do so there are risks
* that the cached entry gets released by the GC before we've finished working with the classloader it
* wraps!
*
* @param libClasspath the classpath for this classloader
* @param gradleApiGroovy the Groovy system used by core Gradle API
* @param antBuilderAdapterGroovy the Groovy system used by the Ant builder adapter
* @param factory the factory to create a new class loader on cache miss
* @param action the action to execute with the cached class loader
*/
public void withCachedClassLoader(ClassPath libClasspath, GroovySystemLoader gradleApiGroovy, GroovySystemLoader antBuilderAdapterGroovy, Factory<? extends ClassLoader> factory, Action<? super CachedClassLoader> action) {
CachedClassLoader cachedClassLoader;
lock.lock();
try {
CacheEntry cacheEntry = cacheEntries.get(libClasspath);
cachedClassLoader = maybeGet(cacheEntry);
if (cachedClassLoader == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Classloader cache miss for classpath : {}. Creating classloader.", libClasspath.getAsURIs());
}
// Lock is held while creating ClassLoader - nothing else can happen while this is running
ClassLoader classLoader = factory.create();
cachedClassLoader = new CachedClassLoader(libClasspath, classLoader);
cacheEntry = new CacheEntry(libClasspath, cachedClassLoader);
GroovySystemLoader groovySystemForLoader = groovySystemLoaderFactory.forClassLoader(classLoader);
Cleanup cleanup = new Cleanup(libClasspath, cachedClassLoader, finalizerThread.getReferenceQueue(), classLoader, groovySystemForLoader, gradleApiGroovy, antBuilderAdapterGroovy);
finalizerThread.putCleanup(libClasspath, cleanup);
cacheEntries.put(libClasspath, cacheEntry);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Classloader found in cache: {}", libClasspath.getAsURIs());
}
}
// in order to make sure that the CacheEntry is not collected
// while the cached class loader is still in use, we need to keep a strong reference onto
// the cached class loader as long as the action is executed
inUseClassLoaders.add(cachedClassLoader);
} finally {
lock.unlock();
}
try {
action.execute(cachedClassLoader);
} finally {
lock.lock();
try {
inUseClassLoaders.remove(cachedClassLoader);
} finally {
lock.unlock();
}
}
}
use of org.gradle.api.internal.classloading.GroovySystemLoader in project gradle by gradle.
the class IsolatedClassloaderWorkerFactory method executeInWorkerClassLoader.
private DefaultWorkResult executeInWorkerClassLoader(ActionExecutionSpec spec, DaemonForkOptions forkOptions) {
ClassLoader actionClasspathLoader = createActionClasspathLoader(forkOptions);
GroovySystemLoader actionClasspathGroovy = groovySystemLoaderFactory.forClassLoader(actionClasspathLoader);
ClassLoader workerClassLoader = createWorkerClassLoader(actionClasspathLoader, forkOptions.getSharedPackages(), spec.getClass());
ClassLoader previousContextLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(workerClassLoader);
Callable<?> worker = transferWorkerIntoWorkerClassloader(spec, workerClassLoader);
Object result = worker.call();
return transferResultFromWorkerClassLoader(result);
} catch (Exception e) {
throw UncheckedException.throwAsUncheckedException(e);
} finally {
// Eventually shutdown any leaky groovy runtime loaded from action classpath loader
actionClasspathGroovy.shutdown();
Thread.currentThread().setContextClassLoader(previousContextLoader);
}
}
use of org.gradle.api.internal.classloading.GroovySystemLoader in project gradle by gradle.
the class IsolatedClassloaderWorker method run.
@Override
public DefaultWorkResult run(TransportableActionExecutionSpec spec) {
GroovySystemLoader workerClasspathGroovy = groovySystemLoaderFactory.forClassLoader(workerClassLoader);
try {
return executeInClassLoader(spec, workerClassLoader);
} finally {
workerClasspathGroovy.shutdown();
// TODO: we should just cache these classloaders and eject/stop them when they are no longer in use
if (!reuseClassloader) {
CompositeStoppable.stoppable(workerClassLoader).stop();
this.workerClassLoader = null;
}
}
}
use of org.gradle.api.internal.classloading.GroovySystemLoader in project gradle by gradle.
the class ApiGroovyCompiler method execute.
@Override
public WorkResult execute(final GroovyJavaJointCompileSpec spec) {
ApiCompilerResult result = new ApiCompilerResult();
result.getAnnotationProcessingResult().setFullRebuildCause("Incremental annotation processing is not supported by Groovy.");
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());
VersionNumber version = parseGroovyVersion();
if (version.compareTo(VersionNumber.parse("2.5")) >= 0) {
configuration.setParameters(spec.getGroovyCompileOptions().isParameters());
} else if (spec.getGroovyCompileOptions().isParameters()) {
throw new GradleException("Using Groovy compiler flag '--parameters' requires Groovy 2.5+ but found Groovy " + version);
}
IncrementalCompilationCustomizer customizer = IncrementalCompilationCustomizer.fromSpec(spec, result);
customizer.addToConfiguration(configuration);
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 */
}
try {
configuration.setDisabledGlobalASTTransformations(spec.getGroovyCompileOptions().getDisabledGlobalASTTransformations());
} catch (NoSuchMethodError ignored) {
/* method was only introduced in Groovy 2.0.0 */
}
Map<String, Object> jointCompilationOptions = new HashMap<String, Object>();
final File stubDir = spec.getGroovyCompileOptions().getStubDir();
stubDir.mkdirs();
jointCompilationOptions.put("stubDir", stubDir);
jointCompilationOptions.put("keepStubs", spec.getGroovyCompileOptions().isKeepStubs());
configuration.setJointCompilationOptions(jointCompilationOptions);
ClassLoader classPathLoader;
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("api-groovy-compile-loader", 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");
groovyCompilerClassLoaderSpec.allowPackage("groovyjarjarasm");
// 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.disallowClass("org.codehaus.groovy.transform.NotYetImplementedASTTransformation");
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);
// 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") });
}
unit.addSources(getSortedSourceFiles(spec));
unit.setCompilerFactory(new JavaCompilerFactory() {
@Override
public JavaCompiler createCompiler(final CompilerConfiguration config) {
return new JavaCompiler() {
@Override
public void compile(List<String> 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(), projectLayout.files(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<File> 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<File>() {
@Override
public boolean apply(File file) {
return hasExtension(file, ".java");
}
}));
try {
WorkResult javaCompilerResult = javaCompiler.execute(spec);
if (javaCompilerResult instanceof ApiCompilerResult) {
result.getSourceClassesMapping().putAll(((ApiCompilerResult) javaCompilerResult).getSourceClassesMapping());
}
} catch (CompilationFailedException e) {
cu.getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(), cu));
}
}
};
}
});
try {
unit.compile();
return result;
} 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();
}
}
Aggregations