| /* |
| * 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; |
| } |