blob: 86f071a24daf8e84db458fa95e58baabc53f0cea [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as vscode from 'vscode';
import * as os from 'os';
import * as path from 'path';
import { MultiStepInput } from '../utils';
import * as settings from './settings';
import * as jdk from './jdk';
const ACTION_NAME = 'JDK Configuration';
export function initialize(context: vscode.ExtensionContext) {
const COMMAND_JDK_CONFIGURATION = 'nbls.jdk.configuration';
context.subscriptions.push(vscode.commands.registerCommand(COMMAND_JDK_CONFIGURATION, () => {
configure();
}));
}
type FeatureItem = { label: string; description: string; detail: string, setting: settings.Setting };
type JdkItem = { label: string; description: string; detail: string, jdk: jdk.Java };
type ScopeItem = { label: string; description: string; scope: vscode.ConfigurationTarget };
interface State {
allSettings: FeatureItem[];
selectedSettings: FeatureItem[];
allJdks: JdkItem[];
selectedJdk: JdkItem;
allScopes: ScopeItem[];
selectedScope: ScopeItem;
}
async function configure() {
const state: Partial<State> = {};
await MultiStepInput.run(input => selectFeatures(input, state));
if (state.selectedSettings && state.selectedJdk && state.selectedScope) {
const jdk = state.selectedJdk.jdk;
for (const selectedSetting of state.selectedSettings) {
await selectedSetting.setting.setJdk(jdk, state.selectedScope.scope);
}
const GRAALVM_EXTENSION_ID = 'oracle-labs-graalvm.graalvm';
const GRAALVM_SETTINGS_NAME = 'GraalVM Tools for Java';
if (vscode.extensions.getExtension(GRAALVM_EXTENSION_ID)) {
vscode.window.showWarningMessage(`The ${GRAALVM_SETTINGS_NAME} extension may conflict with the ${ACTION_NAME} action. Please consider uninstalling it.`, 'Got It');
}
}
}
function totalSteps(_state: Partial<State>): number {
return 3;
}
async function selectFeatures(input: MultiStepInput, state: Partial<State>) {
if (!state.allSettings) {
const settings = allSettings();
state.allSettings = settings[0];
state.selectedSettings = settings[1];
}
const selected: any = await input.showQuickPick({
title: `${ACTION_NAME}: Settings`,
step: 1,
totalSteps: totalSteps(state),
placeholder: 'Select features and settings for which JDK will be configured',
items: state.allSettings,
selectedItems: state.selectedSettings,
canSelectMany: true,
validate: () => Promise.resolve(undefined),
shouldResume: () => Promise.resolve(false)
});
if (selected?.length) {
state.selectedSettings = selected;
return (input: MultiStepInput) => selectJDK(input, state);
}
return;
}
function allSettings(): FeatureItem[][] {
const allSettings = [];
const selectedSettings = [];
const availableSettings = settings.getAvailable();
for (const setting of availableSettings) {
const current = setting.getCurrent();
const item = { label: setting.name, description: setting.getSetting(), detail: `$(coffee) Current: ${current || 'not defined'}`, setting: setting };
allSettings.push(item);
if (current || setting.configureIfNotDefined()) {
selectedSettings.push(item);
}
}
return [ allSettings, selectedSettings ];
}
async function checkJDKCompatibility(check? : jdk.Java, features? : FeatureItem[]) : Promise<FeatureItem | null> {
if (!features) {
return null;
}
if (check) {
for (let setting of features) {
if (!await setting.setting.acceptJdk(check)) {
return setting;
}
}
}
return null;
}
async function selectJDK(input: MultiStepInput, state: Partial<State>): Promise<any | undefined> {
if (!state.allJdks) {
state.allJdks = await allJdks(state);
}
let filteredJDKs : JdkItem[] = [];
let someLeftOut : boolean = false;
if (state.selectedSettings) {
for (let jdk of state.allJdks) {
if (await checkJDKCompatibility(jdk.jdk, state.selectedSettings)) {
// non-null feature item -> incompatible
someLeftOut = true;
} else {
filteredJDKs.push(jdk);
}
}
} else {
filteredJDKs = [...state.allJdks];
}
const selectCustom = { label: '$(folder-opened) Select Custom JDK...' };
const selected: any = await input.showQuickPick({
title: `${ACTION_NAME}: JDK`,
step: 2,
totalSteps: totalSteps(state),
placeholder: someLeftOut ? 'Select JDK (filtering compatible JDKs)' : 'Select JDK',
items: [ selectCustom, ...filteredJDKs ],
validate: () => Promise.resolve(undefined),
shouldResume: () => Promise.resolve(false)
});
if (selected?.length) {
if (selected[0] === selectCustom) {
const javaHome = state.allJdks?.[1]?.jdk?.javaHome;
const javaRoot = javaHome ? path.dirname(javaHome) : undefined;
const customJdk = await selectCustomJdk(javaRoot);
if (customJdk) {
await jdk.registerCustom(customJdk);
state.allJdks = await allJdks(state);
let selected : boolean = false;
for (const jdkItem of state.allJdks) {
if (jdkItem.jdk?.javaHome === customJdk.javaHome) {
state.selectedJdk = jdkItem;
selected = true;
break;
}
}
if (!selected) {
// if for some reason, the custom JDK cannot be found among possible selections,
// reject it
await vscode.window.showErrorMessage(`The selected JDK ${javaRoot} could not be used. Please choose another one`, 'Retry');
return (input: MultiStepInput) => selectJDK(input, state);
}
} else {
return (input: MultiStepInput) => selectJDK(input, state);
}
} else if (selected[0].jdk) {
state.selectedJdk = selected[0];
}
}
const f = await checkJDKCompatibility(state.selectedJdk?.jdk, state.selectedSettings);
if (f) {
const Y = 'Proceed Anyway';
const N = 'Choose a Different JDK';
const q = await vscode.window.showWarningMessage(`The selected JDK (${state.selectedJdk?.jdk.name()}) is not compatible with feature ${f.label}. Do you want to proceed ?`, N, Y);
if (q !== Y) {
state.selectedJdk = undefined;
return (input: MultiStepInput) => selectJDK(input, state);
}
}
return (input: MultiStepInput) => selectScope(input, state);
}
async function allJdks(state: Partial<State>): Promise<JdkItem[]> {
const knownJavas: jdk.Java[] = [];
for (const setting of state.allSettings || []) {
knownJavas.push(...setting.setting.getJavas());
}
const jdks = await jdk.findAll(knownJavas);
jdks.sort((jdk1, jdk2) => jdk1 > jdk2 ? -1 : 1);
const jdkItems = [];
const jdkItemsNi: JdkItem | { jdk?: jdk.Java, kind?: vscode.QuickPickItemKind }[] = [];
const jdkItemsWoNi: JdkItem | { jdk?: jdk.Java, kind?: vscode.QuickPickItemKind }[] = [];
for (const jdk of jdks) {
const jdkItem = { label: `$(coffee) ${jdk.name()}`, description: `${jdk.javaHome}`, jdk: jdk, kind: vscode.QuickPickItemKind.Default };
if (jdk.hasNativeImage()) {
jdkItemsNi.push(jdkItem);
} else {
jdkItemsWoNi.push(jdkItem);
}
}
if (jdkItemsNi.length) {
jdkItems.push({ label: 'With Native Image', kind: vscode.QuickPickItemKind.Separator });
jdkItems.push(...jdkItemsNi);
}
if (jdkItemsWoNi.length) {
jdkItems.push({ label: 'Without Native Image', kind: vscode.QuickPickItemKind.Separator });
jdkItems.push(...jdkItemsWoNi);
}
return jdkItems as JdkItem[];
}
async function selectCustomJdk(javaRoot?: string): Promise<jdk.Java | null | undefined> {
const selected = await vscode.window.showOpenDialog({
title: 'Select Custom JDK',
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
defaultUri: vscode.Uri.file(javaRoot || os.homedir()),
openLabel: 'Select'
});
if (selected?.length === 1) {
const selectedJavaHome = selected[0].fsPath;
const selectedJdk = new jdk.Java(selectedJavaHome);
if (selectedJdk.isJdk()) {
return selectedJdk;
} else {
vscode.window.showWarningMessage(`Not a valid JDK installation: ${selectedJavaHome}`);
return null;
}
}
return undefined;
}
async function selectScope(input: MultiStepInput, state: Partial<State>) {
if (!state.allScopes) {
state.allScopes = allScopes();
}
const selected: any = await input.showQuickPick({
title: `${ACTION_NAME}: Scope`,
step: 3,
totalSteps: totalSteps(state),
placeholder: 'Select configuration scope',
items: state.allScopes,
validate: () => Promise.resolve(undefined),
shouldResume: () => Promise.resolve(false)
});
if (selected?.length && selected[0].scope) {
state.selectedScope = selected[0];
}
}
function allScopes(): ScopeItem[] {
const allScopes = [];
allScopes.push({ label: 'User', description: 'JDK will be configured for all workspaces, may be overriden by Workspace configuration', scope: vscode.ConfigurationTarget.Global });
if (vscode.workspace.workspaceFolders) {
allScopes.push({ label: 'Workspace', description: 'JDK will be configured for the current workspace, overrides User configuration', scope: vscode.ConfigurationTarget.Workspace });
}
return allScopes;
}