diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java index 0a7e77f03b0d..8e43611a7961 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java @@ -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); diff --git a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ExternalJavacMessageHandler.java b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ExternalJavacMessageHandler.java index 39b0818491dd..6ebaefe1169c 100644 --- a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ExternalJavacMessageHandler.java +++ b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ExternalJavacMessageHandler.java @@ -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; } diff --git a/jps/jps-builders-6/src/org/jetbrains/jps/javac/JavacProtoUtil.java b/jps/jps-builders-6/src/org/jetbrains/jps/javac/JavacProtoUtil.java index 8ac7382b00a0..b0a9647e425d 100644 --- a/jps/jps-builders-6/src/org/jetbrains/jps/javac/JavacProtoUtil.java +++ b/jps/jps-builders-6/src/org/jetbrains/jps/javac/JavacProtoUtil.java @@ -34,21 +34,22 @@ public final class JavacProtoUtil { ModulePath modulePath, Iterable upgradeModulePath, Iterable sourcePath, - Map> outs) { + Map> 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> 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()); } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java index dd58f31b369c..35ed6ad313a5 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java @@ -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; diff --git a/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacManager.java b/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacManager.java index bed150305bf5..2a76fb45d0e4 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacManager.java +++ b/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacManager.java @@ -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 PROCESS_ID_KEY = AttributeKey.valueOf("ExternalJavacServer.ProcessId"); private static final Key PROCESS_HASH = Key.create("ExternalJavacServer.SdkHomePath"); + private static final Key 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 mySessions = Collections.synchronizedMap(new HashMap<>()); private final Map myRunningProcesses = Collections.synchronizedMap(new HashMap<>()); private final Map 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 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 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) == '/'; + } + } } \ No newline at end of file