From fd018598f37169cf7efe0a45145d368bc4f212a3 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 8 Jun 2017 18:38:07 +0300 Subject: [PATCH] Enable users to specify toolchain to use (#267) --- doc/common_configuration_parameters.md | 12 + doc/main.md | 2 + package.json | 14 + src/Toolchain.ts | 62 +++++ .../completion/completion_manager.ts | 37 ++- src/components/configuration/Configuration.ts | 22 +- src/components/configuration/Rustup.ts | 258 ++++++++++++++---- src/extension.ts | 72 ++++- 8 files changed, 388 insertions(+), 91 deletions(-) create mode 100644 doc/common_configuration_parameters.md create mode 100644 src/Toolchain.ts diff --git a/doc/common_configuration_parameters.md b/doc/common_configuration_parameters.md new file mode 100644 index 0000000..1b99259 --- /dev/null +++ b/doc/common_configuration_parameters.md @@ -0,0 +1,12 @@ +# Common Configuration Parameters + +## rustup + +Users should adjust properties of this configuration parameter to customize rustup. + +### toolchain + +This configuration parameter specifies which toolchain the extension will invoke rustup with. +It is used for getting sysroot, installing components. + +However there are few exceptions. Currently RLS is available for nightly hence RLS and rust-analysis are installed for the nightly toolchain. diff --git a/doc/main.md b/doc/main.md index ebfa800..2136123 100644 --- a/doc/main.md +++ b/doc/main.md @@ -21,6 +21,8 @@ The chosen mode is stored in `"rust.mode"` and it can be changed by users. Each mode is described in detail on its own page. +Some configuration parameters effect both modes. They are described [there](common_configuration_parameters.md). + Furthermore, the extension provides: * [Linting (the showing of diagnostics in the active text editor)](linting.md) diff --git a/package.json b/package.json index 2e6c49a..3d77043 100644 --- a/package.json +++ b/package.json @@ -466,6 +466,20 @@ }, "type": "array" }, + "rust.rustup": { + "default": null, + "description": "rustup configuration", + "properties": { + "toolchain": { + "default": null, + "description": "The toolchain to use for installing components (rust-src), except RLS", + "type": [ + "string", + "null" + ] + } + } + }, "rust.rls": { "default": null, "description": "Rust Language Server configuration", diff --git a/src/Toolchain.ts b/src/Toolchain.ts new file mode 100644 index 0000000..1d2b624 --- /dev/null +++ b/src/Toolchain.ts @@ -0,0 +1,62 @@ +export class Toolchain { + public static readonly defaultToolchainPrefix: string = ' (default)'; + + /** + * "stable" in "stable-x86_64-pc-windows-msvc (default)" + */ + public readonly channel: string; + + /** + * "x86_64-pc-windows-msvc" in "stable-x86_64-pc-windows-msvc (default)" + * `undefined` in "stable" + */ + public readonly host: string | undefined; + + /** + * true in "stable-x86_64-pc-windows-msvc (default)". + * false in "stable-x86_64-pc-windows-msvc" + */ + public readonly isDefault: boolean; + + /** + * Tries to parse the text and if returns the toolchain parsed from the text + * @param text The text to parse + * @return the toolchain or undefined + */ + public static parse(text: string): Toolchain | undefined { + const sepIndex = text.indexOf('-'); + const channelEnd = sepIndex === -1 ? undefined : sepIndex; + const channel = text.substring(0, channelEnd); + if (channelEnd === undefined) { + // The text represents the toolchain with the only channel. + return new Toolchain(channel, undefined, false); + } + const spaceIndex = text.indexOf(' ', sepIndex); + const hostEnd = spaceIndex === -1 ? undefined : spaceIndex; + const host = text.substring(sepIndex + 1, hostEnd); + const isDefault = text.endsWith(Toolchain.defaultToolchainPrefix); + return new Toolchain(channel, host, isDefault); + } + + public equals(toolchain: Toolchain): boolean { + return this.channel === toolchain.channel && this.host === toolchain.host; + } + + public toString(includeHost: boolean, includeIsDefault: boolean): string { + let s = this.channel.concat(); + if (includeHost && this.host) { + s += '-'; + s += this.host; + } + if (includeIsDefault && this.isDefault) { + s += Toolchain.defaultToolchainPrefix; + } + return s; + } + + private constructor(channel: string, host: string | undefined, isDefault: boolean) { + this.channel = channel; + this.host = host; + this.isDefault = isDefault; + } +} diff --git a/src/components/completion/completion_manager.ts b/src/components/completion/completion_manager.ts index 46f697e..4b8dd94 100644 --- a/src/components/completion/completion_manager.ts +++ b/src/components/completion/completion_manager.ts @@ -91,7 +91,7 @@ export class CompletionManager { logger.debug('racer is not installed'); return; } - const isSourceCodeAvailable: boolean = this.ensureSourceCodeIsAvailable(); + const isSourceCodeAvailable: boolean = await this.ensureSourceCodeIsAvailable(); if (!isSourceCodeAvailable) { logger.debug('Rust\'s source is not installed'); return; @@ -123,22 +123,33 @@ export class CompletionManager { * Ensures that Rust's source code is available to use * @returns flag indicating whether the source code if available or not */ - private ensureSourceCodeIsAvailable(): boolean { + private async ensureSourceCodeIsAvailable(): Promise { + const logger = this.logger.createChildLogger('ensureSourceCodeIsAvailable: '); if (this._rustSource.getPath()) { + logger.debug('sources is available'); return true; } - if (this._rustup) { - // tslint:disable-next-line - const message = 'You are using rustup, but don\'t have installed source code. Do you want to install it?'; - window.showErrorMessage(message, 'Yes').then(chosenItem => { - if (chosenItem === 'Yes') { - const terminal = window.createTerminal('Rust source code installation'); - terminal.sendText('rustup component add rust-src'); - terminal.show(); - } - }); + if (!this._rustup) { + logger.error('rustup is undefined'); + return false; + } + // tslint:disable-next-line + const message = 'You are using rustup, but don\'t have installed source code. Do you want to install it?'; + const choice = await window.showErrorMessage(message, 'Yes'); + if (choice === 'Yes') { + logger.debug('the user agreed to install rust-src'); + const rustSrcInstalled = await this._rustup.installRustSrc(); + if (rustSrcInstalled) { + logger.debug('rust-src has been installed'); + return true; + } else { + logger.error('rust-src has not been installed'); + return false; + } + } else { + logger.debug('the user dismissed the dialog'); + return false; } - return false; } /** diff --git a/src/components/configuration/Configuration.ts b/src/components/configuration/Configuration.ts index 21b0932..2aad9d4 100644 --- a/src/components/configuration/Configuration.ts +++ b/src/components/configuration/Configuration.ts @@ -59,7 +59,7 @@ export class Configuration { } public static getPathConfigParameter(parameterName: string): string | undefined { - const parameter = this.getStringParameter(parameterName); + const parameter = this.getStringConfigParameter(parameterName); if (parameter) { return expandTilde(parameter); } else { @@ -76,6 +76,12 @@ export class Configuration { } } + public static getStringConfigParameter(parameterName: string): string | undefined { + const configuration = workspace.getConfiguration('rust'); + const parameter = configuration.get(parameterName); + return parameter; + } + /** * Creates a new instance of the class. * @param logger A value for the field `logger` @@ -170,10 +176,8 @@ export class Configuration { return shouldExecuteCargoCommandInTerminal; } - public getActionOnSave(): string | null { - const actionOnSave = Configuration.getStringParameter('actionOnSave'); - - return actionOnSave; + public getActionOnSave(): string | undefined { + return Configuration.getStringConfigParameter('actionOnSave'); } public shouldShowRunningCargoTaskOutputChannel(): boolean { @@ -240,12 +244,4 @@ export class Configuration { return ActionOnStartingCommandIfThereIsRunningCommand.IgnoreNewCommand; } } - - private static getStringParameter(parameterName: string): string | null { - const configuration = workspace.getConfiguration('rust'); - - const parameter: string | null = configuration[parameterName]; - - return parameter; - } } diff --git a/src/components/configuration/Rustup.ts b/src/components/configuration/Rustup.ts index 6da7ab5..82f0cc4 100644 --- a/src/components/configuration/Rustup.ts +++ b/src/components/configuration/Rustup.ts @@ -1,12 +1,10 @@ import { join } from 'path'; import { OutputtingProcess } from '../../OutputtingProcess'; +import { Toolchain } from '../../Toolchain'; import { FileSystem } from '../file_system/FileSystem'; import { ChildLogger } from '../logging/child_logger'; import * as OutputChannelProcess from '../../OutputChannelProcess'; - -namespace Constants { - export const DEFAULT_TOOLCHAIN_SUFFIX = '(default)'; -} +import { Configuration } from './Configuration'; /** * Configuration of Rust installed via Rustup @@ -38,12 +36,17 @@ export class Rustup { /** * Components received by invoking rustup */ - private components: string[]; + private components: { [toolchain: string]: string[] }; /** * Toolchains received by invoking rustup */ - private toolchains: string[]; + private toolchains: Toolchain[]; + + /** + * The toolchain chosen by the user + */ + private _userToolchain: Toolchain | undefined; /** * Creates a new instance of the class. @@ -56,38 +59,56 @@ export class Rustup { return undefined; } const rustup = new Rustup(logger); - await rustup.updateToolchains(); - await rustup.updateComponents(); - await rustup.updateSysrootPath('nightly'); - await rustup.updatePathToRustSourceCodePath(); - await rustup.updatePathToRlsExecutable(); return rustup; } /** - * Returns whether the nightly toolchain is installed or not - */ - public isNightlyToolchainInstalled(): boolean { - const nightlyToolchain = this.toolchains.find(t => t.startsWith('nightly')); - if (nightlyToolchain) { - return true; - } else { - return false; + * Return either the only nightly toolchain or undefined if there is no nightly toolchain or + * there are several nightly toolchains + */ + public getNightlyToolchain(logger: ChildLogger): Toolchain | undefined { + const functionLogger = logger.createChildLogger('getNightlyToolchain: '); + const nightlyToolchains = this.getNightlyToolchains(); + switch (nightlyToolchains.length) { + case 0: + functionLogger.error('There is no nightly toolchain'); + return undefined; + case 1: + functionLogger.debug('There is only one nightly toolchain'); + return nightlyToolchains[0]; + default: + functionLogger.debug(`There are ${nightlyToolchains.length} nightly toolchains`); + return undefined; } } /** * Returns either the default toolchain or undefined if there are no installed toolchains */ - public getDefaultToolchain(): string | undefined { + public getDefaultToolchain(): Toolchain | undefined { const logger = this.logger.createChildLogger('getDefaultToolchain: '); - const toolchain = this.toolchains.find(t => t.endsWith(Constants.DEFAULT_TOOLCHAIN_SUFFIX)); + const toolchain = this.toolchains.find(t => t.isDefault); if (!toolchain && this.toolchains.length !== 0) { logger.error(`no default toolchain; this.toolchains=${this.toolchains}`); } return toolchain; } + /** + * Returns the toolchains received from the last rustup invocation + */ + public getToolchains(): Toolchain[] { + return this.toolchains; + } + + /** + * Checks if the toolchain is installed + * @param toolchain The toolchain to check + */ + public isToolchainInstalled(toolchain: Toolchain): boolean { + return this.toolchains.find(t => t.equals(toolchain)) !== undefined; + } + /** * Returns the path to Rust's source code */ @@ -102,6 +123,21 @@ export class Rustup { return this.pathToRlsExecutable; } + /** + * Returns either the toolchain chosen by the user or undefined + */ + public getUserToolchain(): Toolchain | undefined { + return this._userToolchain; + } + + public setUserToolchain(toolchain: Toolchain | undefined): void { + if (this._userToolchain === toolchain) { + return; + } + this._userToolchain = toolchain; + updateUserConfigurationParameter(c => c.toolchain = toolchain ? toolchain.toString(true, false) : null); + } + /** * Requests rustup to install the specified toolchain * @param toolchain The toolchain to install @@ -125,13 +161,34 @@ export class Rustup { return true; } + /** + * Requests Rustup to install rust-src for the chosen toolchain + * @return true if the installing succeeded, otherwise false + */ + public async installRustSrc(): Promise { + const logger = this.logger.createChildLogger('installRustSrc: '); + if (!this._userToolchain) { + logger.error('no toolchain has been chosen'); + return false; + } + return await this.installComponent(this._userToolchain, 'rust-src'); + } + /** * Requests Rustup install RLS * @return true if no error occurred and RLS has been installed otherwise false */ public async installRls(): Promise { const logger = this.logger.createChildLogger('installRls: '); - const isComponentInstalled: boolean = await this.installComponent(Rustup.getRlsComponentName()); + const nightlyToolchain = this.getNightlyToolchain(logger); + if (!nightlyToolchain) { + logger.error('no nightly toolchain'); + return false; + } + const isComponentInstalled: boolean = await this.installComponent( + nightlyToolchain, + Rustup.getRlsComponentName() + ); if (!isComponentInstalled) { return false; } @@ -150,26 +207,33 @@ export class Rustup { * @return true if no error occurred and rust-analysis has been installed otherwise false */ public async installRustAnalysis(): Promise { - return await this.installComponent(Rustup.getRustAnalysisComponentName()); + const logger = this.logger.createChildLogger('installRustAnalysis: '); + const nightlyToolchain = this.getNightlyToolchain(logger); + if (!nightlyToolchain) { + logger.error('no nightly toolchain'); + return false; + } + return await this.installComponent( + nightlyToolchain, + Rustup.getRustAnalysisComponentName() + ); } /** * Requests rustup to give components list and saves them in the field `components` */ - public async updateComponents(): Promise { - this.components = []; - const logger = this.logger.createChildLogger('updateComponents: '); - if (!this.isNightlyToolchainInstalled()) { - logger.error('nightly toolchain is not installed'); - return; - } - const stdoutData: string | undefined = await Rustup.invoke(['component', 'list', '--toolchain', 'nightly'], logger); + public async updateComponents(toolchain: Toolchain): Promise { + const logger = this.logger.createChildLogger(`updateComponents(${toolchain.toString(true, false)}): `); + const toolchainAsString = toolchain.toString(true, false); + this.components[toolchainAsString] = []; + const rustupArgs = ['component', 'list', '--toolchain', toolchainAsString]; + const stdoutData: string | undefined = await Rustup.invoke(rustupArgs, logger); if (!stdoutData) { logger.error(`stdoutData=${stdoutData}`); return; } - this.components = stdoutData.split('\n'); - logger.debug(`this.components=${JSON.stringify(this.components)}`); + this.components[toolchainAsString] = stdoutData.split('\n'); + logger.debug(`components=${JSON.stringify(this.components[toolchainAsString])}`); } /** @@ -185,10 +249,10 @@ export class Rustup { * Requests rustup to give the path to the sysroot of the specified toolchain * @param toolchain The toolchain to get the path to the sysroot for */ - public async updateSysrootPath(toolchain: string): Promise { + public async updateSysrootPath(toolchain: Toolchain): Promise { this.pathToRustcSysRoot = undefined; const logger = this.logger.createChildLogger(`updateSysrootPath: toolchain=${toolchain}: `); - if (!this.toolchains.find(t => t.startsWith(toolchain))) { + if (!this.toolchains.find(t => t.equals(toolchain))) { logger.error('toolchain is not installed'); return; } @@ -227,11 +291,6 @@ export class Rustup { public async updatePathToRlsExecutable(): Promise { const logger = this.logger.createChildLogger('updatePathToRlsExecutable: '); this.pathToRlsExecutable = undefined; - const rlsInstalled: boolean = this.isComponentInstalled(Rustup.getRlsComponentName()); - logger.debug(`rlsInstalled=${rlsInstalled}`); - if (!rlsInstalled) { - return; - } const rlsPath: string | undefined = await FileSystem.findExecutablePath('rls'); logger.debug(`rlsPath=${rlsPath}`); if (!rlsPath) { @@ -248,7 +307,13 @@ export class Rustup { */ public canInstallRls(): boolean { const logger = this.logger.createChildLogger('canInstallRls: '); - const rlsComponent = this.components.find(component => component.startsWith(Rustup.getRlsComponentName())); + const nightlyToolchain = this.getNightlyToolchain(logger); + if (!nightlyToolchain) { + logger.error('no nightly toolchain'); + return false; + } + const components = this.components[nightlyToolchain.toString(true, false)]; + const rlsComponent = components.find(component => component.startsWith(Rustup.getRlsComponentName())); if (!rlsComponent) { return false; } @@ -265,7 +330,13 @@ export class Rustup { * @return true if RLS is installed otherwise false */ public isRlsInstalled(): boolean { - return this.isComponentInstalled(Rustup.getRlsComponentName()); + const logger = this.logger.createChildLogger('isRlsInstalled: '); + const nightlyToolchain = this.getNightlyToolchain(logger); + if (!nightlyToolchain) { + logger.error('no nightly toolchain'); + return false; + } + return this.isComponentInstalled(nightlyToolchain, Rustup.getRlsComponentName()); } /** @@ -273,7 +344,13 @@ export class Rustup { * @return The flag indicating whether "rust-analysis" is installed */ public isRustAnalysisInstalled(): boolean { - return this.isComponentInstalled(Rustup.getRustAnalysisComponentName()); + const logger = this.logger.createChildLogger('isRustAnalysisInstalled: '); + const nightlyToolchain = this.getNightlyToolchain(logger); + if (!nightlyToolchain) { + logger.error('no nightly toolchain'); + return false; + } + return this.isComponentInstalled(nightlyToolchain, Rustup.getRustAnalysisComponentName()); } /** @@ -281,7 +358,14 @@ export class Rustup { * If the component is already installed, the method returns false */ public canInstallRustAnalysis(): boolean { - const component: string | undefined = this.components.find(c => c.startsWith(Rustup.getRustAnalysisComponentName())); + const logger = this.logger.createChildLogger('canInstallRustAnalysis: '); + const nightlyToolchain = this.getNightlyToolchain(logger); + if (!nightlyToolchain) { + logger.error('no nightly toolchain'); + return false; + } + const components = this.components[nightlyToolchain.toString(true, false)]; + const component: string | undefined = components.find(c => c.startsWith(Rustup.getRustAnalysisComponentName())); if (!component) { return false; } @@ -324,8 +408,11 @@ export class Rustup { * @param logger The logger to log messages * @return The output of the invocation if the invocation exited successfully otherwise undefined */ - private static async invokeGettingSysrootPath(toolchain: string, logger: ChildLogger): Promise { - const args = ['run', toolchain, 'rustc', '--print', 'sysroot']; + private static async invokeGettingSysrootPath( + toolchain: Toolchain, + logger: ChildLogger + ): Promise { + const args = ['run', toolchain.toString(true, false), 'rustc', '--print', 'sysroot']; const output: string | undefined = await this.invoke(args, logger); if (!output) { return undefined; @@ -333,14 +420,22 @@ export class Rustup { return output.trim(); } - private static async invokeGettingToolchains(logger: ChildLogger): Promise { + private static async invokeGettingToolchains(logger: ChildLogger): Promise { const functionLogger = logger.createChildLogger('invokeGettingToolchains: '); const output = await this.invoke(['toolchain', 'list'], functionLogger); if (!output) { functionLogger.error(`output=${output}`); return []; } - return output.trim().split('\n'); + const toolchainsAsStrings = output.trim().split('\n'); + const toolchains = []; + for (const toolchainAsString of toolchainsAsStrings) { + const toolchain = Toolchain.parse(toolchainAsString); + if (toolchain) { + toolchains.push(toolchain); + } + } + return toolchains; } /** @@ -405,16 +500,23 @@ export class Rustup { this.pathToRustcSysRoot = undefined; this.pathToRustSourceCode = undefined; this.pathToRlsExecutable = undefined; - this.components = []; + this.components = {}; this.toolchains = []; + this._userToolchain = getUserToolchain(); + } + + private getNightlyToolchains(): Toolchain[] { + return this.toolchains.filter(t => t.channel === 'nightly'); } /** * Takes from the field `components` only installed components * @returns a list of installed components */ - private getInstalledComponents(): string[] { - const installedComponents = this.components.filter(component => { + private getInstalledComponents(toolchain: Toolchain): string[] { + const toolchainAsString = toolchain.toString(true, false); + const components = this.components[toolchainAsString]; + const installedComponents = components.filter(component => { return component.endsWith(Rustup.getSuffixForInstalledComponent()); }); return installedComponents; @@ -424,32 +526,70 @@ export class Rustup { * Returns true if the component is installed otherwise false * @param componentName The component's name */ - private isComponentInstalled(componentName: string): boolean { - const installedComponents: string[] = this.getInstalledComponents(); + private isComponentInstalled(toolchain: Toolchain, componentName: string): boolean { + const installedComponents: string[] = this.getInstalledComponents(toolchain); const component: string | undefined = installedComponents.find(c => c.startsWith(componentName)); const isComponentInstalled = component !== undefined; return isComponentInstalled; } - private async installComponent(componentName: string): Promise { - const logger = this.logger.createChildLogger(`installComponent(${componentName}: `); - if (this.isComponentInstalled(componentName)) { + private async installComponent(toolchain: Toolchain, componentName: string): Promise { + const logger = this.logger.createChildLogger(`installComponent(${toolchain}, ${componentName}: `); + if (this.isComponentInstalled(toolchain, componentName)) { logger.error(`${componentName} is already installed. The method should not have been called`); // We return true because the component is installed, but anyway it is an exceptional situation return true; } - const args = ['component', 'add', componentName, '--toolchain', 'nightly']; + const args = ['component', 'add', componentName, '--toolchain', toolchain.toString(true, false)]; const stdoutData = await Rustup.invokeWithOutputChannel(args, logger, `Rustup: Installing ${componentName}`); if (stdoutData === undefined) { // Some error occurred. It is already logged // So we just need to notify a caller that the installation failed return false; } - await this.updateComponents(); - if (!this.isComponentInstalled(componentName)) { + await this.updateComponents(toolchain); + if (!this.isComponentInstalled(toolchain, componentName)) { logger.error(`${componentName} had been installed successfully, but then Rustup reported that the component was not installed. This should have not happened`); return false; } return true; } } + +function getUserConfiguration(): any { + const configuration = Configuration.getConfiguration(); + if (!configuration) { + return undefined; + } + const rustupConfiguration = configuration.get('rustup'); + if (!rustupConfiguration) { + return undefined; + } + return rustupConfiguration; +} + +function getUserToolchain(): Toolchain | undefined { + const rustupConfiguration = getUserConfiguration(); + if (!rustupConfiguration) { + return undefined; + } + const toolchainAsString = rustupConfiguration.toolchain; + if (!toolchainAsString) { + return undefined; + } + const toolchain = Toolchain.parse(toolchainAsString); + if (toolchain) { + return toolchain; + } else { + return undefined; + } +} + +function updateUserConfigurationParameter(updateParameter: (c: any) => void): void { + let configuration = getUserConfiguration(); + if (!configuration) { + configuration = {}; + } + updateParameter(configuration); + Configuration.getConfiguration().update('rustup', configuration, true); +} diff --git a/src/extension.ts b/src/extension.ts index 28651fd..2a0cf40 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,6 @@ // https://github.com/pwnall/node-open import open = require('open'); -import { ExtensionContext, window, workspace } from 'vscode'; +import { ExtensionContext, QuickPickItem, window, workspace } from 'vscode'; import { CargoManager, CommandInvocationReason } from './components/cargo/cargo_manager'; import { Configuration, Mode } from './components/configuration/Configuration'; import { CurrentWorkingDirectoryManager } @@ -15,6 +15,7 @@ import { ChildLogger } from './components/logging/child_logger'; import { RootLogger } from './components/logging/root_logger'; import { LegacyModeManager } from './legacy_mode_manager'; import * as OutputChannelProcess from './OutputChannelProcess'; +import { Toolchain } from './Toolchain'; /** * Asks the user to choose a mode which the extension will run in. @@ -65,17 +66,17 @@ async function handleMissingNightlyToolchain(logger: ChildLogger, rustup: Rustup const functionLogger = logger.createChildLogger('handleMissingNightlyToolchain: '); await window.showInformationMessage('The nightly toolchain is not installed, but is required to install RLS'); const permissionGranted = await askPermissionToInstall('the nightly toolchain'); - functionLogger.debug(`permissionGranted= ${permissionGranted}`); + functionLogger.debug(`permissionGranted=${permissionGranted}`); if (!permissionGranted) { return false; } window.showInformationMessage('The nightly toolchain is being installed. It can take a while. Please be patient'); const toolchainInstalled = await rustup.installToolchain('nightly'); - functionLogger.debug(`toolchainInstalled= ${toolchainInstalled}`); + functionLogger.debug(`toolchainInstalled=${toolchainInstalled}`); if (!toolchainInstalled) { return false; } - await rustup.updateComponents(); + await rustup.updateComponents(Toolchain.parse('nightly')); return true; } @@ -293,10 +294,10 @@ class RlsMode { logger.debug('Permission to install RLS has not granted'); return false; } - if (!this._rustup.isNightlyToolchainInstalled()) { + if (!this._rustup.getNightlyToolchain(logger)) { logger.debug('The nightly toolchain is not installed'); await handleMissingNightlyToolchain(logger, rustup); - if (!rustup.isNightlyToolchainInstalled()) { + if (!rustup.getNightlyToolchain(logger)) { logger.debug('The nightly toolchain is not installed'); return false; } @@ -332,10 +333,69 @@ class RlsMode { } } +async function handleMissingRustupUserToolchain( + logger: RootLogger, + rustup: Rustup +): Promise { + class Item implements QuickPickItem { + public toolchain: Toolchain; + public label: string; + public description: string; + + public constructor(toolchain: Toolchain, shouldLabelContainHost: boolean) { + this.toolchain = toolchain; + this.label = toolchain.toString(shouldLabelContainHost, true); + this.description = ''; + } + } + const functionLogger = logger.createChildLogger('handleMissingRustupUserToolchain: '); + functionLogger.debug('enter'); + await window.showInformationMessage('To properly function, the extension needs to know what toolchain you want to use'); + const toolchains = rustup.getToolchains(); + if (toolchains.length === 0) { + functionLogger.error('no toolchains'); + return; + } + const toolchainsHaveOneHost = toolchains.every(t => t.host === toolchains[0].host); + const items = toolchains.map(t => new Item(t, !toolchainsHaveOneHost)); + const item = await window.showQuickPick(items); + if (!item) { + return; + } + rustup.setUserToolchain(item.toolchain); +} + export async function activate(ctx: ExtensionContext): Promise { const loggingManager = new LoggingManager(); const logger = loggingManager.getLogger(); const rustup = await Rustup.create(logger.createChildLogger('Rustup: ')); + if (rustup) { + await rustup.updateToolchains(); + { + const userToolchain = rustup.getUserToolchain(); + if (userToolchain !== undefined && !rustup.isToolchainInstalled(userToolchain)) { + logger.error('user toolchain is not installed'); + window.showErrorMessage('The specified toolchain is not installed'); + rustup.setUserToolchain(undefined); + } + } + if (!rustup.getUserToolchain()) { + await handleMissingRustupUserToolchain(logger, rustup); + } + const userToolchain = rustup.getUserToolchain(); + if (userToolchain) { + await rustup.updateSysrootPath(userToolchain); + await rustup.updateComponents(userToolchain); + await rustup.updatePathToRustSourceCodePath(); + } + const nightlyToolchain = rustup.getNightlyToolchain(logger.createChildLogger('activate: ')); + if (nightlyToolchain && (!userToolchain || !userToolchain.equals(nightlyToolchain))) { + await rustup.updateComponents(nightlyToolchain); + if (rustup.isRlsInstalled()) { + await rustup.updatePathToRlsExecutable(); + } + } + } const rustSource = await RustSource.create(rustup); const configuration = new Configuration(logger.createChildLogger('Configuration: ')); const rlsConfiguration = await RlsConfiguration.create(rustup, rustSource);