org.gradle.process.internal.worker.child.ApplicationClassesInSystemClassLoaderWorkerImplementationFactory Maven / Gradle / Ivy
/*
* 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.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.internal.stream.EncodedStream;
import org.gradle.process.internal.JavaExecHandleBuilder;
import org.gradle.process.internal.worker.GradleWorkerMain;
import org.gradle.process.internal.worker.WorkerProcessBuilder;
import org.gradle.util.GUtil;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A factory for a worker process which loads the application classes using the JVM's system ClassLoader.
*
* Class loader hierarchy:
*
* jvm bootstrap
* |
* |
* jvm system
* (GradleWorkerMain, application classes)
* |
* |
* filter
* (shared packages)
* |
* |
* implementation
* (SystemApplicationClassLoaderWorker, logging)
* (ActionExecutionWorker + worker action implementation)
*
*/
public class ApplicationClassesInSystemClassLoaderWorkerImplementationFactory implements WorkerImplementationFactory {
private final ClassPathRegistry classPathRegistry;
private final TemporaryFileProvider temporaryFileProvider;
private final JvmVersionDetector jvmVersionDetector;
private final File gradleUserHomeDir;
public ApplicationClassesInSystemClassLoaderWorkerImplementationFactory(ClassPathRegistry classPathRegistry, TemporaryFileProvider temporaryFileProvider,
JvmVersionDetector jvmVersionDetector, File gradleUserHomeDir) {
this.classPathRegistry = classPathRegistry;
this.temporaryFileProvider = temporaryFileProvider;
this.jvmVersionDetector = jvmVersionDetector;
this.gradleUserHomeDir = gradleUserHomeDir;
}
@Override
public void prepareJavaCommand(long workerId, String displayName, WorkerProcessBuilder processBuilder, List implementationClassPath, List implementationModulePath, Address serverAddress, JavaExecHandleBuilder execSpec, boolean publishProcessInfo) {
Collection applicationClasspath = processBuilder.getApplicationClasspath();
Set applicationModulePath = processBuilder.getApplicationModulePath();
LogLevel logLevel = processBuilder.getLogLevel();
Set sharedPackages = processBuilder.getSharedPackages();
Object requestedSecurityManager = execSpec.getSystemProperties().get("java.security.manager");
List workerMainClassPath = classPathRegistry.getClassPath("WORKER_MAIN").getAsFiles();
execSpec.getMainModule().set("gradle.worker");
execSpec.getMainClass().set("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 jvmArgs = writeOptionsFile(execSpec.getModularity().getInferModulePath().get(), workerMainClassPath, implementationModulePath, applicationClasspath, applicationModulePath, optionsFile);
execSpec.jvmArgs(jvmArgs);
} else {
// Use a dummy security manager, which hacks the application classpath into the system ClassLoader
execSpec.classpath(workerMainClassPath);
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
if (execSpec.getModularity().getInferModulePath().get() || implementationModulePath == null) {
outstr.writeInt(implementationClassPath.size());
for (URL entry : implementationClassPath) {
outstr.writeUTF(entry.toString());
}
// We do not serialize the module path. Instead, implementation modules are directly added to the application module path when
// starting the worker process. Implementation modules are hidden to the application modules by module visibility.
} else {
outstr.writeInt(implementationClassPath.size() + implementationModulePath.size());
for (URL entry : implementationClassPath) {
outstr.writeUTF(entry.toString());
}
for (URL entry : implementationModulePath) {
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);
encoder.writeString(gradleUserHomeDir.getAbsolutePath());
new MultiChoiceAddressSerializer().write(encoder, (MultiChoiceAddress) serverAddress);
encoder.writeSmallLong(workerId);
encoder.writeString(displayName);
// Serialize the worker action, this is consumed by SystemApplicationClassLoaderWorker
byte[] serializedWorker = GUtil.serialize(processBuilder.getWorker());
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 writeOptionsFile(boolean runAsModule, Collection workerMainClassPath, Collection implementationModulePath, Collection applicationClasspath, Set applicationModulePath, File optionsFile) {
List classpath = new ArrayList<>();
List modulePath = new ArrayList<>();
if (runAsModule) {
modulePath.addAll(workerMainClassPath);
} else {
classpath.addAll(workerMainClassPath);
}
modulePath.addAll(applicationModulePath);
classpath.addAll(applicationClasspath);
if (!modulePath.isEmpty() && implementationModulePath != null && !implementationModulePath.isEmpty()) {
// We add the implementation module path as well, as we do not load modules dynamically through a separate class loader in the worker.
// This acceptable, because the implementation modules are hidden to the application by module visibility.
modulePath.addAll(implementationModulePath.stream().map(url -> {
try {
return new File(url.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList()));
}
List argumentList = new ArrayList<>();
if (!modulePath.isEmpty()) {
argumentList.addAll(Arrays.asList("--module-path", Joiner.on(File.pathSeparator).join(modulePath), "--add-modules", "ALL-MODULE-PATH"));
}
if (!classpath.isEmpty()) {
argumentList.addAll(Arrays.asList("-cp", Joiner.on(File.pathSeparator).join(classpath)));
}
return ArgWriter.argsFileGenerator(optionsFile, ArgWriter.javaStyleFactory()).transform(argumentList);
}
}