/* * Copyright 2010 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.process.internal.worker.child; import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.internal.ClassPathProvider; import org.gradle.api.specs.Spec; import org.gradle.cache.CacheRepository; import org.gradle.cache.PersistentCache; import org.gradle.internal.Factory; import org.gradle.internal.UncheckedException; import org.gradle.internal.classloader.ClassLoaderHierarchy; import org.gradle.internal.classloader.ClassLoaderSpec; import org.gradle.internal.classloader.ClassLoaderUtils; import org.gradle.internal.classloader.ClassLoaderVisitor; import org.gradle.internal.classloader.FilteringClassLoader; import org.gradle.internal.classloader.SystemClassLoaderSpec; import org.gradle.internal.classpath.ClassPath; import org.gradle.internal.classpath.DefaultClassPath; import org.gradle.internal.reflect.JavaMethod; import org.gradle.internal.reflect.JavaReflectionUtil; import org.gradle.internal.reflect.NoSuchMethodException; import org.gradle.internal.reflect.NoSuchPropertyException; import org.gradle.internal.reflect.PropertyAccessor; import org.gradle.internal.reflect.PropertyMutator; import org.gradle.process.internal.streams.EncodedStream; import org.gradle.process.internal.worker.GradleWorkerMain; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Type; import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.commons.RemappingClassAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Arrays; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class WorkerProcessClassPathProvider implements ClassPathProvider, Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(WorkerProcessClassPathProvider.class); private final CacheRepository cacheRepository; private final Object lock = new Object(); private ClassPath workerClassPath; private PersistentCache workerClassPathCache; public WorkerProcessClassPathProvider(CacheRepository cacheRepository) { this.cacheRepository = cacheRepository; } public ClassPath findClassPath(String name) { if (name.equals("WORKER_MAIN")) { synchronized (lock) { if (workerClassPath == null) { workerClassPathCache = cacheRepository .cache("workerMain") .withInitializer(new CacheInitializer()) .open(); workerClassPath = new DefaultClassPath(jarFile(workerClassPathCache)); } LOGGER.debug("Using worker process classpath: {}", workerClassPath); return workerClassPath; } } return null; } public void close() { // This isn't quite right. Should close the worker classpath cache once we're finished with the worker processes. This may be before the end of this build // or they may be used across multiple builds synchronized (lock) { try { if (workerClassPathCache != null) { workerClassPathCache.close(); } } finally { workerClassPathCache = null; workerClassPath = null; } } } private static File jarFile(PersistentCache cache) { return new File(cache.getBaseDir(), "gradle-worker.jar"); } private static class CacheInitializer implements Action<PersistentCache> { private final WorkerClassRemapper remapper = new WorkerClassRemapper(); public void execute(PersistentCache cache) { try { File jarFile = jarFile(cache); LOGGER.debug("Generating worker process classes to {}.", jarFile); // TODO - calculate this list of classes dynamically List<Class<?>> classes = Arrays.asList( GradleWorkerMain.class, BootstrapSecurityManager.class, EncodedStream.EncodedInput.class, ClassLoaderUtils.class, FilteringClassLoader.class, FilteringClassLoader.Spec.class, ClassLoaderHierarchy.class, ClassLoaderVisitor.class, ClassLoaderSpec.class, SystemClassLoaderSpec.class, JavaReflectionUtil.class, JavaMethod.class, GradleException.class, NoSuchPropertyException.class, NoSuchMethodException.class, UncheckedException.class, PropertyAccessor.class, PropertyMutator.class, Factory.class, Spec.class); ZipOutputStream outputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile))); try { for (Class<?> classToMap : classes) { remapClass(classToMap, outputStream); } } finally { outputStream.close(); } } catch (Exception e) { throw new GradleException("Could not generate worker process bootstrap classes.", e); } } private void remapClass(Class<?> classToMap, ZipOutputStream jar) throws IOException { String internalName = Type.getInternalName(classToMap); String resourceName = internalName.concat(".class"); URL resource = WorkerProcessClassPathProvider.class.getClassLoader().getResource(resourceName); if (resource == null) { throw new IllegalStateException("Could not locate classpath resource for class " + classToMap.getName()); } InputStream inputStream = resource.openStream(); ClassReader classReader; try { classReader = new ClassReader(inputStream); } finally { inputStream.close(); } ClassWriter classWriter = new ClassWriter(0); ClassVisitor remappingVisitor = new RemappingClassAdapter(classWriter, remapper); classReader.accept(remappingVisitor, ClassReader.EXPAND_FRAMES); byte[] remappedClass = classWriter.toByteArray(); String remappedClassName = remapper.map(internalName).concat(".class"); jar.putNextEntry(new ZipEntry(remappedClassName)); jar.write(remappedClass); } private static class WorkerClassRemapper extends Remapper { private static final String SYSTEM_APP_WORKER_INTERNAL_NAME = Type.getInternalName(SystemApplicationClassLoaderWorker.class); @Override public String map(String typeName) { if (typeName.equals(SYSTEM_APP_WORKER_INTERNAL_NAME)) { return typeName; } if (typeName.startsWith("org/gradle/")) { return "worker/" + typeName; } return typeName; } } } }