/* * 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 com.google.common.base.Joiner; import org.gradle.api.JavaVersion; import org.gradle.api.UncheckedIOException; import org.gradle.api.internal.ClassPathRegistry; import org.gradle.api.internal.file.TemporaryFileProvider; import org.gradle.api.logging.LogLevel; import org.gradle.internal.classpath.ClassPath; import org.gradle.internal.io.StreamByteBuffer; import org.gradle.internal.jvm.inspection.JvmVersionDetector; import org.gradle.internal.process.ArgWriter; import org.gradle.internal.remote.Address; import org.gradle.internal.remote.internal.inet.MultiChoiceAddress; import org.gradle.internal.remote.internal.inet.MultiChoiceAddressSerializer; import org.gradle.internal.serialize.OutputStreamBackedEncoder; import org.gradle.process.internal.JavaExecHandleBuilder; import org.gradle.process.internal.streams.EncodedStream; import org.gradle.process.internal.worker.DefaultWorkerProcessBuilder; import org.gradle.process.internal.worker.GradleWorkerMain; import org.gradle.util.GUtil; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; /** * A factory for a worker process which loads the application classes using the JVM's system ClassLoader. * * <p>Class loader hierarchy:</p> * <pre> * jvm bootstrap * | * | * jvm system * (GradleWorkerMain, application classes) * | * | * filter * (shared packages) * | * | * implementation * (SystemApplicationClassLoaderWorker, logging) * (ActionExecutionWorker + worker action implementation) * </pre> */ public class ApplicationClassesInSystemClassLoaderWorkerImplementationFactory implements WorkerImplementationFactory { private final ClassPathRegistry classPathRegistry; private final TemporaryFileProvider temporaryFileProvider; private final JvmVersionDetector jvmVersionDetector; public ApplicationClassesInSystemClassLoaderWorkerImplementationFactory(ClassPathRegistry classPathRegistry, TemporaryFileProvider temporaryFileProvider, JvmVersionDetector jvmVersionDetector) { this.classPathRegistry = classPathRegistry; this.temporaryFileProvider = temporaryFileProvider; this.jvmVersionDetector = jvmVersionDetector; } @Override public void prepareJavaCommand(Object workerId, String displayName, DefaultWorkerProcessBuilder processBuilder, List<URL> implementationClassPath, Address serverAddress, JavaExecHandleBuilder execSpec, boolean publishProcessInfo) { Collection<File> applicationClasspath = processBuilder.getApplicationClasspath(); LogLevel logLevel = processBuilder.getLogLevel(); Set<String> sharedPackages = processBuilder.getSharedPackages(); Object requestedSecurityManager = execSpec.getSystemProperties().get("java.security.manager"); ClassPath workerMainClassPath = classPathRegistry.getClassPath("WORKER_MAIN"); execSpec.setMain("worker." + GradleWorkerMain.class.getName()); boolean useOptionsFile = shouldUseOptionsFile(execSpec); if (useOptionsFile) { // Use an options file to pass across application classpath File optionsFile = temporaryFileProvider.createTemporaryFile("gradle-worker-classpath", "txt"); List<String> jvmArgs = writeOptionsFile(workerMainClassPath.getAsFiles(), applicationClasspath, optionsFile); execSpec.jvmArgs(jvmArgs); } else { // Use a dummy security manager, which hacks the application classpath into the system ClassLoader execSpec.classpath(workerMainClassPath.getAsFiles()); execSpec.systemProperty("java.security.manager", "worker." + BootstrapSecurityManager.class.getName()); } // Serialize configuration for the worker process to it stdin StreamByteBuffer buffer = new StreamByteBuffer(); try { DataOutputStream outstr = new DataOutputStream(new EncodedStream.EncodedOutput(buffer.getOutputStream())); if (!useOptionsFile) { // Serialize the application classpath, this is consumed by BootstrapSecurityManager outstr.writeInt(applicationClasspath.size()); for (File file : applicationClasspath) { outstr.writeUTF(file.getAbsolutePath()); } // Serialize the actual security manager type, this is consumed by BootstrapSecurityManager outstr.writeUTF(requestedSecurityManager == null ? "" : requestedSecurityManager.toString()); } // Serialize the shared packages, this is consumed by GradleWorkerMain outstr.writeInt(sharedPackages.size()); for (String str : sharedPackages) { outstr.writeUTF(str); } // Serialize the worker implementation classpath, this is consumed by GradleWorkerMain outstr.writeInt(implementationClassPath.size()); for (URL entry : implementationClassPath) { outstr.writeUTF(entry.toString()); } // Serialize the worker config, this is consumed by SystemApplicationClassLoaderWorker OutputStreamBackedEncoder encoder = new OutputStreamBackedEncoder(outstr); encoder.writeSmallInt(logLevel.ordinal()); encoder.writeBoolean(publishProcessInfo); new MultiChoiceAddressSerializer().write(encoder, (MultiChoiceAddress) serverAddress); // Serialize the worker, this is consumed by SystemApplicationClassLoaderWorker ActionExecutionWorker worker = new ActionExecutionWorker(processBuilder.getWorker(), workerId, displayName, processBuilder.getGradleUserHomeDir()); byte[] serializedWorker = GUtil.serialize(worker); encoder.writeBinary(serializedWorker); encoder.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } execSpec.setStandardInput(buffer.getInputStream()); } private boolean shouldUseOptionsFile(JavaExecHandleBuilder execSpec) { JavaVersion executableVersion = jvmVersionDetector.getJavaVersion(execSpec.getExecutable()); return executableVersion != null && executableVersion.isJava9Compatible(); } private List<String> writeOptionsFile(Collection<File> workerMainClassPath, Collection<File> applicationClasspath, File optionsFile) { List<File> classpath = new ArrayList<File>(workerMainClassPath.size() + applicationClasspath.size()); classpath.addAll(workerMainClassPath); classpath.addAll(applicationClasspath); List<String> argumentList = Arrays.asList("-cp", Joiner.on(File.pathSeparator).join(classpath)); return ArgWriter.argsFileGenerator(optionsFile, ArgWriter.unixStyleFactory()).transform(argumentList); } }