Skip to content

Commit

Permalink
Automatically register VMInstalls for the JDKs installed on the local…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
testforstephen committed Sep 18, 2023
1 parent a9b87bd commit 17edf20
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 41 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ The following settings are supported:
- `manual`: Manually reload the sources of the open class files
* `java.edit.smartSemicolonDetection.enabled`: Defines the `smart semicolon` detection. Defaults to `false`.

New in 1.23.0
* `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`.

Semantic Highlighting
===============
[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).
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,11 @@
"default": [],
"scope": "machine-overridable"
},
"java.configuration.detectJdksAtStart": {
"type": "boolean",
"default": true,
"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."
},
"java.server.launchMode": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -1529,7 +1534,7 @@
"fs-extra": "^8.1.0",
"glob": "^7.1.3",
"htmlparser2": "6.0.1",
"jdk-utils": "^0.4.4",
"jdk-utils": "^0.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"semver": "^7.5.2",
Expand Down
10 changes: 6 additions & 4 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { Telemetry } from './telemetry';
import { getMessage } from './errorUtils';
import { TelemetryService } from '@redhat-developer/vscode-redhat-telemetry/lib';
import { activationProgressNotification } from "./serverTaskPresenter";
import { loadSupportedJreNames } from './jdkUtils';

const syntaxClient: SyntaxLanguageClient = new SyntaxLanguageClient();
const standardClient: StandardLanguageClient = new StandardLanguageClient();
Expand Down Expand Up @@ -89,7 +90,8 @@ function getHeapDumpFolderFromSettings(): string {
}


export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
export async function activate(context: ExtensionContext): Promise<ExtensionAPI> {
await loadSupportedJreNames(context);
context.subscriptions.push(markdownPreviewProvider);
context.subscriptions.push(commands.registerCommand(Commands.TEMPLATE_VARIABLES, async () => {
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `${Commands.TEMPLATE_VARIABLES}.md`)), 'Predefined Variables', "", context);
Expand Down Expand Up @@ -163,7 +165,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
initializationOptions: {
bundles: collectJavaExtensions(extensions.all),
workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null,
settings: { java: getJavaConfig(requirements.java_home) },
settings: { java: await getJavaConfig(requirements.java_home) },
extendedClientCapabilities: {
classFileContentsSupport: true,
overrideMethodsPromptSupport: true,
Expand Down Expand Up @@ -192,7 +194,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
didChangeConfiguration: async () => {
await standardClient.getClient().sendNotification(DidChangeConfigurationNotification.type, {
settings: {
java: getJavaConfig(requirements.java_home),
java: await getJavaConfig(requirements.java_home),
}
});
}
Expand Down Expand Up @@ -275,7 +277,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
// the promise is resolved
// no need to pass `resolve` into any code past this point,
// since `resolve` is a no-op from now on
const serverOptions = prepareExecutable(requirements, syntaxServerWorkspacePath, getJavaConfig(requirements.java_home), context, true);
const serverOptions = prepareExecutable(requirements, syntaxServerWorkspacePath, context, true);
if (requireSyntaxServer) {
if (process.env['SYNTAXLS_CLIENT_PORT']) {
syntaxClient.initialize(requirements, clientOptions);
Expand Down
9 changes: 6 additions & 3 deletions src/javaServerStarter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const HEAP_DUMP = '-XX:+HeapDumpOnOutOfMemoryError';
const DEPENDENCY_COLLECTOR_IMPL= '-Daether.dependencyCollector.impl=';
const DEPENDENCY_COLLECTOR_IMPL_BF= 'bf';

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

function prepareParams(requirements: RequirementsData, javaConfiguration, workspacePath, context: ExtensionContext, isSyntaxServer: boolean): string[] {
function prepareParams(requirements: RequirementsData, workspacePath, context: ExtensionContext, isSyntaxServer: boolean): string[] {
const params: string[] = [];
if (DEBUG) {
const port = isSyntaxServer ? 1045 : 1044;
Expand Down Expand Up @@ -117,6 +117,9 @@ function prepareParams(requirements: RequirementsData, javaConfiguration, worksp
} else {
vmargs = '';
}
if (vmargs.indexOf('-DDetectVMInstallationsJob.disabled=') < 0) {
params.push('-DDetectVMInstallationsJob.disabled=true');
}
const encodingKey = '-Dfile.encoding=';
if (vmargs.indexOf(encodingKey) < 0) {
params.push(encodingKey + getJavaEncoding());
Expand Down
77 changes: 77 additions & 0 deletions src/jdkUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use strict';

import { existsSync } from 'fs';
import { IJavaRuntime, findRuntimes, getSources } from 'jdk-utils';
import { join } from 'path';
import { ExtensionContext, Uri, workspace } from 'vscode';

let cachedJdks: IJavaRuntime[];
let cachedJreNames: string[];

export async function loadSupportedJreNames(context: ExtensionContext): Promise<void> {
const buffer = await workspace.fs.readFile(Uri.file(context.asAbsolutePath("package.json")));
const packageJson = JSON.parse(buffer.toString());
cachedJreNames = packageJson?.contributes?.configuration?.properties?.["java.configuration.runtimes"]?.items?.properties?.name?.enum;
}

export function getSupportedJreNames(): string[] {
return cachedJreNames;
}

export async function listJdks(force?: boolean): Promise<IJavaRuntime[]> {
if (force || !cachedJdks) {
cachedJdks = await findRuntimes({ checkJavac: true, withVersion: true, withTags: true })
.then(jdks => jdks.filter(jdk => {
// Validate if it's a real Java Home.
return existsSync(join(jdk.homedir, "lib", "rt.jar"))
|| existsSync(join(jdk.homedir, "jre", "lib", "rt.jar")) // Java 8
|| existsSync(join(jdk.homedir, "lib", "jrt-fs.jar")); // Java 9+
}));
}

return [].concat(cachedJdks);
}

/**
* Sort by source where JDk is located.
* The order is:
* 1. JDK_HOME, JAVA_HOME, PATH
* 2. JDK manager such as SDKMAN, jEnv, jabba, asdf
* 3. Common places such as /usr/lib/jvm
* 4. Others
*/
export function sortJdksBySource(jdks: IJavaRuntime[]) {
const rankedJdks = jdks as Array<IJavaRuntime & { rank: number }>;
const env: string[] = ["JDK_HOME", "JAVA_HOME", "PATH"];
const jdkManagers: string[] = ["SDKMAN", "jEnv", "jabba", "asdf"];
for (const jdk of rankedJdks) {
const detectedSources: string[] = getSources(jdk);
for (const [index, source] of env.entries()) {
if (detectedSources.includes(source)) {
jdk.rank = index; // jdk from environment variables
break;
}
}

if (jdk.rank) {
continue;
}

const fromManager: boolean = detectedSources.some(source => jdkManagers.includes(source));
if (fromManager) {
jdk.rank = env.length + 1; // jdk from the jdk managers such as SDKMAN
} else if (!detectedSources.length){
jdk.rank = env.length + 2; // jdk from common places
} else {
jdk.rank = env.length + 3; // jdk from other source such as ~/.gradle/jdks
}
}
rankedJdks.sort((a, b) => a.rank - b.rank);
}

/**
* Sort by major version in descend order.
*/
export function sortJdksByVersion(jdks: IJavaRuntime[]) {
jdks.sort((a, b) => (b.version?.major ?? 0) - (a.version?.major ?? 0));
}
26 changes: 3 additions & 23 deletions src/requirements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

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

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

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

export function sortJdksBySource(jdks: IJavaRuntime[]) {
const rankedJdks = jdks as Array<IJavaRuntime & { rank: number }>;
const sources = ["JDK_HOME", "JAVA_HOME", "PATH"];
for (const [index, source] of sources.entries()) {
for (const jdk of rankedJdks) {
if (jdk.rank === undefined && getSources(jdk).includes(source)) {
jdk.rank = index;
}
}
}
rankedJdks.filter(jdk => jdk.rank === undefined).forEach(jdk => jdk.rank = sources.length);
rankedJdks.sort((a, b) => a.rank - b.rank);
}

/**
* Sort by major version in descend order.
*/
export function sortJdksByVersion(jdks: IJavaRuntime[]) {
jdks.sort((a, b) => (b.version?.major ?? 0) - (a.version?.major ?? 0));
}

export function parseMajorVersion(version: string): number {
if (!version) {
return 0;
Expand Down
9 changes: 4 additions & 5 deletions src/standardLanguageClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict';

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

const extensionName = 'Language Support for Java';
const GRADLE_CHECKSUM = "gradle/checksum/prompt";
Expand Down Expand Up @@ -91,7 +90,7 @@ export class StandardLanguageClient {
if (!port) {
const lsPort = process.env['JDTLS_CLIENT_PORT'];
if (!lsPort) {
serverOptions = prepareExecutable(requirements, workspacePath, getJavaConfig(requirements.java_home), context, false);
serverOptions = prepareExecutable(requirements, workspacePath, context, false);
} else {
serverOptions = () => {
const socket = net.connect(lsPort);
Expand Down Expand Up @@ -217,7 +216,7 @@ export class StandardLanguageClient {
const options: string[] = [];
const info = notification.data as GradleCompatibilityInfo;
const highestJavaVersion = Number(info.highestJavaVersion);
let runtimes = await findRuntimes({ checkJavac: true, withVersion: true, withTags: true });
let runtimes = await listJdks(true);
runtimes = runtimes.filter(runtime => {
return runtime.version.major <= highestJavaVersion;
});
Expand Down
2 changes: 1 addition & 1 deletion src/syntaxLanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class SyntaxLanguageClient {
didChangeConfiguration: async () => {
await this.languageClient.sendNotification(DidChangeConfigurationNotification.type, {
settings: {
java: getJavaConfig(requirements.java_home),
java: await getJavaConfig(requirements.java_home),
}
});
}
Expand Down
49 changes: 48 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as fs from 'fs';
import * as path from 'path';
import { workspace, WorkspaceConfiguration, commands, Uri, version } from 'vscode';
import { Commands } from './commands';
import { IJavaRuntime } from 'jdk-utils';
import { getSupportedJreNames, listJdks, sortJdksBySource, sortJdksByVersion } from './jdkUtils';

export function getJavaConfiguration(): WorkspaceConfiguration {
return workspace.getConfiguration('java');
Expand Down Expand Up @@ -175,8 +177,9 @@ function getDirectoriesByBuildFile(inclusions: string[], exclusions: string[], f
});
}

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

export function getJavaConfig(javaHome: string) {
export async function getJavaConfig(javaHome: string) {
const origConfig = getJavaConfiguration();
const javaConfig = JSON.parse(JSON.stringify(origConfig));
javaConfig.home = javaHome;
Expand Down Expand Up @@ -215,5 +218,49 @@ export function getJavaConfig(javaHome: string) {
}

javaConfig.telemetry = { enabled: workspace.getConfiguration('redhat.telemetry').get('enabled', false) };
if (detectJdksAtStart) {
const userConfiguredJREs: any[] = javaConfig.configuration.runtimes;
javaConfig.configuration.runtimes = await addAutoDetectedJdks(userConfiguredJREs);
}
return javaConfig;
}

async function addAutoDetectedJdks(configuredJREs: any[]): Promise<any[]> {
// search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories
const autoDetectedJREs: IJavaRuntime[] = await listJdks();
sortJdksByVersion(autoDetectedJREs);
sortJdksBySource(autoDetectedJREs);
const addedJreNames: Set<string> = new Set<string>();
const supportedJreNames: string[] = getSupportedJreNames();
for (const jre of configuredJREs) {
if (jre.name) {
addedJreNames.add(jre.name);
}
}
for (const jre of autoDetectedJREs) {
const majorVersion: number = jre.version?.major ?? 0;
if (!majorVersion) {
continue;
}

let jreName: string = `JavaSE-${majorVersion}`;
if (majorVersion <= 5) {
jreName = `J2SE-1.${majorVersion}`;
} else if (majorVersion <= 8) {
jreName = `JavaSE-1.${majorVersion}`;
}

if (addedJreNames.has(jreName) || !supportedJreNames?.includes(jreName)) {
continue;
}

configuredJREs.push({
name: jreName,
path: jre.homedir,
});

addedJreNames.add(jreName);
}

return configuredJREs;
}
Loading

0 comments on commit 17edf20

Please sign in to comment.