make ExternalJavacManager WSL-aware (IDEA-267753, IDEA-286844)

GitOrigin-RevId: dc487141f56192507046fba2d49c50e3688a00fc
This commit is contained in:
Eugene Zhuravlev
2022-02-23 10:59:48 +00:00
committed by intellij-monorepo-bot
parent 98a709c5da
commit 329030f0b1
5 changed files with 154 additions and 29 deletions
@@ -5,6 +5,7 @@ import com.intellij.compiler.impl.*;
import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
import com.intellij.compiler.server.BuildManager;
import com.intellij.execution.process.ProcessIOExecutorService;
import com.intellij.execution.wsl.WSLDistribution;
import com.intellij.ide.IdeEventQueue;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.Compiler;
@@ -500,6 +501,7 @@ public class CompilerManagerImpl extends CompilerManager {
manager = new ExternalJavacManager(
compilerWorkingDir, ProcessIOExecutorService.INSTANCE, Registry.intValue("compiler.external.javac.keep.alive.timeout", 5*60*1000)
);
manager.setWslExecutablePath(WSLDistribution.findWslExe());
manager.start(listenPort);
myExternalJavacManager = manager;
IdeEventQueue.getInstance().addIdleListener(new IdleTask(manager), IdleTask.CHECK_PERIOD);
@@ -3,10 +3,14 @@ package org.jetbrains.jps.javac;
import com.google.protobuf.ByteString;
import com.google.protobuf.MessageLite;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.incremental.BinaryContent;
import javax.tools.*;
import javax.tools.Diagnostic;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
@@ -22,13 +26,27 @@ public final class ExternalJavacMessageHandler {
@Nullable
private final String myEncodingName;
private volatile boolean myTerminatedSuccessfully;
private final WslSupport myWslSupport;
interface WslSupport {
WslSupport DIRECT = new WslSupport() {
@Override
public String convertPath(String path) {
return path != null? path.replace('\\', '/') : null;
}
};
String convertPath(String path);
}
public ExternalJavacMessageHandler(DiagnosticOutputConsumer diagnosticSink,
OutputFileConsumer outputSink,
@Nullable final String encodingName) {
@Nullable final String encodingName,
@NotNull WslSupport wslSupport) {
myDiagnosticSink = diagnosticSink;
myOutputSink = outputSink;
myEncodingName = encodingName;
myWslSupport = wslSupport;
}
public DiagnosticOutputConsumer getDiagnosticSink() {
@@ -67,7 +85,7 @@ public final class ExternalJavacMessageHandler {
final JavacRemoteProto.Message.Response.OutputObject.Kind kind = outputObject.getKind();
final String outputRoot = outputObject.hasOutputRoot()? outputObject.getOutputRoot() : null;
final File outputRootFile = outputRoot != null? new File(outputRoot) : null;
final File outputRootFile = outputRoot != null? new File(myWslSupport.convertPath(outputRoot)) : null;
final BinaryContent fileObjectContent;
final ByteString content = outputObject.hasContent()? outputObject.getContent() : null;
@@ -90,7 +108,7 @@ public final class ExternalJavacMessageHandler {
null,
outputRootFile,
outputObject.hasRelativePath()? outputObject.getRelativePath() : null,
new File(outputObject.getFilePath()),
new File(myWslSupport.convertPath(outputObject.getFilePath())),
convertKind(kind),
outputObject.hasClassName()? outputObject.getClassName() : null,
sources,
@@ -104,7 +122,7 @@ public final class ExternalJavacMessageHandler {
if (responseType == JavacRemoteProto.Message.Response.Type.SRC_FILE_LOADED) {
final JavacRemoteProto.Message.Response.OutputObject outputObject = response.getOutputObject();
final File file = new File(outputObject.getFilePath());
final File file = new File(myWslSupport.convertPath(outputObject.getFilePath()));
myDiagnosticSink.javaFileLoaded(file);
return false;
}
@@ -34,21 +34,22 @@ public final class JavacProtoUtil {
ModulePath modulePath,
Iterable<? extends File> upgradeModulePath,
Iterable<? extends File> sourcePath,
Map<File, Set<File>> outs) {
Map<File, Set<File>> outs,
ExternalJavacMessageHandler.WslSupport wslSupport) {
final JavacRemoteProto.Message.Request.Builder builder = JavacRemoteProto.Message.Request.newBuilder();
builder.setRequestType(JavacRemoteProto.Message.Request.Type.COMPILE);
builder.addAllOption(options);
for (File file : files) {
builder.addFile(FileUtilRt.toSystemIndependentName(file.getPath()));
builder.addFile(wslSupport.convertPath(file.getPath()));
}
for (File file : classpath) {
builder.addClasspath(FileUtilRt.toSystemIndependentName(file.getPath()));
builder.addClasspath(wslSupport.convertPath(file.getPath()));
}
for (File file : platformCp) {
builder.addPlatformClasspath(FileUtilRt.toSystemIndependentName(file.getPath()));
builder.addPlatformClasspath(wslSupport.convertPath(file.getPath()));
}
for (File file : modulePath.getPath()) {
final String pathEntry = FileUtilRt.toSystemIndependentName(file.getPath());
final String pathEntry = wslSupport.convertPath(file.getPath());
builder.addModulePath(pathEntry);
final String moduleName = modulePath.getModuleName(file);
if (moduleName != null) {
@@ -56,16 +57,16 @@ public final class JavacProtoUtil {
}
}
for (File file : upgradeModulePath) {
builder.addUpgradeModulePath(FileUtilRt.toSystemIndependentName(file.getPath()));
builder.addUpgradeModulePath(wslSupport.convertPath(file.getPath()));
}
for (File file : sourcePath) {
builder.addSourcepath(FileUtilRt.toSystemIndependentName(file.getPath()));
builder.addSourcepath(wslSupport.convertPath(file.getPath()));
}
for (Map.Entry<File, Set<File>> entry : outs.entrySet()) {
final JavacRemoteProto.Message.Request.OutputGroup.Builder groupBuilder = JavacRemoteProto.Message.Request.OutputGroup.newBuilder();
groupBuilder.setOutputRoot(FileUtilRt.toSystemIndependentName(entry.getKey().getPath()));
groupBuilder.setOutputRoot(wslSupport.convertPath(entry.getKey().getPath()));
for (File srcRoot : entry.getValue()) {
groupBuilder.addSourceRoot(FileUtilRt.toSystemIndependentName(srcRoot.getPath()));
groupBuilder.addSourceRoot(wslSupport.convertPath(srcRoot.getPath()));
}
builder.addOutput(groupBuilder.build());
}
@@ -398,7 +398,7 @@ public final class JavaBuilder extends ModuleLevelBuilder {
DiagnosticOutputConsumer diagnosticSink,
OutputFileConsumer outputSink,
JavaCompilingTool compilingTool,
File moduleInfoFile) {
File moduleInfoFile) throws IOException {
final Semaphore counter = new Semaphore();
COUNTER_KEY.set(context, counter);
@@ -766,7 +766,7 @@ public final class JavaBuilder extends ModuleLevelBuilder {
}
@NotNull
private static synchronized ExternalJavacManager ensureJavacServerStarted(@NotNull CompileContext context) {
private static synchronized ExternalJavacManager ensureJavacServerStarted(@NotNull CompileContext context) throws IOException {
ExternalJavacManager server = ExternalJavacManager.KEY.get(context);
if (server != null) {
return server;
@@ -6,6 +6,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.io.BaseOutputReader;
@@ -33,6 +34,8 @@ import org.jetbrains.jps.incremental.GlobalContextKey;
import javax.tools.Diagnostic;
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -52,16 +55,19 @@ public class ExternalJavacManager extends ProcessAdapter {
private static final AttributeKey<UUID> PROCESS_ID_KEY = AttributeKey.valueOf("ExternalJavacServer.ProcessId");
private static final Key<Integer> PROCESS_HASH = Key.create("ExternalJavacServer.SdkHomePath");
private static final Key<ExternalJavacMessageHandler.WslSupport> WSL_SUPPORT = Key.create("_wsl_support_");
private final File myWorkingDir;
private final ChannelRegistrar myChannelRegistrar;
private int myListenPort = DEFAULT_SERVER_PORT;
private InetAddress myListenAddress;
private final Map<UUID, CompileSession> mySessions = Collections.synchronizedMap(new HashMap<>());
private final Map<UUID, ExternalJavacProcessHandler> myRunningProcesses = Collections.synchronizedMap(new HashMap<>());
private final Map<UUID, Channel> myConnections = Collections.synchronizedMap(new HashMap<>()); // processId->channel
private final Executor myExecutor;
private boolean myOwnExecutor;
private final long myKeepAliveTimeout;
private String myWslExePath = "wsl";
public ExternalJavacManager(@NotNull final File workingDir, @NotNull Executor executor) {
this(workingDir, executor, 5 * 60 * 1000L /* 5 minutes default*/);
@@ -74,7 +80,7 @@ public class ExternalJavacManager extends ProcessAdapter {
myKeepAliveTimeout = keepAliveTimeout;
}
public void start(int listenPort) {
public void start(int listenPort) throws UnknownHostException {
final ChannelHandler compilationRequestsHandler = new CompilationRequestsHandler();
final ServerBootstrap bootstrap = new ServerBootstrap()
.group(new NioEventLoopGroup(1, myExecutor))
@@ -92,7 +98,9 @@ public class ExternalJavacManager extends ProcessAdapter {
compilationRequestsHandler);
}
});
myChannelRegistrar.add(bootstrap.bind(InetAddress.getLoopbackAddress(), listenPort).syncUninterruptibly().channel());
// javac process forked in a Linux VM can only access ExternalJavacManager, which is listening in a host machine, using real IP
myListenAddress = WslToLinuxPathConverter.isWslPath(myWorkingDir)? InetAddress.getLocalHost() : InetAddress.getLoopbackAddress();
myChannelRegistrar.add(bootstrap.bind(myListenAddress, listenPort).syncUninterruptibly().channel());
myListenPort = listenPort;
}
@@ -116,12 +124,14 @@ public class ExternalJavacManager extends ProcessAdapter {
final Channel channel = lookupChannel(processHandler.getProcessId());
if (channel != null) {
final ExternalJavacMessageHandler.WslSupport wslSupport = WSL_SUPPORT.get(processHandler);
final ExternalJavacMessageHandler.WslSupport converter = wslSupport instanceof WslToLinuxPathConverter? ((WslToLinuxPathConverter)wslSupport).reverseConverter() : ExternalJavacMessageHandler.WslSupport.DIRECT;
final CompileSession session = new CompileSession(
processHandler.getProcessId(), new ExternalJavacMessageHandler(diagnosticSink, outputSink, getEncodingName(options)), cancelStatus
processHandler.getProcessId(), new ExternalJavacMessageHandler(diagnosticSink, outputSink, getEncodingName(options), converter), cancelStatus
);
mySessions.put(session.getId(), session);
channel.writeAndFlush(JavacProtoUtil.toMessage(session.getId(), JavacProtoUtil.createCompilationRequest(
options, files, paths.getClasspath(), paths.getPlatformClasspath(), paths.getModulePath(), paths.getUpgradeModulePath(), paths.getSourcePath(), outs
options, files, paths.getClasspath(), paths.getPlatformClasspath(), paths.getModulePath(), paths.getUpgradeModulePath(), paths.getSourcePath(), outs, wslSupport
)));
return session;
}
@@ -283,7 +293,7 @@ public class ExternalJavacManager extends ProcessAdapter {
return false;
}
private ExternalJavacProcessHandler launchExternalJavacProcess(String sdkHomePath,
private ExternalJavacProcessHandler launchExternalJavacProcess(final String sdkHomePath,
int heapSize,
int port,
File workingDir,
@@ -293,8 +303,10 @@ public class ExternalJavacManager extends ProcessAdapter {
final UUID processId = UUID.randomUUID();
final List<String> cmdLine = new ArrayList<>();
appendParam(cmdLine, getVMExecutablePath(sdkHomePath));
final WslToLinuxPathConverter wslConverter = WslToLinuxPathConverter.createFrom(sdkHomePath);
final boolean launchInLinuxVM = wslConverter != null;
final ExternalJavacMessageHandler.WslSupport wslSupport = launchInLinuxVM? wslConverter : ExternalJavacMessageHandler.WslSupport.DIRECT;
appendParam(cmdLine, wslSupport.convertPath(sdkHomePath + "/bin/java"));
appendParam(cmdLine, "-Djava.awt.headless=true");
//appendParam(cmdLine, "-XX:MaxPermSize=150m");
@@ -330,21 +342,44 @@ public class ExternalJavacManager extends ProcessAdapter {
appendParam(cmdLine, "-classpath");
List<File> cp = ClasspathBootstrap.getExternalJavacProcessClasspath(sdkHomePath, compilingTool);
appendParam(cmdLine, cp.stream().map(File::getPath).collect(Collectors.joining(File.pathSeparator)));
final String pathSeparator = launchInLinuxVM? ":" : File.pathSeparator;
appendParam(cmdLine, cp.stream().map(f -> wslSupport.convertPath(f.getPath())).collect(Collectors.joining(pathSeparator)));
appendParam(cmdLine, ExternalJavacProcess.class.getName());
appendParam(cmdLine, processId.toString());
appendParam(cmdLine, InetAddress.getLoopbackAddress().getHostAddress());
InetAddress targetAddress = myListenAddress;
if (targetAddress == null) {
targetAddress = InetAddress.getLoopbackAddress();
}
appendParam(cmdLine, targetAddress.getHostAddress());
appendParam(cmdLine, Integer.toString(port));
appendParam(cmdLine, Boolean.toString(keepProcessAlive)); // keep in memory after build finished
appendParam(cmdLine, FileUtil.toSystemIndependentName(workingDir.getPath()));
if (launchInLinuxVM) {
cmdLine.add(0, "&&");
cmdLine.add(0, wslSupport.convertPath(workingDir.getPath()));
cmdLine.add(0, "cd");
final String command = StringUtil.join(cmdLine, " ");
cmdLine.clear();
cmdLine.add(myWslExePath);
cmdLine.add("--distribution");
cmdLine.add(wslConverter.getDistributionId());
cmdLine.add("--exec");
cmdLine.add("/bin/sh");
cmdLine.add("-c");
cmdLine.add("\"" + command + "\"");
}
debug(()-> "starting external compiler: " + cmdLine);
FileUtil.createDirectory(workingDir);
final int processHash = processHash(sdkHomePath, vmOptions, compilingTool);
final ExternalJavacProcessHandler processHandler = createProcessHandler(processId, new ProcessBuilder(cmdLine).directory(workingDir).start(), StringUtil.join(cmdLine, " "), keepProcessAlive);
final ProcessBuilder processBuilder = new ProcessBuilder(cmdLine);
if (!launchInLinuxVM) {
processBuilder.directory(workingDir);
}
final ExternalJavacProcessHandler processHandler = createProcessHandler(processId, processBuilder.start(), StringUtil.join(cmdLine, " "), keepProcessAlive);
WSL_SUPPORT.set(processHandler, wslSupport);
PROCESS_HASH.set(processHandler, processHash);
processHandler.lock();
myRunningProcesses.put(processId, processHandler);
@@ -442,8 +477,10 @@ public class ExternalJavacManager extends ProcessAdapter {
}
}
private static String getVMExecutablePath(String sdkHome) {
return sdkHome + "/bin/java";
public void setWslExecutablePath(@Nullable Path wslExePath) {
if (wslExePath != null) {
myWslExePath = wslExePath.toAbsolutePath().toString();
}
}
protected static class ExternalJavacProcessHandler extends BaseOSProcessHandler {
@@ -707,4 +744,71 @@ public class ExternalJavacManager extends ProcessAdapter {
}
}
private static class WslToLinuxPathConverter implements ExternalJavacMessageHandler.WslSupport {
private static final String WSL_PATH_PREFIX = "//wsl$/";
private static final String MNT_PREFIX = "/mnt/";
private static final int MNT_PATTERN_LENGTH = MNT_PREFIX.length() + 2;
private final String myDistributionId;
WslToLinuxPathConverter(String distributionId) {
myDistributionId = distributionId;
}
public String getDistributionId() {
return myDistributionId;
}
@Override
public String convertPath(String path) {
final String normalized = FileUtilRt.toSystemIndependentName(path);
if (isWslPath(normalized)) {
final int distrSeparatorIndex = normalized.indexOf('/', WSL_PATH_PREFIX.length());
return distrSeparatorIndex > WSL_PATH_PREFIX.length()? normalized.substring(distrSeparatorIndex) : normalized;
}
if (isWinPath(normalized)) {
return MNT_PREFIX + Character.toLowerCase(normalized.charAt(0)) + normalized.substring(2);
}
return normalized;
}
public ExternalJavacMessageHandler.WslSupport reverseConverter() {
final String prefix = WSL_PATH_PREFIX + myDistributionId;
return path -> {
if (path.startsWith(MNT_PREFIX) && path.length() >= MNT_PATTERN_LENGTH) {
final char driveLetter = path.charAt(MNT_PREFIX.length());
if (Character.isLetter(driveLetter) && path.charAt(MNT_PATTERN_LENGTH - 1) == '/') {
return driveLetter + ":/" + path.substring(MNT_PATTERN_LENGTH);
}
}
return path.startsWith("/")? prefix + path : prefix + "/" + path;
};
}
@Nullable
static ExternalJavacManager.WslToLinuxPathConverter createFrom(String path) {
if (SystemInfo.isWin10OrNewer) {
path = FileUtilRt.toSystemIndependentName(path);
if (path.startsWith(WSL_PATH_PREFIX)) {
final int distrSeparatorIndex = path.indexOf('/', WSL_PATH_PREFIX.length());
if (distrSeparatorIndex > WSL_PATH_PREFIX.length()) {
return new WslToLinuxPathConverter(path.substring(WSL_PATH_PREFIX.length(), distrSeparatorIndex));
}
}
}
return null;
}
static boolean isWslPath(File file) {
return file != null && isWslPath(FileUtilRt.toSystemIndependentName(file.getAbsolutePath()));
}
static boolean isWslPath(String path) {
return path.startsWith(WSL_PATH_PREFIX);
}
static boolean isWinPath(String path) {
return path.length() >= 3 && Character.isLetter(path.charAt(0)) && path.charAt(1) == ':' && path.charAt(2) == '/';
}
}
}