forked from microsoft/vscode-java-debug
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlaunchCommand.ts
More file actions
199 lines (177 loc) · 7.38 KB
/
launchCommand.ts
File metadata and controls
199 lines (177 loc) · 7.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as cp from "child_process";
import * as fs from "fs";
import * as _ from "lodash";
import * as path from "path";
import * as vscode from "vscode";
import { UNSUPPORTED_CLASS_VERSION_ERROR } from "./anchor";
import { fetchPlatformSettings, inferLaunchCommandLength } from "./languageServerPlugin";
import { getJavaHome, showWarningMessageWithTroubleshooting } from "./utility";
enum shortenApproach {
none = "none",
jarmanifest = "jarmanifest",
argfile = "argfile",
}
const HELPFUL_NPE_VMARGS = "-XX:+ShowCodeDetailsInExceptionMessages";
/**
* Returns the recommended approach to shorten the command line length.
* @param config the launch configuration
* @param runtimeVersion the target runtime version
*/
export async function getShortenApproachForCLI(config: vscode.DebugConfiguration, runtimeVersion: number): Promise<shortenApproach> {
const recommendedShortenApproach = runtimeVersion <= 8 ? shortenApproach.jarmanifest : shortenApproach.argfile;
return (await shouldShortenIfNecessary(config)) ? recommendedShortenApproach : shortenApproach.none;
}
/**
* Validates whether the specified runtime version could be supported by the Java tooling.
* @param runtimeVersion the target runtime version
*/
export async function validateRuntimeCompatibility(runtimeVersion: number) {
try {
const platformSettings = await fetchPlatformSettings();
if (platformSettings && platformSettings.latestSupportedJavaVersion) {
const latestSupportedVersion = flattenMajorVersion(platformSettings.latestSupportedJavaVersion);
if (latestSupportedVersion < runtimeVersion) {
showWarningMessageWithTroubleshooting({
message: "The compiled classes are not compatible with the runtime JDK. To mitigate the issue, please refer to \"Learn More\".",
anchor: UNSUPPORTED_CLASS_VERSION_ERROR,
});
}
}
} catch (err) {
// do nothing
}
}
/**
* Add some helpful VM arguments to the launch configuration based on the target runtime version.
* @param config the launch configuration
* @param runtimeVersion the target runtime version
*/
export async function addMoreHelpfulVMArgs(config: vscode.DebugConfiguration, runtimeVersion: number) {
try {
if (runtimeVersion >= 14) {
// JEP-358: https://openjdk.java.net/jeps/358
if (config.vmArgs && config.vmArgs.indexOf(HELPFUL_NPE_VMARGS) >= 0) {
return;
}
config.vmArgs = (config.vmArgs || "") + " " + HELPFUL_NPE_VMARGS;
}
} catch (error) {
// do nothing.
}
}
/**
* Returns the target runtime version. If the javaExec is not specified, then return the current Java version
* that the Java tooling used.
* @param javaExec the path of the Java executable
*/
export async function getJavaVersion(javaExec: string): Promise<number> {
javaExec = javaExec || path.join(await getJavaHome(), "bin", "java");
let javaVersion = await checkVersionInReleaseFile(path.resolve(javaExec, "..", ".."));
if (!javaVersion) {
javaVersion = await checkVersionByCLI(javaExec);
}
return javaVersion;
}
async function checkVersionInReleaseFile(javaHome: string): Promise<number> {
if (!javaHome) {
return 0;
}
const releaseFile = path.join(javaHome, "release");
if (!await fs.existsSync(releaseFile)) {
return 0;
}
try {
const content = fs.readFileSync(releaseFile);
const regexp = /^JAVA_VERSION="(.*)"/gm;
const match = regexp.exec(content.toString());
if (!match) {
return 0;
}
const majorVersion = flattenMajorVersion(match[1]);
return majorVersion;
} catch (error) {
// ignore
}
return 0;
}
/**
* Get version by parsing `JAVA_HOME/bin/java -version`
*/
async function checkVersionByCLI(javaExec: string): Promise<number> {
if (!javaExec) {
return 0;
}
return new Promise((resolve) => {
cp.execFile(javaExec, ["-version"], {}, (_error, _stdout, stderr) => {
const regexp = /version "(.*)"/g;
const match = regexp.exec(stderr);
if (!match) {
return resolve(0);
}
const javaVersion = flattenMajorVersion(match[1]);
resolve(javaVersion);
});
});
}
function flattenMajorVersion(version: string): number {
// Ignore '1.' prefix for legacy Java versions
if (version.startsWith("1.")) {
version = version.substring(2);
}
// look into the interesting bits now
const regexp = /\d+/g;
const 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 classPaths = config.classPaths || [];
const modulePaths = config.modulePaths || [];
const classPathLength = classPaths.join(path.delimiter).length;
const modulePathLength = modulePaths.join(path.delimiter).length;
if (!config.console || config.console === "internalConsole") {
return cliLength >= getMaxProcessCommandLineLength(config) || classPathLength >= getMaxArgLength() || modulePathLength >= getMaxArgLength();
} else {
return classPaths.length > 1 || modulePaths.length > 1;
}
}
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 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;
}