Skip to content

Commit d3b8b3a

Browse files
Provide jarmanifest/argfile approachs to shorten the command line (#253)
* Provide jarmanifest/argfile approachs to shorten the command line Signed-off-by: Jinbo Wang <[email protected]> * Address review comments Signed-off-by: Jinbo Wang <[email protected]>
1 parent a1a02bc commit d3b8b3a

File tree

9 files changed

+227
-29
lines changed

9 files changed

+227
-29
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

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

14+
import java.io.File;
1415
import java.io.UnsupportedEncodingException;
16+
import java.net.MalformedURLException;
1517
import java.net.URI;
1618
import java.net.URISyntaxException;
1719
import java.net.URLDecoder;
@@ -159,6 +161,19 @@ public static String toUri(String path) {
159161
}
160162
}
161163

164+
/**
165+
* Convert a file path to an url string.
166+
* @param path
167+
* the file path
168+
* @return the url string
169+
* @throws MalformedURLException
170+
* if the file path cannot be constructed to an url because of some errors.
171+
*/
172+
public static String toUrl(String path) throws MalformedURLException {
173+
File file = new File(path);
174+
return file.toURI().toURL().toString();
175+
}
176+
162177
/**
163178
* Check a string variable is an uri or not.
164179
*/

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ public CompletableFuture<Messages.Response> dispatchRequest(Messages.Request req
7272
Command command = Command.parse(request.command);
7373
Arguments cmdArgs = JsonUtils.fromJson(request.arguments, command.getArgumentType());
7474

75-
if (debugContext.isVmTerminated()) {
76-
// the operation is meaningless
75+
if (debugContext.isVmTerminated() && command != Command.DISCONNECT) {
7776
return CompletableFuture.completedFuture(response);
7877
}
7978
List<IDebugRequestHandler> handlers = this.debugContext.getLaunchMode() == LaunchMode.DEBUG

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

Lines changed: 23 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.nio.charset.Charset;
15+
import java.nio.file.Path;
1516
import java.util.Collections;
1617
import java.util.Map;
1718

@@ -42,6 +43,8 @@ public class DebugAdapterContext implements IDebugAdapterContext {
4243
private Process debuggeeProcess;
4344
private String mainClass;
4445
private StepFilters stepFilters;
46+
private Path classpathJar = null;
47+
private Path argsfile = null;
4548

4649
private IdCollection<String> sourceReferences = new IdCollection<>();
4750
private RecyclableObjectPool<Long, Object> recyclableIdPool = new RecyclableObjectPool<>();
@@ -253,4 +256,24 @@ public Process getDebuggeeProcess() {
253256
public void setDebuggeeProcess(Process debuggeeProcess) {
254257
this.debuggeeProcess = debuggeeProcess;
255258
}
259+
260+
@Override
261+
public void setClasspathJar(Path classpathJar) {
262+
this.classpathJar = classpathJar;
263+
}
264+
265+
@Override
266+
public Path getClasspathJar() {
267+
return this.classpathJar;
268+
}
269+
270+
@Override
271+
public void setArgsfile(Path argsfile) {
272+
this.argsfile = argsfile;
273+
}
274+
275+
@Override
276+
public Path getArgsfile() {
277+
return this.argsfile;
278+
}
256279
}

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

Lines changed: 9 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.nio.charset.Charset;
15+
import java.nio.file.Path;
1516
import java.util.Map;
1617

1718
import com.microsoft.java.debug.core.IDebugSession;
@@ -109,4 +110,12 @@ public interface IDebugAdapterContext {
109110
Process getDebuggeeProcess();
110111

111112
void setDebuggeeProcess(Process debuggeeProcess);
113+
114+
void setClasspathJar(Path classpathJar);
115+
116+
Path getClasspathJar();
117+
118+
void setArgsfile(Path argsfile);
119+
120+
Path getArgsfile();
112121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2019 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.core.adapter.handler;
13+
14+
import java.io.IOException;
15+
import java.nio.file.Files;
16+
import java.util.Arrays;
17+
import java.util.List;
18+
import java.util.concurrent.CompletableFuture;
19+
import java.util.logging.Level;
20+
import java.util.logging.Logger;
21+
22+
import com.microsoft.java.debug.core.Configuration;
23+
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
24+
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
25+
import com.microsoft.java.debug.core.adapter.LaunchMode;
26+
import com.microsoft.java.debug.core.protocol.Messages.Response;
27+
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
28+
import com.microsoft.java.debug.core.protocol.Requests.Command;
29+
30+
public abstract class AbstractDisconnectRequestHandler implements IDebugRequestHandler {
31+
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
32+
33+
@Override
34+
public List<Command> getTargetCommands() {
35+
return Arrays.asList(Command.DISCONNECT);
36+
}
37+
38+
@Override
39+
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response,
40+
IDebugAdapterContext context) {
41+
destroyDebugSession(command, arguments, response, context);
42+
destroyResource(context);
43+
return CompletableFuture.completedFuture(response);
44+
}
45+
46+
/**
47+
* Destroy the resources generated by the debug session.
48+
*
49+
* @param context the debug context
50+
*/
51+
private void destroyResource(IDebugAdapterContext context) {
52+
if (shouldDestroyLaunchFiles(context)) {
53+
destroyLaunchFiles(context);
54+
}
55+
}
56+
57+
private boolean shouldDestroyLaunchFiles(IDebugAdapterContext context) {
58+
// Delete the temporary launch files must happen after the debuggee process is fully exited,
59+
// otherwise it throws error saying the file is being used by other process.
60+
// In Debug mode, the debugger is able to receive VM terminate event. It's sensible to do cleanup.
61+
// In noDebug mode, if the debuggee is launched internally by the debugger, the debugger knows
62+
// when the debuggee process exited. Should do cleanup. But if the debuggee is launched in the
63+
// integrated/external terminal, the debugger lost the contact with the debuggee after it's launched.
64+
// Have no idea when the debuggee is exited. So ignore the cleanup.
65+
return context.getLaunchMode() == LaunchMode.DEBUG || context.getDebuggeeProcess() != null;
66+
}
67+
68+
private void destroyLaunchFiles(IDebugAdapterContext context) {
69+
// Sometimes when the debug session is terminated, the debuggee process is not exited immediately.
70+
// Add retry to delete the temporary launch files.
71+
int retry = 5;
72+
while (retry-- > 0) {
73+
try {
74+
if (context.getClasspathJar() != null) {
75+
Files.deleteIfExists(context.getClasspathJar());
76+
context.setClasspathJar(null);
77+
}
78+
79+
if (context.getArgsfile() != null) {
80+
Files.deleteIfExists(context.getArgsfile());
81+
context.setArgsfile(null);
82+
}
83+
} catch (IOException e) {
84+
// do nothing.
85+
logger.log(Level.WARNING, "Failed to destory launch files, will retry again.");
86+
}
87+
88+
try {
89+
Thread.sleep(1000);
90+
} catch (InterruptedException e) {
91+
// do nothing.
92+
}
93+
}
94+
}
95+
96+
protected abstract void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context);
97+
}

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

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,17 @@
1111

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

14-
import java.util.Arrays;
15-
import java.util.List;
16-
import java.util.concurrent.CompletableFuture;
17-
1814
import com.microsoft.java.debug.core.IDebugSession;
1915
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
20-
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
2116
import com.microsoft.java.debug.core.protocol.Messages.Response;
2217
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
2318
import com.microsoft.java.debug.core.protocol.Requests.Command;
2419
import com.microsoft.java.debug.core.protocol.Requests.DisconnectArguments;
2520

26-
public class DisconnectRequestHandler implements IDebugRequestHandler {
27-
28-
@Override
29-
public List<Command> getTargetCommands() {
30-
return Arrays.asList(Command.DISCONNECT);
31-
}
21+
public class DisconnectRequestHandler extends AbstractDisconnectRequestHandler {
3222

3323
@Override
34-
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
24+
public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
3525
DisconnectArguments disconnectArguments = (DisconnectArguments) arguments;
3626
IDebugSession debugSession = context.getDebugSession();
3727
if (debugSession != null) {
@@ -41,7 +31,6 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
4131
debugSession.detach();
4232
}
4333
}
44-
return CompletableFuture.completedFuture(response);
4534
}
4635

4736
}

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,20 @@
1111

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

14-
import java.util.Arrays;
15-
import java.util.List;
16-
import java.util.concurrent.CompletableFuture;
17-
1814
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
19-
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
2015
import com.microsoft.java.debug.core.protocol.Messages.Response;
2116
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
2217
import com.microsoft.java.debug.core.protocol.Requests.Command;
2318
import com.microsoft.java.debug.core.protocol.Requests.DisconnectArguments;
2419

25-
public class DisconnectRequestWithoutDebuggingHandler implements IDebugRequestHandler {
20+
public class DisconnectRequestWithoutDebuggingHandler extends AbstractDisconnectRequestHandler {
2621

2722
@Override
28-
public List<Command> getTargetCommands() {
29-
return Arrays.asList(Command.DISCONNECT);
30-
}
31-
32-
@Override
33-
public CompletableFuture<Response> handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
23+
public void destroyDebugSession(Command command, Arguments arguments, Response response, IDebugAdapterContext context) {
3424
DisconnectArguments disconnectArguments = (DisconnectArguments) arguments;
3525
Process debuggeeProcess = context.getDebuggeeProcess();
3626
if (debuggeeProcess != null && disconnectArguments.terminateDebuggee) {
3727
debuggeeProcess.destroy();
3828
}
39-
return CompletableFuture.completedFuture(response);
4029
}
41-
4230
}

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

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

1414
import java.io.File;
15+
import java.io.FileOutputStream;
16+
import java.io.IOException;
17+
import java.net.MalformedURLException;
1518
import java.nio.charset.Charset;
1619
import java.nio.charset.StandardCharsets;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
1722
import java.util.ArrayList;
1823
import java.util.Arrays;
1924
import java.util.HashMap;
2025
import java.util.List;
2126
import java.util.Map;
2227
import java.util.Map.Entry;
2328
import java.util.concurrent.CompletableFuture;
29+
import java.util.jar.Attributes;
30+
import java.util.jar.JarOutputStream;
31+
import java.util.jar.Manifest;
32+
import java.util.logging.Level;
2433
import java.util.logging.Logger;
2534

2635
import org.apache.commons.lang3.ArrayUtils;
@@ -39,6 +48,7 @@
3948
import com.microsoft.java.debug.core.protocol.Requests.CONSOLE;
4049
import com.microsoft.java.debug.core.protocol.Requests.Command;
4150
import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments;
51+
import com.microsoft.java.debug.core.protocol.Requests.ShortenApproach;
4252

4353
public class LaunchRequestHandler implements IDebugRequestHandler {
4454
protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
@@ -87,6 +97,62 @@ protected CompletableFuture<Response> handleLaunchCommand(Arguments arguments, R
8797

8898
activeLaunchHandler.preLaunch(launchArguments, context);
8999

100+
Path tempfile = null;
101+
// Use the specified cli style to launch the program.
102+
if (launchArguments.shortenCommandLine == ShortenApproach.JARMANIFEST) {
103+
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
104+
List<String> classpathUrls = new ArrayList<>();
105+
for (String classpath : launchArguments.classPaths) {
106+
try {
107+
classpathUrls.add(AdapterUtils.toUrl(classpath));
108+
} catch (IllegalArgumentException | MalformedURLException ex) {
109+
logger.log(Level.SEVERE, String.format("Failed to launch the program with jarmanifest style: %s", ex.toString(), ex));
110+
throw AdapterUtils.createCompletionException("Failed to launch the program with jarmanifest style: " + ex.toString(),
111+
ErrorCode.LAUNCH_FAILURE, ex);
112+
}
113+
}
114+
115+
try {
116+
tempfile = Files.createTempFile("classpath_", ".jar");
117+
Manifest manifest = new Manifest();
118+
Attributes attributes = manifest.getMainAttributes();
119+
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
120+
// In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar
121+
attributes.put(Attributes.Name.CLASS_PATH, String.join(" ", classpathUrls));
122+
JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest);
123+
jar.close();
124+
launchArguments.vmArgs += " -cp \"" + tempfile.toAbsolutePath().toString() + "\"";
125+
launchArguments.classPaths = new String[0];
126+
context.setClasspathJar(tempfile);
127+
} catch (IOException e) {
128+
logger.log(Level.SEVERE, String.format("Failed to create a temp classpath.jar: %s", e.toString()), e);
129+
tempfile = null;
130+
}
131+
}
132+
} else if (launchArguments.shortenCommandLine == ShortenApproach.ARGFILE) {
133+
try {
134+
tempfile = Files.createTempFile("java_", ".argfile");
135+
String argfile = "";
136+
if (ArrayUtils.isNotEmpty(launchArguments.classPaths)) {
137+
argfile = "-classpath \"" + String.join(File.pathSeparator, launchArguments.classPaths) + "\"";
138+
}
139+
140+
if (ArrayUtils.isNotEmpty(launchArguments.modulePaths)) {
141+
argfile = " --module-path \"" + String.join(File.pathSeparator, launchArguments.modulePaths) + "\"";
142+
}
143+
144+
argfile = argfile.replace("\\", "\\\\");
145+
Files.write(tempfile, argfile.getBytes());
146+
launchArguments.vmArgs += " @" + tempfile.toAbsolutePath().toString();
147+
launchArguments.classPaths = new String[0];
148+
launchArguments.modulePaths = new String[0];
149+
context.setArgsfile(tempfile);
150+
} catch (IOException e) {
151+
logger.log(Level.SEVERE, String.format("Failed to create a temp argfile: %s", e.toString()), e);
152+
tempfile = null;
153+
}
154+
}
155+
90156
return launch(launchArguments, response, context).thenCompose(res -> {
91157
if (res.success) {
92158
activeLaunchHandler.postLaunch(launchArguments, context);

0 commit comments

Comments
 (0)