diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java index 3b9174b64..bd182ae67 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java @@ -17,9 +17,11 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; @@ -57,7 +59,7 @@ public class DebugUtility { /** * Launch a debuggee in suspend mode. - * @see #launch(VirtualMachineManager, String, String, String, String, String) + * @see #launch(VirtualMachineManager, String, String, String, String, String, String, String[]) */ public static IDebugSession launch(VirtualMachineManager vmManager, String mainClass, @@ -78,6 +80,31 @@ public static IDebugSession launch(VirtualMachineManager vmManager, envVars); } + /** + * Launch a debuggee in suspend mode. + * @see #launch(VirtualMachineManager, String, String, String, String, String, String, String[], String) + */ + public static IDebugSession launch(VirtualMachineManager vmManager, + String mainClass, + String programArguments, + String vmArguments, + List modulePaths, + List classPaths, + String cwd, + String[] envVars, + String javaExec) + throws IOException, IllegalConnectorArgumentsException, VMStartException { + return DebugUtility.launch(vmManager, + mainClass, + programArguments, + vmArguments, + String.join(File.pathSeparator, modulePaths), + String.join(File.pathSeparator, classPaths), + cwd, + envVars, + javaExec); + } + /** * Launches a debuggee in suspend mode. * @@ -116,6 +143,50 @@ public static IDebugSession launch(VirtualMachineManager vmManager, String cwd, String[] envVars) throws IOException, IllegalConnectorArgumentsException, VMStartException { + return launch(vmManager, mainClass, programArguments, vmArguments, modulePaths, classPaths, cwd, envVars, null); + } + + /** + * Launches a debuggee in suspend mode. + * + * @param vmManager + * the virtual machine manager. + * @param mainClass + * the main class. + * @param programArguments + * the program arguments. + * @param vmArguments + * the vm arguments. + * @param modulePaths + * the module paths. + * @param classPaths + * the class paths. + * @param cwd + * the working directory of the program. + * @param envVars + * array of strings, each element of which has environment variable settings in the format name=value. + * or null if the subprocess should inherit the environment of the current process. + * @param javaExec + * the java executable path. If not defined, then resolve from java home. + * @return an instance of IDebugSession. + * @throws IOException + * when unable to launch. + * @throws IllegalConnectorArgumentsException + * when one of the arguments is invalid. + * @throws VMStartException + * when the debuggee was successfully launched, but terminated + * with an error before a connection could be established. + */ + public static IDebugSession launch(VirtualMachineManager vmManager, + String mainClass, + String programArguments, + String vmArguments, + String modulePaths, + String classPaths, + String cwd, + String[] envVars, + String javaExec) + throws IOException, IllegalConnectorArgumentsException, VMStartException { List connectors = vmManager.launchingConnectors(); LaunchingConnector connector = connectors.get(0); @@ -164,7 +235,12 @@ public static IDebugSession launch(VirtualMachineManager vmManager, arguments.get(ENV).setValue(encodeArrayArgument(envVars)); } - if (StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome)) { + if (isValidJavaExec(javaExec)) { + String vmExec = new File(javaExec).getName(); + String javaHome = new File(javaExec).getParentFile().getParentFile().getAbsolutePath(); + arguments.get(HOME).setValue(javaHome); + arguments.get(EXEC).setValue(vmExec); + } else if (StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome)) { arguments.get(HOME).setValue(DebugSettings.getCurrent().javaHome); } @@ -179,6 +255,20 @@ public static IDebugSession launch(VirtualMachineManager vmManager, return new DebugSession(vm); } + private static boolean isValidJavaExec(String javaExec) { + if (StringUtils.isBlank(javaExec)) { + return false; + } + + File file = new File(javaExec); + if (!file.exists() || !file.isFile()) { + return false; + } + + return Files.isExecutable(file.toPath()) + && Objects.equals(file.getParentFile().getName(), "bin"); + } + /** * Attach to an existing debuggee VM. * @param vmManager diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java index 16ad09cfa..26869a394 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java @@ -18,6 +18,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -183,14 +184,18 @@ protected void handleTerminatedEvent(IDebugAdapterContext context) { * @return the command arrays */ public static String[] constructLaunchCommands(LaunchArguments launchArguments, boolean serverMode, String address) { - String slash = System.getProperty("file.separator"); List launchCmds = new ArrayList<>(); if (launchArguments.launcherScript != null) { launchCmds.add(launchArguments.launcherScript); } - final String javaHome = StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome) ? DebugSettings.getCurrent().javaHome - : System.getProperty("java.home"); - launchCmds.add(javaHome + slash + "bin" + slash + "java"); + + if (StringUtils.isNotBlank(launchArguments.javaExec)) { + launchCmds.add(launchArguments.javaExec); + } else { + final String javaHome = StringUtils.isNotEmpty(DebugSettings.getCurrent().javaHome) ? DebugSettings.getCurrent().javaHome + : System.getProperty("java.home"); + launchCmds.add(Paths.get(javaHome, "bin", "java").toString()); + } if (StringUtils.isNotEmpty(address)) { launchCmds.add(String.format("-agentlib:jdwp=transport=dt_socket,server=%s,suspend=y,address=%s", serverMode ? "y" : "n", address)); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java index 0f690773a..c4585dd1f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java @@ -181,7 +181,8 @@ public Process launch(LaunchArguments launchArguments, IDebugAdapterContext cont Arrays.asList(launchArguments.modulePaths), Arrays.asList(launchArguments.classPaths), launchArguments.cwd, - LaunchRequestHandler.constructEnvironmentVariables(launchArguments)); + LaunchRequestHandler.constructEnvironmentVariables(launchArguments), + launchArguments.javaExec); context.setDebugSession(debugSession); logger.info("Launching debuggee VM succeeded."); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java index 7fc32452e..1874825e7 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Requests.java @@ -86,6 +86,7 @@ public static class LaunchArguments extends LaunchBaseArguments { public CONSOLE console = CONSOLE.integratedTerminal; public ShortenApproach shortenCommandLine = ShortenApproach.NONE; public String launcherScript; + public String javaExec; } public static class AttachArguments extends LaunchBaseArguments { diff --git a/com.microsoft.java.debug.plugin/plugin.xml b/com.microsoft.java.debug.plugin/plugin.xml index d71e1c7fd..5f7634b14 100644 --- a/com.microsoft.java.debug.plugin/plugin.xml +++ b/com.microsoft.java.debug.plugin/plugin.xml @@ -16,6 +16,7 @@ + diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java index 7a1b55dfc..8284abc23 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java @@ -43,6 +43,7 @@ public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler public static final String RESOLVE_ELEMENT_AT_SELECTION = "vscode.java.resolveElementAtSelection"; public static final String RESOLVE_BUILD_FILES = "vscode.java.resolveBuildFiles"; public static final String IS_ON_CLASSPATH = "vscode.java.isOnClasspath"; + public static final String RESOLVE_JAVA_EXECUTABLE = "vscode.java.resolveJavaExecutable"; @Override public Object executeCommand(String commandId, List arguments, IProgressMonitor progress) throws Exception { @@ -78,6 +79,8 @@ public Object executeCommand(String commandId, List arguments, IProgress return getBuildFiles(); case IS_ON_CLASSPATH: return isOnClasspath(arguments); + case RESOLVE_JAVA_EXECUTABLE: + return ResolveJavaExecutableHandler.resolveJavaExecutable(arguments); default: break; } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveJavaExecutableHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveJavaExecutableHandler.java new file mode 100644 index 000000000..cd368a6d2 --- /dev/null +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveJavaExecutableHandler.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2020 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.plugin.internal; + +import java.io.File; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.JavaRuntime; + +import com.microsoft.java.debug.core.Configuration; + +public class ResolveJavaExecutableHandler { + private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private static final String[] javaExecCandidates = { + "java", + "java.exe", + "javaw", + "javaw.exe", + "j9", + "j9.exe", + "j9w", + "j9w.exe" + }; + private static final String[] javaBinCandidates = { + File.separator, + "bin" + File.separatorChar, + "jre" + File.separatorChar + "bin" + File.separatorChar + }; + + /** + * Resolve the java executable path from the project's java runtime. + */ + public static String resolveJavaExecutable(List arguments) throws Exception { + try { + String mainClass = (String) arguments.get(0); + String projectName = (String) arguments.get(1); + IJavaProject targetProject = null; + if (StringUtils.isNotBlank(projectName)) { + targetProject = JdtUtils.getJavaProject(projectName); + } else { + List targetProjects = ResolveClasspathsHandler.getJavaProjectFromType(mainClass); + if (!targetProjects.isEmpty()) { + targetProject = targetProjects.get(0); + } + } + + if (targetProject == null) { + return null; + } + + IVMInstall vmInstall = JavaRuntime.getVMInstall(targetProject); + if (vmInstall == null || vmInstall.getInstallLocation() == null) { + return null; + } + + File exe = findJavaExecutable(vmInstall.getInstallLocation()); + if (exe == null) { + return null; + } + + return exe.getAbsolutePath(); + } catch (CoreException e) { + logger.log(Level.SEVERE, "Failed to resolve java executable: " + e.getMessage(), e); + } + + return null; + } + + private static File findJavaExecutable(File vmInstallLocation) { + boolean isBin = Objects.equals("bin", vmInstallLocation.getName()); + for (int i = 0; i < javaExecCandidates.length; i++) { + for (int j = 0; j < javaBinCandidates.length; j++) { + if (!isBin && j == 0) { + continue; + } + File javaFile = new File(vmInstallLocation, Paths.get(javaBinCandidates[j], javaExecCandidates[i]).toString()); + if (javaFile.isFile()) { + return javaFile; + } + } + } + + return null; + } +}