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:
Jinbo Wang 2019-02-21 14:53:36 +08:00 committed by GitHub
parent a5d2bdfd3f
commit 7abda57511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 158 additions and 0 deletions

View File

@ -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.

View File

@ -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%",

View File

@ -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.",

View File

@ -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时跳过合成方法。",

View File

@ -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);

View File

@ -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;

View File

@ -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));
}

115
src/launchCommand.ts Normal file
View File

@ -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;
}