Skip to content

Commit 17edf20

Browse files
Automatically register VMInstalls for the JDKs installed on the local machine (#3301)
* Automatically register VMInstalls for the JDKs installed on the local machine * Bump to [email protected] to fix wrong jdk detection issue on macOS * Validate if the detected jdk is a real java home * Add a setting 'java.configuration.detectJdksAtStart' to control whether to detect jdks on start
1 parent a9b87bd commit 17edf20

File tree

11 files changed

+169
-41
lines changed

11 files changed

+169
-41
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ The following settings are supported:
239239
- `manual`: Manually reload the sources of the open class files
240240
* `java.edit.smartSemicolonDetection.enabled`: Defines the `smart semicolon` detection. Defaults to `false`.
241241

242+
New in 1.23.0
243+
* `java.configuration.detectJdksAtStart`: Automatically detect JDKs installed on local machine at startup. If you have specified the same JDK version in `java.configuration.runtimes`, the extension will use that version first. Defaults to `true`.
244+
242245
Semantic Highlighting
243246
===============
244247
[Semantic Highlighting](https://github.com/redhat-developer/vscode-java/wiki/Semantic-Highlighting) fixes numerous syntax highlighting issues with the default Java Textmate grammar. However, you might experience a few minor issues, particularly a delay when it kicks in, as it needs to be computed by the Java Language server, when opening a new file or when typing. Semantic highlighting can be disabled for all languages using the `editor.semanticHighlighting.enabled` setting, or for Java only using [language-specific editor settings](https://code.visualstudio.com/docs/getstarted/settings#_languagespecific-editor-settings).

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,11 @@
833833
"default": [],
834834
"scope": "machine-overridable"
835835
},
836+
"java.configuration.detectJdksAtStart": {
837+
"type": "boolean",
838+
"default": true,
839+
"markdownDescription": "Automatically detect JDKs installed on local machine at startup. If you have specified the same JDK version in `#java.configuration.runtimes#`, the extension will use that version first."
840+
},
836841
"java.server.launchMode": {
837842
"type": "string",
838843
"enum": [
@@ -1529,7 +1534,7 @@
15291534
"fs-extra": "^8.1.0",
15301535
"glob": "^7.1.3",
15311536
"htmlparser2": "6.0.1",
1532-
"jdk-utils": "^0.4.4",
1537+
"jdk-utils": "^0.5.1",
15331538
"react": "^17.0.2",
15341539
"react-dom": "^17.0.2",
15351540
"semver": "^7.5.2",

src/extension.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { Telemetry } from './telemetry';
3535
import { getMessage } from './errorUtils';
3636
import { TelemetryService } from '@redhat-developer/vscode-redhat-telemetry/lib';
3737
import { activationProgressNotification } from "./serverTaskPresenter";
38+
import { loadSupportedJreNames } from './jdkUtils';
3839

3940
const syntaxClient: SyntaxLanguageClient = new SyntaxLanguageClient();
4041
const standardClient: StandardLanguageClient = new StandardLanguageClient();
@@ -89,7 +90,8 @@ function getHeapDumpFolderFromSettings(): string {
8990
}
9091

9192

92-
export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
93+
export async function activate(context: ExtensionContext): Promise<ExtensionAPI> {
94+
await loadSupportedJreNames(context);
9395
context.subscriptions.push(markdownPreviewProvider);
9496
context.subscriptions.push(commands.registerCommand(Commands.TEMPLATE_VARIABLES, async () => {
9597
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `${Commands.TEMPLATE_VARIABLES}.md`)), 'Predefined Variables', "", context);
@@ -163,7 +165,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
163165
initializationOptions: {
164166
bundles: collectJavaExtensions(extensions.all),
165167
workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null,
166-
settings: { java: getJavaConfig(requirements.java_home) },
168+
settings: { java: await getJavaConfig(requirements.java_home) },
167169
extendedClientCapabilities: {
168170
classFileContentsSupport: true,
169171
overrideMethodsPromptSupport: true,
@@ -192,7 +194,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
192194
didChangeConfiguration: async () => {
193195
await standardClient.getClient().sendNotification(DidChangeConfigurationNotification.type, {
194196
settings: {
195-
java: getJavaConfig(requirements.java_home),
197+
java: await getJavaConfig(requirements.java_home),
196198
}
197199
});
198200
}
@@ -275,7 +277,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
275277
// the promise is resolved
276278
// no need to pass `resolve` into any code past this point,
277279
// since `resolve` is a no-op from now on
278-
const serverOptions = prepareExecutable(requirements, syntaxServerWorkspacePath, getJavaConfig(requirements.java_home), context, true);
280+
const serverOptions = prepareExecutable(requirements, syntaxServerWorkspacePath, context, true);
279281
if (requireSyntaxServer) {
280282
if (process.env['SYNTAXLS_CLIENT_PORT']) {
281283
syntaxClient.initialize(requirements, clientOptions);

src/javaServerStarter.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const HEAP_DUMP = '-XX:+HeapDumpOnOutOfMemoryError';
3434
const DEPENDENCY_COLLECTOR_IMPL= '-Daether.dependencyCollector.impl=';
3535
const DEPENDENCY_COLLECTOR_IMPL_BF= 'bf';
3636

37-
export function prepareExecutable(requirements: RequirementsData, workspacePath, javaConfig, context: ExtensionContext, isSyntaxServer: boolean): Executable {
37+
export function prepareExecutable(requirements: RequirementsData, workspacePath, context: ExtensionContext, isSyntaxServer: boolean): Executable {
3838
const executable: Executable = Object.create(null);
3939
const options: ExecutableOptions = Object.create(null);
4040
options.env = Object.assign({ syntaxserver : isSyntaxServer }, process.env);
@@ -47,7 +47,7 @@ export function prepareExecutable(requirements: RequirementsData, workspacePath,
4747
}
4848
executable.options = options;
4949
executable.command = path.resolve(`${requirements.tooling_jre}/bin/java`);
50-
executable.args = prepareParams(requirements, javaConfig, workspacePath, context, isSyntaxServer);
50+
executable.args = prepareParams(requirements, workspacePath, context, isSyntaxServer);
5151
logger.info(`Starting Java server with: ${executable.command} ${executable.args.join(' ')}`);
5252
return executable;
5353
}
@@ -68,7 +68,7 @@ export function awaitServerConnection(port): Thenable<StreamInfo> {
6868
});
6969
}
7070

71-
function prepareParams(requirements: RequirementsData, javaConfiguration, workspacePath, context: ExtensionContext, isSyntaxServer: boolean): string[] {
71+
function prepareParams(requirements: RequirementsData, workspacePath, context: ExtensionContext, isSyntaxServer: boolean): string[] {
7272
const params: string[] = [];
7373
if (DEBUG) {
7474
const port = isSyntaxServer ? 1045 : 1044;
@@ -117,6 +117,9 @@ function prepareParams(requirements: RequirementsData, javaConfiguration, worksp
117117
} else {
118118
vmargs = '';
119119
}
120+
if (vmargs.indexOf('-DDetectVMInstallationsJob.disabled=') < 0) {
121+
params.push('-DDetectVMInstallationsJob.disabled=true');
122+
}
120123
const encodingKey = '-Dfile.encoding=';
121124
if (vmargs.indexOf(encodingKey) < 0) {
122125
params.push(encodingKey + getJavaEncoding());

src/jdkUtils.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict';
2+
3+
import { existsSync } from 'fs';
4+
import { IJavaRuntime, findRuntimes, getSources } from 'jdk-utils';
5+
import { join } from 'path';
6+
import { ExtensionContext, Uri, workspace } from 'vscode';
7+
8+
let cachedJdks: IJavaRuntime[];
9+
let cachedJreNames: string[];
10+
11+
export async function loadSupportedJreNames(context: ExtensionContext): Promise<void> {
12+
const buffer = await workspace.fs.readFile(Uri.file(context.asAbsolutePath("package.json")));
13+
const packageJson = JSON.parse(buffer.toString());
14+
cachedJreNames = packageJson?.contributes?.configuration?.properties?.["java.configuration.runtimes"]?.items?.properties?.name?.enum;
15+
}
16+
17+
export function getSupportedJreNames(): string[] {
18+
return cachedJreNames;
19+
}
20+
21+
export async function listJdks(force?: boolean): Promise<IJavaRuntime[]> {
22+
if (force || !cachedJdks) {
23+
cachedJdks = await findRuntimes({ checkJavac: true, withVersion: true, withTags: true })
24+
.then(jdks => jdks.filter(jdk => {
25+
// Validate if it's a real Java Home.
26+
return existsSync(join(jdk.homedir, "lib", "rt.jar"))
27+
|| existsSync(join(jdk.homedir, "jre", "lib", "rt.jar")) // Java 8
28+
|| existsSync(join(jdk.homedir, "lib", "jrt-fs.jar")); // Java 9+
29+
}));
30+
}
31+
32+
return [].concat(cachedJdks);
33+
}
34+
35+
/**
36+
* Sort by source where JDk is located.
37+
* The order is:
38+
* 1. JDK_HOME, JAVA_HOME, PATH
39+
* 2. JDK manager such as SDKMAN, jEnv, jabba, asdf
40+
* 3. Common places such as /usr/lib/jvm
41+
* 4. Others
42+
*/
43+
export function sortJdksBySource(jdks: IJavaRuntime[]) {
44+
const rankedJdks = jdks as Array<IJavaRuntime & { rank: number }>;
45+
const env: string[] = ["JDK_HOME", "JAVA_HOME", "PATH"];
46+
const jdkManagers: string[] = ["SDKMAN", "jEnv", "jabba", "asdf"];
47+
for (const jdk of rankedJdks) {
48+
const detectedSources: string[] = getSources(jdk);
49+
for (const [index, source] of env.entries()) {
50+
if (detectedSources.includes(source)) {
51+
jdk.rank = index; // jdk from environment variables
52+
break;
53+
}
54+
}
55+
56+
if (jdk.rank) {
57+
continue;
58+
}
59+
60+
const fromManager: boolean = detectedSources.some(source => jdkManagers.includes(source));
61+
if (fromManager) {
62+
jdk.rank = env.length + 1; // jdk from the jdk managers such as SDKMAN
63+
} else if (!detectedSources.length){
64+
jdk.rank = env.length + 2; // jdk from common places
65+
} else {
66+
jdk.rank = env.length + 3; // jdk from other source such as ~/.gradle/jdks
67+
}
68+
}
69+
rankedJdks.sort((a, b) => a.rank - b.rank);
70+
}
71+
72+
/**
73+
* Sort by major version in descend order.
74+
*/
75+
export function sortJdksByVersion(jdks: IJavaRuntime[]) {
76+
jdks.sort((a, b) => (b.version?.major ?? 0) - (a.version?.major ?? 0));
77+
}

src/requirements.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import * as expandHomeDir from 'expand-home-dir';
44
import * as fse from 'fs-extra';
5-
import { findRuntimes, getRuntime, getSources, IJavaRuntime, JAVAC_FILENAME, JAVA_FILENAME } from 'jdk-utils';
5+
import { getRuntime, getSources, JAVAC_FILENAME, JAVA_FILENAME } from 'jdk-utils';
66
import * as path from 'path';
77
import { env, ExtensionContext, Uri, window, workspace } from 'vscode';
88
import { Commands } from './commands';
99
import { logger } from './log';
1010
import { checkJavaPreferences } from './settings';
11+
import { listJdks, sortJdksBySource, sortJdksByVersion } from './jdkUtils';
1112

1213
const REQUIRED_JDK_VERSION = 17;
1314
/* eslint-disable @typescript-eslint/naming-convention */
@@ -70,7 +71,7 @@ export async function resolveRequirements(context: ExtensionContext): Promise<Re
7071
}
7172

7273
// search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories
73-
const javaRuntimes = await findRuntimes({ checkJavac: true, withVersion: true, withTags: true });
74+
const javaRuntimes = await listJdks();
7475
if (!toolingJre) { // universal version
7576
// as latest version as possible.
7677
sortJdksByVersion(javaRuntimes);
@@ -159,27 +160,6 @@ async function findDefaultRuntimeFromSettings(): Promise<string | undefined> {
159160
return undefined;
160161
}
161162

162-
export function sortJdksBySource(jdks: IJavaRuntime[]) {
163-
const rankedJdks = jdks as Array<IJavaRuntime & { rank: number }>;
164-
const sources = ["JDK_HOME", "JAVA_HOME", "PATH"];
165-
for (const [index, source] of sources.entries()) {
166-
for (const jdk of rankedJdks) {
167-
if (jdk.rank === undefined && getSources(jdk).includes(source)) {
168-
jdk.rank = index;
169-
}
170-
}
171-
}
172-
rankedJdks.filter(jdk => jdk.rank === undefined).forEach(jdk => jdk.rank = sources.length);
173-
rankedJdks.sort((a, b) => a.rank - b.rank);
174-
}
175-
176-
/**
177-
* Sort by major version in descend order.
178-
*/
179-
export function sortJdksByVersion(jdks: IJavaRuntime[]) {
180-
jdks.sort((a, b) => (b.version?.major ?? 0) - (a.version?.major ?? 0));
181-
}
182-
183163
export function parseMajorVersion(version: string): number {
184164
if (!version) {
185165
return 0;

src/standardLanguageClient.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
'use strict';
22

3-
import * as fse from 'fs-extra';
4-
import { findRuntimes } from "jdk-utils";
53
import * as net from 'net';
64
import * as path from 'path';
75
import { CancellationToken, CodeActionKind, commands, ConfigurationTarget, DocumentSelector, EventEmitter, ExtensionContext, extensions, languages, Location, ProgressLocation, TextEditor, Uri, ViewColumn, window, workspace } from "vscode";
@@ -24,7 +22,7 @@ import { collectBuildFilePattern, onExtensionChange } from "./plugin";
2422
import { pomCodeActionMetadata, PomCodeActionProvider } from "./pom/pomCodeActionProvider";
2523
import { ActionableNotification, BuildProjectParams, BuildProjectRequest, CompileWorkspaceRequest, CompileWorkspaceStatus, EventNotification, EventType, ExecuteClientCommandRequest, FeatureStatus, FindLinks, GradleCompatibilityInfo, LinkLocation, ProgressKind, ProgressNotification, ServerNotification, SourceAttachmentAttribute, SourceAttachmentRequest, SourceAttachmentResult, SourceInvalidatedEvent, StatusNotification, UpgradeGradleWrapperInfo } from "./protocol";
2624
import * as refactorAction from './refactorAction';
27-
import { getJdkUrl, RequirementsData, sortJdksBySource, sortJdksByVersion } from "./requirements";
25+
import { getJdkUrl, RequirementsData } from "./requirements";
2826
import { serverStatus, ServerStatusKind } from "./serverStatus";
2927
import { serverStatusBarProvider } from "./serverStatusBarProvider";
3028
import { activationProgressNotification, serverTaskPresenter } from "./serverTaskPresenter";
@@ -41,6 +39,7 @@ import { Telemetry } from "./telemetry";
4139
import { TelemetryEvent } from "@redhat-developer/vscode-redhat-telemetry/lib";
4240
import { registerDocumentValidationListener } from './diagnostic';
4341
import { registerSmartSemicolonDetection } from './smartSemicolonDetection';
42+
import { listJdks, sortJdksBySource, sortJdksByVersion } from './jdkUtils';
4443

4544
const extensionName = 'Language Support for Java';
4645
const GRADLE_CHECKSUM = "gradle/checksum/prompt";
@@ -91,7 +90,7 @@ export class StandardLanguageClient {
9190
if (!port) {
9291
const lsPort = process.env['JDTLS_CLIENT_PORT'];
9392
if (!lsPort) {
94-
serverOptions = prepareExecutable(requirements, workspacePath, getJavaConfig(requirements.java_home), context, false);
93+
serverOptions = prepareExecutable(requirements, workspacePath, context, false);
9594
} else {
9695
serverOptions = () => {
9796
const socket = net.connect(lsPort);
@@ -217,7 +216,7 @@ export class StandardLanguageClient {
217216
const options: string[] = [];
218217
const info = notification.data as GradleCompatibilityInfo;
219218
const highestJavaVersion = Number(info.highestJavaVersion);
220-
let runtimes = await findRuntimes({ checkJavac: true, withVersion: true, withTags: true });
219+
let runtimes = await listJdks(true);
221220
runtimes = runtimes.filter(runtime => {
222221
return runtime.version.major <= highestJavaVersion;
223222
});

src/syntaxLanguageClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class SyntaxLanguageClient {
2828
didChangeConfiguration: async () => {
2929
await this.languageClient.sendNotification(DidChangeConfigurationNotification.type, {
3030
settings: {
31-
java: getJavaConfig(requirements.java_home),
31+
java: await getJavaConfig(requirements.java_home),
3232
}
3333
});
3434
}

src/utils.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as fs from 'fs';
44
import * as path from 'path';
55
import { workspace, WorkspaceConfiguration, commands, Uri, version } from 'vscode';
66
import { Commands } from './commands';
7+
import { IJavaRuntime } from 'jdk-utils';
8+
import { getSupportedJreNames, listJdks, sortJdksBySource, sortJdksByVersion } from './jdkUtils';
79

810
export function getJavaConfiguration(): WorkspaceConfiguration {
911
return workspace.getConfiguration('java');
@@ -175,8 +177,9 @@ function getDirectoriesByBuildFile(inclusions: string[], exclusions: string[], f
175177
});
176178
}
177179

180+
const detectJdksAtStart: boolean = getJavaConfiguration().get<boolean>('configuration.detectJdksAtStart');
178181

179-
export function getJavaConfig(javaHome: string) {
182+
export async function getJavaConfig(javaHome: string) {
180183
const origConfig = getJavaConfiguration();
181184
const javaConfig = JSON.parse(JSON.stringify(origConfig));
182185
javaConfig.home = javaHome;
@@ -215,5 +218,49 @@ export function getJavaConfig(javaHome: string) {
215218
}
216219

217220
javaConfig.telemetry = { enabled: workspace.getConfiguration('redhat.telemetry').get('enabled', false) };
221+
if (detectJdksAtStart) {
222+
const userConfiguredJREs: any[] = javaConfig.configuration.runtimes;
223+
javaConfig.configuration.runtimes = await addAutoDetectedJdks(userConfiguredJREs);
224+
}
218225
return javaConfig;
219226
}
227+
228+
async function addAutoDetectedJdks(configuredJREs: any[]): Promise<any[]> {
229+
// search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories
230+
const autoDetectedJREs: IJavaRuntime[] = await listJdks();
231+
sortJdksByVersion(autoDetectedJREs);
232+
sortJdksBySource(autoDetectedJREs);
233+
const addedJreNames: Set<string> = new Set<string>();
234+
const supportedJreNames: string[] = getSupportedJreNames();
235+
for (const jre of configuredJREs) {
236+
if (jre.name) {
237+
addedJreNames.add(jre.name);
238+
}
239+
}
240+
for (const jre of autoDetectedJREs) {
241+
const majorVersion: number = jre.version?.major ?? 0;
242+
if (!majorVersion) {
243+
continue;
244+
}
245+
246+
let jreName: string = `JavaSE-${majorVersion}`;
247+
if (majorVersion <= 5) {
248+
jreName = `J2SE-1.${majorVersion}`;
249+
} else if (majorVersion <= 8) {
250+
jreName = `JavaSE-1.${majorVersion}`;
251+
}
252+
253+
if (addedJreNames.has(jreName) || !supportedJreNames?.includes(jreName)) {
254+
continue;
255+
}
256+
257+
configuredJREs.push({
258+
name: jreName,
259+
path: jre.homedir,
260+
});
261+
262+
addedJreNames.add(jreName);
263+
}
264+
265+
return configuredJREs;
266+
}

0 commit comments

Comments
 (0)