Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically register VMInstalls for the JDKs installed on the local machine #3301

Merged
merged 5 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
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