Provide jarmanifest/argfile approachs to shorten the command line (#532)
* Provide jarmainifest/argfile approachs to shorten the command line Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Add new launch.json config to README Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Fix the localizable words per comments Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Add auto mode to auto detect the right approach to shorten command line Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * fix build error Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Address the review comments Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Differentiate the command line length limit for different OS Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
This commit is contained in:
parent
a5d2bdfd3f
commit
7abda57511
|
@ -63,6 +63,11 @@ Please also check the documentation of [Language Support for Java by Red Hat](ht
|
|||
- `internalConsole` - VS Code debug console (input stream not supported).
|
||||
- `integratedTerminal` - VS Code integrated terminal.
|
||||
- `externalTerminal` - External terminal that can be configured in user settings.
|
||||
- `shortenCommandLine` - When the project has long classpath or big VM arguments, the command line to launch the program may exceed the maximum command line string limitation allowed by the OS. This configuration item provides multiple approaches to shorten the command line. Defaults to `auto`.
|
||||
- `none` - Launch the program with the standard command line 'java [options] classname [args]'.
|
||||
- `jarmanifest` - Generate the classpath parameters to a temporary classpath.jar file, and launch the program with the command line 'java -cp classpath.jar classname [args]'.
|
||||
- `argfile` - Generate the classpath parameters to a temporary argument file, and launch the program with the command line 'java @argfile [args]'. This value only applies to Java 9 and higher.
|
||||
- `auto` - Automatically detect the command line length and determine whether to shorten the command line via an appropriate approach.
|
||||
- `stepFilters` - Skip specified classes or methods when stepping.
|
||||
- `classNameFilters` - Skip the specified classes when stepping. Class names should be fully qualified. Wildcard is supported.
|
||||
- `skipSynthetics` - Skip synthetic methods when stepping.
|
||||
|
|
17
package.json
17
package.json
|
@ -148,6 +148,23 @@
|
|||
"description": "%java.debugger.launch.console.description%",
|
||||
"default": "internalConsole"
|
||||
},
|
||||
"shortenCommandLine": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"jarmanifest",
|
||||
"argfile",
|
||||
"auto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%java.debugger.launch.shortenCommandLine.none%",
|
||||
"%java.debugger.launch.shortenCommandLine.jarmanifest%",
|
||||
"%java.debugger.launch.shortenCommandLine.argfile%",
|
||||
"%java.debugger.launch.shortenCommandLine.auto%"
|
||||
],
|
||||
"description": "%java.debugger.launch.shortenCommandLine.description%",
|
||||
"default": "auto"
|
||||
},
|
||||
"stepFilters": {
|
||||
"type": "object",
|
||||
"description": "%java.debugger.launch.stepFilters.description%",
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
"java.debugger.launch.integratedTerminal.description": "VS Code integrated terminal.",
|
||||
"java.debugger.launch.externalTerminal.description": "External terminal that can be configured in user settings.",
|
||||
"java.debugger.launch.console.description": "The specified console to launch the program.",
|
||||
"java.debugger.launch.shortenCommandLine.auto": "Automatically detect the command line length and determine whether to shorten the command line via an appropriate approach.",
|
||||
"java.debugger.launch.shortenCommandLine.none": "Launch the program with the standard command line 'java [options] classname [args]'.",
|
||||
"java.debugger.launch.shortenCommandLine.jarmanifest": "Generate the classpath parameters to a temporary classpath.jar file, and launch the program with the command line 'java -cp classpath.jar classname [args]'.",
|
||||
"java.debugger.launch.shortenCommandLine.argfile": "Generate the classpath parameters to a temporary argument file, and launch the program with the command line 'java @argfile [args]'. This value only applies to Java 9 and higher.",
|
||||
"java.debugger.launch.shortenCommandLine.description": "When the project has long classpath or big VM arguments, the command line to launch the program may exceed the maximum command line string limitation allowed by the OS. This configuration item provides multiple approaches to shorten the command line.",
|
||||
"java.debugger.launch.stepFilters.description": "Skip specified classes or methods when stepping.",
|
||||
"java.debugger.launch.classNameFilters.description": "Skip the specified classes when stepping. Class names should be fully qualified. Wildcard is supported.",
|
||||
"java.debugger.launch.skipSynthetics.description": "Skip synthetic methods when stepping.",
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
"java.debugger.launch.integratedTerminal.description": "VS Code集成终端。",
|
||||
"java.debugger.launch.externalTerminal.description": "外部终端(可在用户设置中修改)。",
|
||||
"java.debugger.launch.console.description": "用于启动应用程序的控制台。",
|
||||
"java.debugger.launch.shortenCommandLine.auto": "自动检测命令行长度并决定是否通过适当的方法缩短命令行。",
|
||||
"java.debugger.launch.shortenCommandLine.none": "使用标准命令行 'java [options] classname [args]' 启动应用程序。",
|
||||
"java.debugger.launch.shortenCommandLine.jarmanifest": "将类路径参数生成到临时 classpath.jar 文件中,并使用命令行 'java -cp classpath.jar classname [args]' 启动应用程序。",
|
||||
"java.debugger.launch.shortenCommandLine.argfile": "将类路径参数生成到临时 argument 文件中, 并使用命令行 'java @argfile [args]' 启动应用程序。该值仅适用于 Java 9 及以上版本。",
|
||||
"java.debugger.launch.shortenCommandLine.description": "当项目具有较长的类路径或较大的VM参数时,启动程序的命令行可能会超出OS允许的最大命令行字符串限制。此配置项提供了多种缩短命令行的方法。",
|
||||
"java.debugger.launch.stepFilters.description": "Step时跳过指定的类或方法。",
|
||||
"java.debugger.launch.classNameFilters.description": "Step时跳过指定的类。仅支持全名,以及通配符。",
|
||||
"java.debugger.launch.skipSynthetics.description": "Step时跳过合成方法。",
|
||||
|
|
|
@ -25,6 +25,8 @@ export const JAVA_UPDATE_DEBUG_SETTINGS = "vscode.java.updateDebugSettings";
|
|||
|
||||
export const JAVA_RESOLVE_MAINMETHOD = "vscode.java.resolveMainMethod";
|
||||
|
||||
export const JAVA_INFER_LAUNCH_COMMAND_LENGTH = "vscode.java.inferLaunchCommandLength";
|
||||
|
||||
export function executeJavaLanguageServerCommand(...rest) {
|
||||
// TODO: need to handle error and trace telemetry
|
||||
return vscode.commands.executeCommand(JAVA_EXECUTE_WORKSPACE_COMMAND, ...rest);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { instrumentOperation } from "vscode-extension-telemetry-wrapper";
|
|||
import * as anchor from "./anchor";
|
||||
import * as commands from "./commands";
|
||||
import * as lsPlugin from "./languageServerPlugin";
|
||||
import { detectLaunchCommandStyle } from "./launchCommand";
|
||||
import { logger, Type } from "./logger";
|
||||
import * as utility from "./utility";
|
||||
import { VariableResolver } from "./variableResolver";
|
||||
|
@ -209,6 +210,10 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
config.vmArgs = this.concatArgs(config.vmArgs);
|
||||
}
|
||||
|
||||
if (config.request === "launch" && (!config.shortenCommandLine || config.shortenCommandLine === "auto")) {
|
||||
config.shortenCommandLine = await detectLaunchCommandStyle(config);
|
||||
}
|
||||
|
||||
const debugServerPort = await lsPlugin.startDebugSession();
|
||||
if (debugServerPort) {
|
||||
config.debugServer = debugServerPort;
|
||||
|
|
|
@ -57,3 +57,7 @@ export function validateLaunchConfig(workspaceUri: vscode.Uri, mainClass: string
|
|||
return <Promise<ILaunchValidationResponse>>commands.executeJavaLanguageServerCommand(commands.JAVA_VALIDATE_LAUNCHCONFIG,
|
||||
workspaceUri ? workspaceUri.toString() : undefined, mainClass, projectName, containsExternalClasspaths);
|
||||
}
|
||||
|
||||
export function inferLaunchCommandLength(config: vscode.DebugConfiguration): Promise<number> {
|
||||
return <Promise<number>>commands.executeJavaLanguageServerCommand(commands.JAVA_INFER_LAUNCH_COMMAND_LENGTH, JSON.stringify(config));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
import * as cp from "child_process";
|
||||
import * as _ from "lodash";
|
||||
import * as path from "path";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { inferLaunchCommandLength } from "./languageServerPlugin";
|
||||
import { getJavaHome } from "./utility";
|
||||
|
||||
enum shortenApproach {
|
||||
none = "none",
|
||||
jarmanifest = "jarmanifest",
|
||||
argfile = "argfile",
|
||||
}
|
||||
|
||||
export async function detectLaunchCommandStyle(config: vscode.DebugConfiguration): Promise<shortenApproach> {
|
||||
const javaHome = await getJavaHome();
|
||||
const javaVersion = await checkJavaVersion(javaHome);
|
||||
const recommendedShortenApproach = javaVersion <= 8 ? shortenApproach.jarmanifest : shortenApproach.argfile;
|
||||
return (await shouldShortenIfNecessary(config)) ? recommendedShortenApproach : shortenApproach.none;
|
||||
}
|
||||
|
||||
function checkJavaVersion(javaHome: string): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
cp.execFile(javaHome + "/bin/java", ["-version"], {}, (error, stdout, stderr) => {
|
||||
const javaVersion = parseMajorVersion(stderr);
|
||||
resolve(javaVersion);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parseMajorVersion(content: string): number {
|
||||
let regexp = /version "(.*)"/g;
|
||||
let match = regexp.exec(content);
|
||||
if (!match) {
|
||||
return 0;
|
||||
}
|
||||
let version = match[1];
|
||||
// Ignore '1.' prefix for legacy Java versions
|
||||
if (version.startsWith("1.")) {
|
||||
version = version.substring(2);
|
||||
}
|
||||
|
||||
// look into the interesting bits now
|
||||
regexp = /\d+/g;
|
||||
match = regexp.exec(version);
|
||||
let javaVersion = 0;
|
||||
if (match) {
|
||||
javaVersion = parseInt(match[0], 10);
|
||||
}
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
async function shouldShortenIfNecessary(config: vscode.DebugConfiguration): Promise<boolean> {
|
||||
const cliLength = await inferLaunchCommandLength(config);
|
||||
const classPathLength = (config.classPaths || []).join(path.delimiter).length;
|
||||
const modulePathLength = (config.modulePaths || []).join(path.delimiter).length;
|
||||
if (!config.console || config.console === "internalConsole") {
|
||||
return cliLength >= getMaxProcessCommandLineLength(config) || classPathLength >= getMaxArgLength() || modulePathLength >= getMaxArgLength();
|
||||
} else {
|
||||
return cliLength >= getMaxTerminalCommandLineLength(config) || classPathLength >= getMaxArgLength() || modulePathLength >= getMaxArgLength();
|
||||
}
|
||||
}
|
||||
|
||||
function getMaxProcessCommandLineLength(config: vscode.DebugConfiguration): number {
|
||||
const ARG_MAX_WINDOWS = 32768;
|
||||
const ARG_MAX_MACOS = 262144;
|
||||
const ARG_MAX_LINUX = 2097152;
|
||||
// for Posix systems, ARG_MAX is the maximum length of argument to the exec functions including environment data.
|
||||
// POSIX suggests to subtract 2048 additionally so that the process may safely modify its environment.
|
||||
// see https://www.in-ulm.de/~mascheck/various/argmax/
|
||||
if (process.platform === "win32") {
|
||||
// https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553/
|
||||
// On windows, the max process commmand line length is 32k (32768) characters.
|
||||
return ARG_MAX_WINDOWS - 2048;
|
||||
} else if (process.platform === "darwin") {
|
||||
return ARG_MAX_MACOS - getEnvironmentLength(config) - 2048;
|
||||
} else if (process.platform === "linux") {
|
||||
return ARG_MAX_LINUX - getEnvironmentLength(config) - 2048;
|
||||
}
|
||||
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
function getMaxTerminalCommandLineLength(config: vscode.DebugConfiguration): number {
|
||||
const MAX_CMD_WINDOWS = 8192;
|
||||
if (process.platform === "win32") {
|
||||
// https://support.microsoft.com/en-us/help/830473/command-prompt-cmd--exe-command-line-string-limitation
|
||||
// On windows, the max command line length for cmd terminal is 8192 characters.
|
||||
return MAX_CMD_WINDOWS;
|
||||
}
|
||||
|
||||
return getMaxProcessCommandLineLength(config);
|
||||
}
|
||||
|
||||
function getEnvironmentLength(config: vscode.DebugConfiguration): number {
|
||||
const env = config.env || {};
|
||||
return _.isEmpty(env) ? 0 : Object.keys(env).map((key) => strlen(key) + strlen(env[key]) + 1).reduce((a, b) => a + b);
|
||||
}
|
||||
|
||||
function strlen(str: string): number {
|
||||
return str ? str.length : 0;
|
||||
}
|
||||
|
||||
function getMaxArgLength(): number {
|
||||
const MAX_ARG_STRLEN_LINUX = 131072;
|
||||
if (process.platform === "linux") {
|
||||
// On Linux, MAX_ARG_STRLEN (kernel >= 2.6.23) is the maximum length of a command line argument (or environment variable). Its value
|
||||
// cannot be changed without recompiling the kernel.
|
||||
return MAX_ARG_STRLEN_LINUX - 2048;
|
||||
}
|
||||
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
Loading…
Reference in New Issue