Skip to content

Commit 60313c6

Browse files
Customize launching connector to support cwd and enviroment variable (microsoft#89)
* Customize launching connector to support cwd and environment variables Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Show a warning message in logger for duplicated variable Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * fix review comments Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Trigger travis ci * Move customized launching connector to plugin project instead of core Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * fix review comments Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Use URLEncoder/Decoder to encode/decode string array Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
1 parent ece63b8 commit 60313c6

File tree

8 files changed

+286
-14
lines changed

8 files changed

+286
-14
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugUtility.java

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
import java.io.File;
1515
import java.io.IOException;
16+
import java.io.UnsupportedEncodingException;
17+
import java.net.URLDecoder;
18+
import java.net.URLEncoder;
19+
import java.nio.charset.StandardCharsets;
1620
import java.util.ArrayList;
1721
import java.util.List;
1822
import java.util.Map;
@@ -36,9 +40,10 @@
3640
import com.sun.jdi.request.StepRequest;
3741

3842
public class DebugUtility {
39-
public static IDebugSession launch(VirtualMachineManager vmManager, String mainClass, String programArguments, String vmArguments, List<String> classPaths)
40-
throws IOException, IllegalConnectorArgumentsException, VMStartException {
41-
return DebugUtility.launch(vmManager, mainClass, programArguments, vmArguments, String.join(File.pathSeparator, classPaths));
43+
44+
public static IDebugSession launch(VirtualMachineManager vmManager, String mainClass, String programArguments, String vmArguments, List<String> classPaths,
45+
String cwd, String[] envVars) throws IOException, IllegalConnectorArgumentsException, VMStartException {
46+
return DebugUtility.launch(vmManager, mainClass, programArguments, vmArguments, String.join(File.pathSeparator, classPaths), cwd, envVars);
4247
}
4348

4449
/**
@@ -54,6 +59,11 @@ public static IDebugSession launch(VirtualMachineManager vmManager, String mainC
5459
* the vm arguments.
5560
* @param classPaths
5661
* the class paths.
62+
* @param cwd
63+
* the working directory of the program.
64+
* @param envVars
65+
* array of strings, each element of which has environment variable settings in the format name=value.
66+
* or null if the subprocess should inherit the environment of the current process.
5767
* @return an instance of IDebugSession.
5868
* @throws IOException
5969
* when unable to launch.
@@ -63,8 +73,8 @@ public static IDebugSession launch(VirtualMachineManager vmManager, String mainC
6373
* when the debuggee was successfully launched, but terminated
6474
* with an error before a connection could be established.
6575
*/
66-
public static IDebugSession launch(VirtualMachineManager vmManager, String mainClass, String programArguments, String vmArguments, String classPaths)
67-
throws IOException, IllegalConnectorArgumentsException, VMStartException {
76+
public static IDebugSession launch(VirtualMachineManager vmManager, String mainClass, String programArguments, String vmArguments, String classPaths,
77+
String cwd, String[] envVars) throws IOException, IllegalConnectorArgumentsException, VMStartException {
6878
List<LaunchingConnector> connectors = vmManager.launchingConnectors();
6979
LaunchingConnector connector = connectors.get(0);
7080

@@ -80,6 +90,15 @@ public static IDebugSession launch(VirtualMachineManager vmManager, String mainC
8090
} else {
8191
arguments.get("main").setValue(mainClass);
8292
}
93+
94+
if (arguments.get("cwd") != null) {
95+
arguments.get("cwd").setValue(cwd);
96+
}
97+
98+
if (arguments.get("env") != null) {
99+
arguments.get("env").setValue(encodeArrayArgument(envVars));
100+
}
101+
83102
VirtualMachine vm = connector.launch(arguments);
84103
// workaround for JDT bug.
85104
// vm.version() calls org.eclipse.jdi.internal.MirrorImpl#requestVM
@@ -242,4 +261,56 @@ public static void resumeThread(ThreadReference thread) {
242261
// the resume operation to this thread is meanness.
243262
}
244263
}
264+
265+
/**
266+
* Encode an string array to a string as the follows.
267+
*
268+
* <p>source argument:
269+
* <pre>["path=C:\\ProgramFiles\\java\\bin", "JAVA_HOME=C:\\ProgramFiles\\java"]</pre>
270+
*
271+
* <p>after encoded:
272+
* <pre>"path%3DC%3A%5CProgramFiles%5Cjava%5Cbin\nJAVA_HOME%3DC%3A%5CProgramFiles%5Cjava"</pre>
273+
*
274+
* @param argument the string array arguments
275+
* @return the encoded string
276+
*/
277+
public static String encodeArrayArgument(String[] argument) {
278+
if (argument == null) {
279+
return null;
280+
}
281+
282+
List<String> encodedArgs = new ArrayList<>();
283+
for (String arg : argument) {
284+
try {
285+
encodedArgs.add(URLEncoder.encode(arg, StandardCharsets.UTF_8.name()));
286+
} catch (UnsupportedEncodingException e) {
287+
// do nothing.
288+
}
289+
}
290+
return String.join("\n", encodedArgs);
291+
}
292+
293+
/**
294+
* Decode the encoded string to the original string array by the rules defined in encodeArrayArgument.
295+
*
296+
* @param argument the encoded string
297+
* @return the original string array argument
298+
*/
299+
public static String[] decodeArrayArgument(String argument) {
300+
if (argument == null) {
301+
return new String[0];
302+
}
303+
304+
List<String> result = new ArrayList<>();
305+
String[] splits = argument.split("\n");
306+
for (String split : splits) {
307+
try {
308+
result.add(URLDecoder.decode(split, StandardCharsets.UTF_8.name()));
309+
} catch (UnsupportedEncodingException e) {
310+
// do nothing.
311+
}
312+
}
313+
314+
return result.toArray(new String[0]);
315+
}
245316
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Requests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
package com.microsoft.java.debug.core.adapter;
1313

1414
import java.util.Arrays;
15+
import java.util.Map;
1516

1617
/**
1718
* The request arguments types defined by VSCode Debug Protocol.
@@ -48,6 +49,8 @@ public static class LaunchArguments extends Arguments {
4849
public String encoding = "";
4950
public String[] classPaths = new String[0];
5051
public String[] sourcePaths = new String[0];
52+
public String cwd;
53+
public Map<String, String> env;
5154
}
5255

5356
public static class AttachArguments extends Arguments {

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@
1111

1212
package com.microsoft.java.debug.core.adapter.handler;
1313

14+
import java.io.File;
1415
import java.io.IOException;
1516
import java.nio.charset.Charset;
1617
import java.nio.charset.StandardCharsets;
18+
import java.util.ArrayList;
1719
import java.util.Arrays;
20+
import java.util.HashMap;
1821
import java.util.List;
1922
import java.util.Map;
23+
import java.util.Map.Entry;
2024
import java.util.logging.Logger;
2125

2226
import org.apache.commons.lang3.StringUtils;
@@ -91,11 +95,35 @@ public void handle(Command command, Arguments arguments, Response response, IDeb
9195
}
9296
sourceProvider.initialize(options);
9397

98+
// Append environment to native environment.
99+
String[] envVars = null;
100+
if (launchArguments.env != null && !launchArguments.env.isEmpty()) {
101+
Map<String, String> environment = new HashMap<>(System.getenv());
102+
List<String> duplicated = new ArrayList<>();
103+
for (Entry<String, String> entry : launchArguments.env.entrySet()) {
104+
if (environment.containsKey(entry.getKey())) {
105+
duplicated.add(entry.getKey());
106+
}
107+
environment.put(entry.getKey(), entry.getValue());
108+
}
109+
// For duplicated variables, show a warning message.
110+
if (!duplicated.isEmpty()) {
111+
logger.warning(String.format("There are duplicated environment variables. The values specified in launch.json will be used. "
112+
+ "Here are the duplicated entries: %s.", String.join(",", duplicated)));
113+
}
114+
115+
envVars = new String[environment.size()];
116+
int i = 0;
117+
for (Entry<String, String> entry : environment.entrySet()) {
118+
envVars[i++] = entry.getKey() + "=" + entry.getValue();
119+
}
120+
}
121+
94122
try {
95123
logger.info(String.format("Trying to launch Java Program with options \"%s -cp %s %s %s\" .",
96-
launchArguments.vmArgs, StringUtils.join(launchArguments.classPaths, ";"), launchArguments.mainClass, launchArguments.args));
97-
IDebugSession debugSession = DebugUtility.launch(vmProvider.getVirtualMachineManager(),
98-
launchArguments.mainClass, launchArguments.args, launchArguments.vmArgs, Arrays.asList(launchArguments.classPaths));
124+
launchArguments.vmArgs, StringUtils.join(launchArguments.classPaths, File.pathSeparator), launchArguments.mainClass, launchArguments.args));
125+
IDebugSession debugSession = DebugUtility.launch(vmProvider.getVirtualMachineManager(), launchArguments.mainClass, launchArguments.args,
126+
launchArguments.vmArgs, Arrays.asList(launchArguments.classPaths), launchArguments.cwd, envVars);
99127
context.setDebugSession(debugSession);
100128
logger.info("Launching debuggee VM succeeded.");
101129

com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/AbstractJdiTestCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ protected BreakpointEvent waitForBreakPointEvent(String breakpointAtClass, int l
3838
}
3939
IDebugSession debugSession = getCurrentDebugSession();
4040

41-
IBreakpoint breakpointToAdd = getCurrentDebugSession().createBreakpoint(breakpointAtClass, line);
41+
IBreakpoint breakpointToAdd = debugSession.createBreakpoint(breakpointAtClass, line);
4242
breakpointToAdd.install().thenAccept(t -> {
4343
System.out.println("Breakpoint is accepted.");
4444
});

com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/DebugSessionFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static IDebugSession getDebugSession(String projectName, String mainClass
5252
String projectRoot = new File(rootPath, name).getAbsolutePath();
5353
try {
5454
final IDebugSession debugSession = DebugUtility.launch(Bootstrap.virtualMachineManager(), mainClass, "", "",
55-
new File(projectRoot, "bin").getAbsolutePath());
55+
new File(projectRoot, "bin").getAbsolutePath(), null, null);
5656
debugSession.eventHub().events().subscribe(debugEvent -> {
5757
if (debugEvent.event instanceof VMDisconnectEvent) {
5858
try {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.plugin.internal;
13+
14+
import java.io.File;
15+
import java.io.IOException;
16+
import java.nio.file.Files;
17+
import java.nio.file.Paths;
18+
import java.util.Map;
19+
20+
import org.eclipse.debug.core.DebugPlugin;
21+
import org.eclipse.jdi.internal.VirtualMachineImpl;
22+
import org.eclipse.jdi.internal.VirtualMachineManagerImpl;
23+
import org.eclipse.jdi.internal.connect.SocketLaunchingConnectorImpl;
24+
import org.eclipse.jdi.internal.connect.SocketListeningConnectorImpl;
25+
26+
import com.microsoft.java.debug.core.DebugUtility;
27+
import com.sun.jdi.VirtualMachine;
28+
import com.sun.jdi.connect.Connector;
29+
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
30+
import com.sun.jdi.connect.LaunchingConnector;
31+
import com.sun.jdi.connect.VMStartException;
32+
33+
/**
34+
* An advanced launching connector that supports cwd and enviroment variables.
35+
*
36+
*/
37+
public class AdvancedLaunchingConnector extends SocketLaunchingConnectorImpl implements LaunchingConnector {
38+
public static final String HOME = "home";
39+
public static final String OPTIONS = "options";
40+
public static final String MAIN = "main";
41+
public static final String SUSPEND = "suspend";
42+
public static final String QUOTE = "quote";
43+
public static final String EXEC = "vmexec";
44+
private static final String CWD = "cwd";
45+
private static final String ENV = "env";
46+
private static final int ACCEPT_TIMEOUT = 10 * 1000;
47+
48+
public AdvancedLaunchingConnector(VirtualMachineManagerImpl virtualMachineManager) {
49+
super(virtualMachineManager);
50+
}
51+
52+
@Override
53+
public Map<String, Argument> defaultArguments() {
54+
Map<String, Argument> defaultArgs = super.defaultArguments();
55+
56+
Argument cwdArg = new AdvancedStringArgumentImpl(CWD, "Current working directory", CWD, false);
57+
cwdArg.setValue(null);
58+
defaultArgs.put(CWD, cwdArg);
59+
60+
Argument envArg = new AdvancedStringArgumentImpl(ENV, "Environment variables", ENV, false);
61+
envArg.setValue(null);
62+
defaultArgs.put(ENV, envArg);
63+
64+
return defaultArgs;
65+
}
66+
67+
@Override
68+
public String name() {
69+
return "com.microsoft.java.debug.AdvancedLaunchingConnector";
70+
}
71+
72+
@Override
73+
public VirtualMachine launch(Map<String, ? extends Argument> connectionArgs)
74+
throws IOException, IllegalConnectorArgumentsException, VMStartException {
75+
String cwd = connectionArgs.get(CWD).value();
76+
File workingDir = null;
77+
if (cwd != null && Files.isDirectory(Paths.get(cwd))) {
78+
workingDir = new File(cwd);
79+
}
80+
81+
String[] envVars = null;
82+
try {
83+
envVars = DebugUtility.decodeArrayArgument(connectionArgs.get(ENV).value());
84+
} catch (IllegalArgumentException e) {
85+
// do nothing.
86+
}
87+
88+
SocketListeningConnectorImpl listenConnector = new SocketListeningConnectorImpl(
89+
virtualMachineManager());
90+
Map<String, Connector.Argument> args = listenConnector.defaultArguments();
91+
((Connector.IntegerArgument) args.get("timeout")).setValue(ACCEPT_TIMEOUT); //$NON-NLS-1$
92+
String address = listenConnector.startListening(args);
93+
94+
String[] cmds = constructLaunchCommand(connectionArgs, address);
95+
Process process = Runtime.getRuntime().exec(cmds, envVars, workingDir);
96+
97+
VirtualMachineImpl vm;
98+
try {
99+
vm = (VirtualMachineImpl) listenConnector.accept(args);
100+
} catch (IOException | IllegalConnectorArgumentsException e) {
101+
process.destroy();
102+
throw new VMStartException(String.format("VM did not connect within given time: %d ms", ACCEPT_TIMEOUT), process);
103+
}
104+
105+
vm.setLaunchedProcess(process);
106+
return vm;
107+
}
108+
109+
private static String[] constructLaunchCommand(Map<String, ? extends Argument> launchingOptions, String address) {
110+
final String javaHome = launchingOptions.get(HOME).value();
111+
final String javaExec = launchingOptions.get(EXEC).value();
112+
final String slash = System.getProperty("file.separator");
113+
boolean suspend = Boolean.valueOf(launchingOptions.get(SUSPEND).value());
114+
final String javaOptions = launchingOptions.get(OPTIONS).value();
115+
final String main = launchingOptions.get(MAIN).value();
116+
117+
StringBuilder execString = new StringBuilder();
118+
execString.append("\"" + javaHome + slash + "bin" + slash + javaExec + "\"");
119+
execString.append(" -Xdebug -Xnoagent -Djava.compiler=NONE");
120+
execString.append(" -Xrunjdwp:transport=dt_socket,address=" + address + ",server=n,suspend=" + (suspend ? "y" : "n"));
121+
if (javaOptions != null) {
122+
execString.append(" " + javaOptions);
123+
}
124+
execString.append(" " + main);
125+
126+
return DebugPlugin.parseArguments(execString.toString());
127+
}
128+
129+
class AdvancedStringArgumentImpl extends StringArgumentImpl implements StringArgument {
130+
private static final long serialVersionUID = 1L;
131+
132+
protected AdvancedStringArgumentImpl(String name, String description, String label, boolean mustSpecify) {
133+
super(name, description, label, mustSpecify);
134+
}
135+
}
136+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.plugin.internal;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
import org.eclipse.jdi.internal.VirtualMachineManagerImpl;
18+
19+
import com.sun.jdi.VirtualMachineManager;
20+
import com.sun.jdi.connect.LaunchingConnector;
21+
22+
public class AdvancedVirtualMachineManager extends VirtualMachineManagerImpl implements VirtualMachineManager {
23+
24+
@Override
25+
public List<LaunchingConnector> launchingConnectors() {
26+
List<LaunchingConnector> connectors = new ArrayList<>();
27+
connectors.add(new AdvancedLaunchingConnector(this));
28+
connectors.addAll(super.launchingConnectors());
29+
return connectors;
30+
}
31+
32+
}

0 commit comments

Comments
 (0)