Skip to content
This repository was archived by the owner on Dec 8, 2020. It is now read-only.

Install the nightly toolchain with rustup #249

Merged
merged 1 commit into from
May 19, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
146 changes: 108 additions & 38 deletions src/components/configuration/Rustup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { FileSystem } from '../file_system/FileSystem';

import ChildLogger from '../logging/child_logger';

namespace Constants {
export const DEFAULT_TOOLCHAIN_SUFFIX = '(default)';
}

/**
* Configuration of Rust installed via Rustup
*/
Expand All @@ -21,7 +25,7 @@ export class Rustup {
* A path to Rust's installation root.
* It is what `rustc --print=sysroot` returns.
*/
private pathToRustcSysRoot: string;
private pathToRustcSysRoot: string | undefined;

/**
* A path to Rust's source code.
Expand All @@ -41,44 +45,51 @@ export class Rustup {
private components: string[];

/**
* Checks if Rustup manages a specified Rust's installation root
* @param rustcSysRoot a path to Rust's installation root to check
* @returns true if Rustup manages it otherwire false
* Toolchains received by invoking rustup
*/
public static doesManageRustcSysRoot(pathToRustcSysRoot: string): boolean {
// Usually rustup installs itself to the directory `.rustup` so if the sysroot is in the directory `.rustup`, then it is controlled by rustup.
// Also a user can specify a directory to install rustup to by specifying the environment variable `RUSTUP_HOME`
const rustupHome: string | undefined = process.env.RUSTUP_HOME;
if (rustupHome) {
return pathToRustcSysRoot.startsWith(rustupHome);
} else {
// It can be inaccurate since nobody can stop a user from installing Rust not via Rustup, but to `.rustup` directory
return pathToRustcSysRoot.includes('.rustup');
}
}
private toolchains: string[];

/**
* Creates a new instance of the class.
* The method is asynchronous because it tries to find Rust's source code
* @param pathToRustcSysRoot A path to Rust's installation root
*/
public static async create(logger: ChildLogger): Promise<Rustup | undefined> {
const sysrootPath: string | undefined = await this.invokeGettingSysrootPath('nightly', logger);
if (!sysrootPath) {
const rustupExe = await FileSystem.findExecutablePath(Rustup.getRustupExecutable());
if (!rustupExe) {
return undefined;
}
const rustup = new Rustup(logger, sysrootPath, undefined, undefined);
await rustup.updatePathToRustSourceCodePath();
const rustup = new Rustup(logger);
await rustup.updateToolchains();
await rustup.updateComponents();
await rustup.updateSysrootPath('nightly');
await rustup.updatePathToRustSourceCodePath();
await rustup.updatePathToRlsExecutable();
return rustup;
}

/**
* Returns the path to Rust's installation root
* 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;
}
}

/**
* Returns either the default toolchain or undefined if there are no installed toolchains
*/
public getPathToRustcSysRoot(): string {
return this.pathToRustcSysRoot;
public getDefaultToolchain(): string | undefined {
const logger = this.logger.createChildLogger('getDefaultToolchain: ');
const toolchain = this.toolchains.find(t => t.endsWith(Constants.DEFAULT_TOOLCHAIN_SUFFIX));
if (!toolchain && this.toolchains.length !== 0) {
logger.error(`no default toolchain; this.toolchains=${this.toolchains}`);
}
return toolchain;
}

/**
Expand All @@ -95,6 +106,28 @@ export class Rustup {
return this.pathToRlsExecutable;
}

/**
* Requests rustup to install the specified toolchain
* @param toolchain The toolchain to install
* @return true if no error occurred and the toolchain has been installed otherwise false
*/
public async installToolchain(toolchain: string): Promise<boolean> {
const logger = this.logger.createChildLogger(`installToolchain: toolchain=${toolchain}`);
const output = await Rustup.invoke(['toolchain', 'install', toolchain], logger);
if (output) {
logger.debug(`output=${output}`);
} else {
logger.error(`output=${output}`);
return false;
}
await this.updateToolchains();
if (this.toolchains.length === 0) {
logger.error('this.toolchains.length === 0');
return false;
}
return true;
}

/**
* Requests Rustup install RLS
* @return true if no error occurred and RLS has been installed otherwise false
Expand Down Expand Up @@ -127,26 +160,61 @@ export class Rustup {
* Requests rustup to give components list and saves them in the field `components`
*/
public async updateComponents(): Promise<void> {
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);
if (!stdoutData) {
logger.error(`stdoutData=${stdoutData}`);
return undefined;
return;
}
this.components = stdoutData.split('\n');
logger.debug(`this.components=${JSON.stringify(this.components)}`);
}

/**
* Requests rustup to give toolchains list and saves it in the field `toolchains`
*/
public async updateToolchains(): Promise<void> {
const logger = this.logger.createChildLogger('updateToolchains: ');
this.toolchains = await Rustup.invokeGettingToolchains(logger);
logger.debug(`this.toolchains=${JSON.stringify(this.toolchains)}`);
}

/**
* 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<void> {
this.pathToRustcSysRoot = undefined;
const logger = this.logger.createChildLogger(`updateSysrootPath: toolchain=${toolchain}: `);
if (!this.toolchains.find(t => t.startsWith(toolchain))) {
logger.error('toolchain is not installed');
return;
}
this.pathToRustcSysRoot = await Rustup.invokeGettingSysrootPath(toolchain, logger);
if (!this.pathToRustcSysRoot) {
logger.error(`this.pathToRustcSysRoot=${this.pathToRustcSysRoot}`);
}
}

/**
* Checks if Rust's source code is installed at the expected path.
* This method assigns either the expected path or undefined to the field `pathToRustSourceCode`, depending on if the expected path exists.
* The method is asynchronous because it checks if the expected path exists
*/
public async updatePathToRustSourceCodePath(): Promise<void> {
const logger = this.logger.createChildLogger('updatePathToRustSourceCodePath: ');
this.pathToRustSourceCode = undefined;
if (!this.pathToRustcSysRoot) {
logger.error(`this.pathToRustcSysRoot=${this.pathToRustcSysRoot}`);
return;
}
const pathToRustSourceCode = join(this.pathToRustcSysRoot, 'lib', 'rustlib', 'src', 'rust', 'src');

const isRustSourceCodeInstalled: boolean = await FileSystem.doesPathExist(pathToRustSourceCode);

if (isRustSourceCodeInstalled) {
this.pathToRustSourceCode = pathToRustSourceCode;
} else {
Expand Down Expand Up @@ -259,6 +327,16 @@ export class Rustup {
return output.trim();
}

private static async invokeGettingToolchains(logger: ChildLogger): Promise<string[]> {
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');
}

/**
* Invokes `rustup run...` with the specified toolchain and arguments, checks if it exited successfully and returns its output
* @param toolchain The toolchain to invoke rustup with
Expand Down Expand Up @@ -298,21 +376,13 @@ export class Rustup {
* @param pathToRustSourceCode A value for the field `pathToRustSourceCode`
* @param pathToRlsExecutable A value fo the field `pathToRlsExecutable`
*/
private constructor(
logger: ChildLogger,
pathToRustcSysRoot: string,
pathToRustSourceCode: string | undefined,
pathToRlsExecutable: string | undefined
) {
private constructor(logger: ChildLogger) {
this.logger = logger;

this.pathToRustcSysRoot = pathToRustcSysRoot;

this.pathToRustSourceCode = pathToRustSourceCode;

this.pathToRlsExecutable = pathToRlsExecutable;

this.pathToRustcSysRoot = undefined;
this.pathToRustSourceCode = undefined;
this.pathToRlsExecutable = undefined;
this.components = [];
this.toolchains = [];
}

/**
Expand Down
45 changes: 45 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { Manager as LanguageClientManager } from './components/language_client/m

import LoggingManager from './components/logging/logging_manager';

import ChildLogger from './components/logging/child_logger';

import RootLogger from './components/logging/root_logger';

import LegacyModeManager from './legacy_mode_manager';
Expand Down Expand Up @@ -60,6 +62,42 @@ async function askPermissionToInstallRls(logger: RootLogger): Promise<RlsInstall
}
}

/**
* Asks the user's permission to install the nightly toolchain
* @param logger The logger to log messages
* @return true if the user granted the permission otherwise false
*/
async function askPermissionToInstallNightlyToolchain(logger: ChildLogger): Promise<boolean> {
const functionLogger = logger.createChildLogger('askPermissionToInstallNightlyToolchain: ');
const installChoice = 'Install';
const choice = await window.showInformationMessage('Do you want to install the nightly toolchain?', installChoice);
functionLogger.debug(`choice=${choice}`);
return choice === installChoice;
}

/**
* Handles the case when rustup reported that the nightly toolchain wasn't installed
* @param logger The logger to log messages
* @param rustup The rustup
*/
async function handleMissingNightlyToolchain(logger: ChildLogger, rustup: Rustup): Promise<boolean> {
const functionLogger = logger.createChildLogger('handleMissingNightlyToolchain: ');
await window.showInformationMessage('The nightly toolchain is not installed, but is required to install RLS');
const permissionGranted = await askPermissionToInstallNightlyToolchain(logger);
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}`);
if (!toolchainInstalled) {
return false;
}
await rustup.updateComponents();
return true;
}

/**
* Handles the case when the user does not have RLS.
* It tries to install RLS if it is possible
Expand Down Expand Up @@ -89,6 +127,13 @@ async function handleMissingRls(logger: RootLogger, configuration: Configuration
case RlsInstallDecision.NotInstall:
return;
}
if (!rustup.isNightlyToolchainInstalled()) {
await handleMissingNightlyToolchain(functionLogger, rustup);
if (!rustup.isNightlyToolchainInstalled()) {
functionLogger.error('nightly toolchain is not installed');
return;
}
}
async function installComponent(componentName: string, installComponent: () => Promise<boolean>): Promise<boolean> {
window.showInformationMessage(`${componentName} is being installed. It can take a while`);
const componentInstalled: boolean = await installComponent();
Expand Down