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

Commit

Permalink
Install the nightly toolchain with rustup (#249)
Browse files Browse the repository at this point in the history
  • Loading branch information
KalitaAlexey authored May 19, 2017
1 parent 9e15f1b commit 0b50fbb
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 38 deletions.
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

0 comments on commit 0b50fbb

Please sign in to comment.