/* * Copyright 2015 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.project.antbuilder; import com.google.common.collect.Lists; import groovy.lang.Closure; import org.gradle.api.Action; import org.gradle.api.internal.ClassPathRegistry; import org.gradle.api.internal.ClosureBackedAction; import org.gradle.api.internal.classloading.GroovySystemLoader; import org.gradle.api.internal.classloading.GroovySystemLoaderFactory; import org.gradle.api.internal.classpath.ModuleRegistry; import org.gradle.api.internal.project.IsolatedAntBuilder; import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.internal.Factory; import org.gradle.internal.UncheckedException; import org.gradle.internal.classloader.CachingClassLoader; import org.gradle.internal.classloader.ClassLoaderFactory; import org.gradle.internal.classloader.ClassLoaderUtils; import org.gradle.internal.classloader.FilteringClassLoader; import org.gradle.internal.classloader.MultiParentClassLoader; import org.gradle.internal.classloader.VisitableURLClassLoader; import org.gradle.internal.classpath.ClassPath; import org.gradle.internal.classpath.DefaultClassPath; import org.gradle.internal.concurrent.Stoppable; import org.gradle.internal.jvm.Jvm; import java.io.File; import java.lang.reflect.Method; import java.util.List; import java.util.Vector; public class DefaultIsolatedAntBuilder implements IsolatedAntBuilder, Stoppable { private final static Logger LOG = Logging.getLogger(DefaultIsolatedAntBuilder.class); private final ClassLoader antLoader; private final ClassLoader baseAntLoader; private final ClassPath libClasspath; private final ClassLoader antAdapterLoader; private final ClassPathRegistry classPathRegistry; private final ClassLoaderFactory classLoaderFactory; private final ModuleRegistry moduleRegistry; private final ClassPathToClassLoaderCache classLoaderCache; private final GroovySystemLoader gradleApiGroovyLoader; private final GroovySystemLoader antAdapterGroovyLoader; public DefaultIsolatedAntBuilder(ClassPathRegistry classPathRegistry, ClassLoaderFactory classLoaderFactory, ModuleRegistry moduleRegistry) { this.classPathRegistry = classPathRegistry; this.classLoaderFactory = classLoaderFactory; this.moduleRegistry = moduleRegistry; this.libClasspath = new DefaultClassPath(); GroovySystemLoaderFactory groovySystemLoaderFactory = new GroovySystemLoaderFactory(); this.classLoaderCache = new ClassPathToClassLoaderCache(groovySystemLoaderFactory); List<File> antClasspath = Lists.newArrayList(classPathRegistry.getClassPath("ANT").getAsFiles()); // Need tools.jar for compile tasks File toolsJar = Jvm.current().getToolsJar(); if (toolsJar != null) { antClasspath.add(toolsJar); } antLoader = classLoaderFactory.createIsolatedClassLoader(new DefaultClassPath(antClasspath)); FilteringClassLoader.Spec loggingLoaderSpec = new FilteringClassLoader.Spec(); loggingLoaderSpec.allowPackage("org.slf4j"); loggingLoaderSpec.allowPackage("org.apache.commons.logging"); loggingLoaderSpec.allowPackage("org.apache.log4j"); loggingLoaderSpec.allowClass(Logger.class); loggingLoaderSpec.allowClass(LogLevel.class); FilteringClassLoader loggingLoader = new FilteringClassLoader(getClass().getClassLoader(), loggingLoaderSpec); this.baseAntLoader = new CachingClassLoader(new MultiParentClassLoader(antLoader, loggingLoader)); // Need gradle core to pick up ant logging adapter, AntBuilder and such ClassPath gradleCoreUrls = moduleRegistry.getModule("gradle-core").getImplementationClasspath(); gradleCoreUrls = gradleCoreUrls.plus(moduleRegistry.getModule("gradle-logging").getImplementationClasspath()); gradleCoreUrls = gradleCoreUrls.plus(moduleRegistry.getExternalModule("groovy-all").getClasspath()); // Need Transformer (part of AntBuilder API) from base services gradleCoreUrls = gradleCoreUrls.plus(moduleRegistry.getModule("gradle-base-services").getImplementationClasspath()); this.antAdapterLoader = new VisitableURLClassLoader(baseAntLoader, gradleCoreUrls); gradleApiGroovyLoader = groovySystemLoaderFactory.forClassLoader(this.getClass().getClassLoader()); antAdapterGroovyLoader = groovySystemLoaderFactory.forClassLoader(antAdapterLoader); } protected DefaultIsolatedAntBuilder(DefaultIsolatedAntBuilder copy, Iterable<File> libClasspath) { this.classPathRegistry = copy.classPathRegistry; this.classLoaderFactory = copy.classLoaderFactory; this.moduleRegistry = copy.moduleRegistry; this.antLoader = copy.antLoader; this.baseAntLoader = copy.baseAntLoader; this.antAdapterLoader = copy.antAdapterLoader; this.libClasspath = new DefaultClassPath(libClasspath); this.gradleApiGroovyLoader = copy.gradleApiGroovyLoader; this.antAdapterGroovyLoader = copy.antAdapterGroovyLoader; this.classLoaderCache = copy.classLoaderCache; } public ClassPathToClassLoaderCache getClassLoaderCache() { return classLoaderCache; } public IsolatedAntBuilder withClasspath(Iterable<File> classpath) { if (LOG.isDebugEnabled()) { LOG.debug("Forking a new isolated ant builder for classpath : {}", classpath); } return new DefaultIsolatedAntBuilder(this, classpath); } public void execute(final Closure antClosure) { classLoaderCache.withCachedClassLoader(libClasspath, gradleApiGroovyLoader, antAdapterGroovyLoader, new Factory<ClassLoader>() { @Override public ClassLoader create() { return new VisitableURLClassLoader(baseAntLoader, libClasspath); } }, new Action<CachedClassLoader>() { @Override public void execute(CachedClassLoader cachedClassLoader) { ClassLoader classLoader = cachedClassLoader.getClassLoader(); Object antBuilder = newInstanceOf("org.gradle.api.internal.project.ant.BasicAntBuilder"); Object antLogger = newInstanceOf("org.gradle.api.internal.project.ant.AntLoggingAdapter"); // This looks ugly, very ugly, but that is apparently what Ant does itself ClassLoader originalLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); try { configureAntBuilder(antBuilder, antLogger); // Ideally, we'd delegate directly to the AntBuilder, but its Closure class is different to our caller's // Closure class, so the AntBuilder's methodMissing() doesn't work. It just converts our Closures to String // because they are not an instanceof its Closure class. Object delegate = new AntBuilderDelegate(antBuilder, classLoader); ClosureBackedAction.execute(delegate, antClosure); } finally { Thread.currentThread().setContextClassLoader(originalLoader); disposeBuilder(antBuilder, antLogger); } } }); } private Object newInstanceOf(String className) { // we must use a String literal here, otherwise using things like Foo.class.name will trigger unnecessary // loading of classes in the classloader of the DefaultIsolatedAntBuilder, which is not what we want. try { return antAdapterLoader.loadClass(className).newInstance(); } catch (Exception e) { // should never happen throw UncheckedException.throwAsUncheckedException(e); } } // We *absolutely* need to avoid polluting the project with ClassInfo from *our* classloader // So this class must NOT call any dynamic Groovy code. This means we must do what follows using // good old java reflection! private Object getProject(Object antBuilder) throws Exception { return antBuilder.getClass().getMethod("getProject").invoke(antBuilder); } protected void configureAntBuilder(Object antBuilder, Object antLogger) { try { Object project = getProject(antBuilder); Class<?> projectClass = project.getClass(); ClassLoader cl = projectClass.getClassLoader(); Class<?> buildListenerClass = cl.loadClass("org.apache.tools.ant.BuildListener"); Method addBuildListener = projectClass.getDeclaredMethod("addBuildListener", buildListenerClass); Method removeBuildListener = projectClass.getDeclaredMethod("removeBuildListener", buildListenerClass); Method getBuildListeners = projectClass.getDeclaredMethod("getBuildListeners"); Vector listeners = (Vector) getBuildListeners.invoke(project); removeBuildListener.invoke(project, listeners.get(0)); addBuildListener.invoke(project, antLogger); } catch (Exception ex) { throw UncheckedException.throwAsUncheckedException(ex); } } protected void disposeBuilder(Object antBuilder, Object antLogger) { try { Object project = getProject(antBuilder); Class<?> projectClass = project.getClass(); ClassLoader cl = projectClass.getClassLoader(); // remove build listener Class<?> buildListenerClass = cl.loadClass("org.apache.tools.ant.BuildListener"); Method removeBuildListener = projectClass.getDeclaredMethod("removeBuildListener", buildListenerClass); removeBuildListener.invoke(project, antLogger); antBuilder.getClass().getDeclaredMethod("close").invoke(antBuilder); } catch (Exception ex) { throw UncheckedException.throwAsUncheckedException(ex); } } @Override public void stop() { classLoaderCache.stop(); // Remove classes from core Gradle API gradleApiGroovyLoader.discardTypesFrom(antAdapterLoader); gradleApiGroovyLoader.discardTypesFrom(antLoader); // Shutdown the adapter Groovy system antAdapterGroovyLoader.shutdown(); ClassLoaderUtils.tryClose(antAdapterLoader); } }