diff --git a/package-lock.json b/package-lock.json index 917845ba6..508257a0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4615,9 +4615,9 @@ "dev": true }, "jdk-utils": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jdk-utils/-/jdk-utils-0.4.4.tgz", - "integrity": "sha512-QSwVdPtwdHRvY6zDD0GOZo5Fu7AdgfNDyizZWhk16jnrcLTPbmsww4WLDC+5wyxCWm8izZZfmjTVcicdreuJLw==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jdk-utils/-/jdk-utils-0.5.0.tgz", + "integrity": "sha512-inZ7j5NSLVVlIKgg4NNt02v9CbtJk9I3ILalVpF+XMK9VjHb8VUx02Dozg6PXF+87tEIDoOs7ZQB6NuiR3lhUQ==" }, "jest-worker": { "version": "27.5.1", diff --git a/package.json b/package.json index a478f0c84..1a3e11e84 100644 --- a/package.json +++ b/package.json @@ -1545,7 +1545,7 @@ "fs-extra": "^8.1.0", "glob": "^7.1.3", "htmlparser2": "6.0.1", - "jdk-utils": "^0.4.4", + "jdk-utils": "^0.5.0", "react": "^17.0.2", "react-dom": "^17.0.2", "semver": "^7.5.2", diff --git a/src/extension.ts b/src/extension.ts index 1541fc3e4..d7b0ab348 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -155,7 +155,7 @@ export function activate(context: ExtensionContext): Promise { 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, @@ -184,7 +184,7 @@ export function activate(context: ExtensionContext): Promise { didChangeConfiguration: async () => { await standardClient.getClient().sendNotification(DidChangeConfigurationNotification.type, { settings: { - java: getJavaConfig(requirements.java_home), + java: await getJavaConfig(requirements.java_home), } }); } @@ -267,7 +267,7 @@ export function activate(context: ExtensionContext): Promise { // 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); diff --git a/src/javaServerStarter.ts b/src/javaServerStarter.ts index d6c35a283..656fe8ae6 100644 --- a/src/javaServerStarter.ts +++ b/src/javaServerStarter.ts @@ -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); @@ -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; } @@ -68,7 +68,7 @@ export function awaitServerConnection(port): Thenable { }); } -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; @@ -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()); diff --git a/src/jdkUtils.ts b/src/jdkUtils.ts new file mode 100644 index 000000000..309fbe0d7 --- /dev/null +++ b/src/jdkUtils.ts @@ -0,0 +1,57 @@ +'use strict'; + +import { IJavaRuntime, findRuntimes, getSources } from 'jdk-utils'; + +let cachedJdks: IJavaRuntime[]; + +export async function listJdks(force?: boolean): Promise { + if (force || !cachedJdks) { + cachedJdks = await findRuntimes({ checkJavac: true, withVersion: true, withTags: true }); + } + + 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; + 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)); +} diff --git a/src/requirements.ts b/src/requirements.ts index 1ca128b1e..b753f2200 100644 --- a/src/requirements.ts +++ b/src/requirements.ts @@ -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 */ @@ -70,7 +71,7 @@ export async function resolveRequirements(context: ExtensionContext): Promise { return undefined; } -export function sortJdksBySource(jdks: IJavaRuntime[]) { - const rankedJdks = jdks as Array; - 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; diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 97c44ba54..3a468160b 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -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"; @@ -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"; @@ -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"; @@ -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); @@ -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; }); diff --git a/src/syntaxLanguageClient.ts b/src/syntaxLanguageClient.ts index 96b417dec..59d815e48 100644 --- a/src/syntaxLanguageClient.ts +++ b/src/syntaxLanguageClient.ts @@ -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), } }); } diff --git a/src/utils.ts b/src/utils.ts index 897e5c07e..89bcd72dd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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 { listJdks, sortJdksBySource, sortJdksByVersion } from './jdkUtils'; export function getJavaConfiguration(): WorkspaceConfiguration { return workspace.getConfiguration('java'); @@ -176,7 +178,7 @@ function getDirectoriesByBuildFile(inclusions: string[], exclusions: string[], f } -export function getJavaConfig(javaHome: string) { +export async function getJavaConfig(javaHome: string) { const origConfig = getJavaConfiguration(); const javaConfig = JSON.parse(JSON.stringify(origConfig)); javaConfig.home = javaHome; @@ -215,5 +217,46 @@ export function getJavaConfig(javaHome: string) { } javaConfig.telemetry = { enabled: workspace.getConfiguration('redhat.telemetry').get('enabled', false) }; + const userConfiguredJREs: any[] = javaConfig.configuration.runtimes; + javaConfig.configuration.runtimes = await addAutoDetectedJdks(userConfiguredJREs); return javaConfig; } + +async function addAutoDetectedJdks(configuredJREs: any[]): Promise { + // 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 = new Set(); + 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)) { + continue; + } + + configuredJREs.push({ + name: jreName, + path: jre.homedir, + }); + + addedJreNames.add(jreName); + } + + return configuredJREs; +}