Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add 'auto' as the default value to unblock majority of use cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new PR microsoft/java-debug#254 on java-debug to help compute the launch command length.

- `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.
Expand Down
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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%",
Expand Down
5 changes: 5 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
5 changes: 5 additions & 0 deletions package.nls.zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -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时跳过合成方法。",
Expand Down
2 changes: 2 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions src/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/languageServerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 115 additions & 0 deletions src/launchCommand.ts
Original file line number Diff line number Diff line change
@@ -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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the third place that we need parse/check java version. Please work with @Eskibear & @jdneo for how we can consolidate this part logic together or put in the vscode java API part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree. but prefer to do it with a new PR when the change in vscode-java is ready.

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