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

Commit 0b50fbb

Browse files
authored
Install the nightly toolchain with rustup (#249)
1 parent 9e15f1b commit 0b50fbb

File tree

2 files changed

+153
-38
lines changed

2 files changed

+153
-38
lines changed

src/components/configuration/Rustup.ts

Lines changed: 108 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { FileSystem } from '../file_system/FileSystem';
88

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

11+
namespace Constants {
12+
export const DEFAULT_TOOLCHAIN_SUFFIX = '(default)';
13+
}
14+
1115
/**
1216
* Configuration of Rust installed via Rustup
1317
*/
@@ -21,7 +25,7 @@ export class Rustup {
2125
* A path to Rust's installation root.
2226
* It is what `rustc --print=sysroot` returns.
2327
*/
24-
private pathToRustcSysRoot: string;
28+
private pathToRustcSysRoot: string | undefined;
2529

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

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

6052
/**
6153
* Creates a new instance of the class.
6254
* The method is asynchronous because it tries to find Rust's source code
6355
* @param pathToRustcSysRoot A path to Rust's installation root
6456
*/
6557
public static async create(logger: ChildLogger): Promise<Rustup | undefined> {
66-
const sysrootPath: string | undefined = await this.invokeGettingSysrootPath('nightly', logger);
67-
if (!sysrootPath) {
58+
const rustupExe = await FileSystem.findExecutablePath(Rustup.getRustupExecutable());
59+
if (!rustupExe) {
6860
return undefined;
6961
}
70-
const rustup = new Rustup(logger, sysrootPath, undefined, undefined);
71-
await rustup.updatePathToRustSourceCodePath();
62+
const rustup = new Rustup(logger);
63+
await rustup.updateToolchains();
7264
await rustup.updateComponents();
65+
await rustup.updateSysrootPath('nightly');
66+
await rustup.updatePathToRustSourceCodePath();
7367
await rustup.updatePathToRlsExecutable();
7468
return rustup;
7569
}
7670

7771
/**
78-
* Returns the path to Rust's installation root
72+
* Returns whether the nightly toolchain is installed or not
73+
*/
74+
public isNightlyToolchainInstalled(): boolean {
75+
const nightlyToolchain = this.toolchains.find(t => t.startsWith('nightly'));
76+
if (nightlyToolchain) {
77+
return true;
78+
} else {
79+
return false;
80+
}
81+
}
82+
83+
/**
84+
* Returns either the default toolchain or undefined if there are no installed toolchains
7985
*/
80-
public getPathToRustcSysRoot(): string {
81-
return this.pathToRustcSysRoot;
86+
public getDefaultToolchain(): string | undefined {
87+
const logger = this.logger.createChildLogger('getDefaultToolchain: ');
88+
const toolchain = this.toolchains.find(t => t.endsWith(Constants.DEFAULT_TOOLCHAIN_SUFFIX));
89+
if (!toolchain && this.toolchains.length !== 0) {
90+
logger.error(`no default toolchain; this.toolchains=${this.toolchains}`);
91+
}
92+
return toolchain;
8293
}
8394

8495
/**
@@ -95,6 +106,28 @@ export class Rustup {
95106
return this.pathToRlsExecutable;
96107
}
97108

109+
/**
110+
* Requests rustup to install the specified toolchain
111+
* @param toolchain The toolchain to install
112+
* @return true if no error occurred and the toolchain has been installed otherwise false
113+
*/
114+
public async installToolchain(toolchain: string): Promise<boolean> {
115+
const logger = this.logger.createChildLogger(`installToolchain: toolchain=${toolchain}`);
116+
const output = await Rustup.invoke(['toolchain', 'install', toolchain], logger);
117+
if (output) {
118+
logger.debug(`output=${output}`);
119+
} else {
120+
logger.error(`output=${output}`);
121+
return false;
122+
}
123+
await this.updateToolchains();
124+
if (this.toolchains.length === 0) {
125+
logger.error('this.toolchains.length === 0');
126+
return false;
127+
}
128+
return true;
129+
}
130+
98131
/**
99132
* Requests Rustup install RLS
100133
* @return true if no error occurred and RLS has been installed otherwise false
@@ -127,26 +160,61 @@ export class Rustup {
127160
* Requests rustup to give components list and saves them in the field `components`
128161
*/
129162
public async updateComponents(): Promise<void> {
163+
this.components = [];
130164
const logger = this.logger.createChildLogger('updateComponents: ');
165+
if (!this.isNightlyToolchainInstalled()) {
166+
logger.error('nightly toolchain is not installed');
167+
return;
168+
}
131169
const stdoutData: string | undefined = await Rustup.invoke(['component', 'list', '--toolchain', 'nightly'], logger);
132170
if (!stdoutData) {
133171
logger.error(`stdoutData=${stdoutData}`);
134-
return undefined;
172+
return;
135173
}
136174
this.components = stdoutData.split('\n');
137175
logger.debug(`this.components=${JSON.stringify(this.components)}`);
138176
}
139177

178+
/**
179+
* Requests rustup to give toolchains list and saves it in the field `toolchains`
180+
*/
181+
public async updateToolchains(): Promise<void> {
182+
const logger = this.logger.createChildLogger('updateToolchains: ');
183+
this.toolchains = await Rustup.invokeGettingToolchains(logger);
184+
logger.debug(`this.toolchains=${JSON.stringify(this.toolchains)}`);
185+
}
186+
187+
/**
188+
* Requests rustup to give the path to the sysroot of the specified toolchain
189+
* @param toolchain The toolchain to get the path to the sysroot for
190+
*/
191+
public async updateSysrootPath(toolchain: string): Promise<void> {
192+
this.pathToRustcSysRoot = undefined;
193+
const logger = this.logger.createChildLogger(`updateSysrootPath: toolchain=${toolchain}: `);
194+
if (!this.toolchains.find(t => t.startsWith(toolchain))) {
195+
logger.error('toolchain is not installed');
196+
return;
197+
}
198+
this.pathToRustcSysRoot = await Rustup.invokeGettingSysrootPath(toolchain, logger);
199+
if (!this.pathToRustcSysRoot) {
200+
logger.error(`this.pathToRustcSysRoot=${this.pathToRustcSysRoot}`);
201+
}
202+
}
203+
140204
/**
141205
* Checks if Rust's source code is installed at the expected path.
142206
* This method assigns either the expected path or undefined to the field `pathToRustSourceCode`, depending on if the expected path exists.
143207
* The method is asynchronous because it checks if the expected path exists
144208
*/
145209
public async updatePathToRustSourceCodePath(): Promise<void> {
210+
const logger = this.logger.createChildLogger('updatePathToRustSourceCodePath: ');
211+
this.pathToRustSourceCode = undefined;
212+
if (!this.pathToRustcSysRoot) {
213+
logger.error(`this.pathToRustcSysRoot=${this.pathToRustcSysRoot}`);
214+
return;
215+
}
146216
const pathToRustSourceCode = join(this.pathToRustcSysRoot, 'lib', 'rustlib', 'src', 'rust', 'src');
147-
148217
const isRustSourceCodeInstalled: boolean = await FileSystem.doesPathExist(pathToRustSourceCode);
149-
150218
if (isRustSourceCodeInstalled) {
151219
this.pathToRustSourceCode = pathToRustSourceCode;
152220
} else {
@@ -259,6 +327,16 @@ export class Rustup {
259327
return output.trim();
260328
}
261329

330+
private static async invokeGettingToolchains(logger: ChildLogger): Promise<string[]> {
331+
const functionLogger = logger.createChildLogger('invokeGettingToolchains: ');
332+
const output = await this.invoke(['toolchain', 'list'], functionLogger);
333+
if (!output) {
334+
functionLogger.error(`output=${output}`);
335+
return [];
336+
}
337+
return output.trim().split('\n');
338+
}
339+
262340
/**
263341
* Invokes `rustup run...` with the specified toolchain and arguments, checks if it exited successfully and returns its output
264342
* @param toolchain The toolchain to invoke rustup with
@@ -298,21 +376,13 @@ export class Rustup {
298376
* @param pathToRustSourceCode A value for the field `pathToRustSourceCode`
299377
* @param pathToRlsExecutable A value fo the field `pathToRlsExecutable`
300378
*/
301-
private constructor(
302-
logger: ChildLogger,
303-
pathToRustcSysRoot: string,
304-
pathToRustSourceCode: string | undefined,
305-
pathToRlsExecutable: string | undefined
306-
) {
379+
private constructor(logger: ChildLogger) {
307380
this.logger = logger;
308-
309-
this.pathToRustcSysRoot = pathToRustcSysRoot;
310-
311-
this.pathToRustSourceCode = pathToRustSourceCode;
312-
313-
this.pathToRlsExecutable = pathToRlsExecutable;
314-
381+
this.pathToRustcSysRoot = undefined;
382+
this.pathToRustSourceCode = undefined;
383+
this.pathToRlsExecutable = undefined;
315384
this.components = [];
385+
this.toolchains = [];
316386
}
317387

318388
/**

src/extension.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { Manager as LanguageClientManager } from './components/language_client/m
1515

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

18+
import ChildLogger from './components/logging/child_logger';
19+
1820
import RootLogger from './components/logging/root_logger';
1921

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

65+
/**
66+
* Asks the user's permission to install the nightly toolchain
67+
* @param logger The logger to log messages
68+
* @return true if the user granted the permission otherwise false
69+
*/
70+
async function askPermissionToInstallNightlyToolchain(logger: ChildLogger): Promise<boolean> {
71+
const functionLogger = logger.createChildLogger('askPermissionToInstallNightlyToolchain: ');
72+
const installChoice = 'Install';
73+
const choice = await window.showInformationMessage('Do you want to install the nightly toolchain?', installChoice);
74+
functionLogger.debug(`choice=${choice}`);
75+
return choice === installChoice;
76+
}
77+
78+
/**
79+
* Handles the case when rustup reported that the nightly toolchain wasn't installed
80+
* @param logger The logger to log messages
81+
* @param rustup The rustup
82+
*/
83+
async function handleMissingNightlyToolchain(logger: ChildLogger, rustup: Rustup): Promise<boolean> {
84+
const functionLogger = logger.createChildLogger('handleMissingNightlyToolchain: ');
85+
await window.showInformationMessage('The nightly toolchain is not installed, but is required to install RLS');
86+
const permissionGranted = await askPermissionToInstallNightlyToolchain(logger);
87+
functionLogger.debug(`permissionGranted=${permissionGranted}`);
88+
if (!permissionGranted) {
89+
return false;
90+
}
91+
window.showInformationMessage('The nightly toolchain is being installed. It can take a while. Please be patient');
92+
const toolchainInstalled = await rustup.installToolchain('nightly');
93+
functionLogger.debug(`toolchainInstalled=${toolchainInstalled}`);
94+
if (!toolchainInstalled) {
95+
return false;
96+
}
97+
await rustup.updateComponents();
98+
return true;
99+
}
100+
63101
/**
64102
* Handles the case when the user does not have RLS.
65103
* It tries to install RLS if it is possible
@@ -89,6 +127,13 @@ async function handleMissingRls(logger: RootLogger, configuration: Configuration
89127
case RlsInstallDecision.NotInstall:
90128
return;
91129
}
130+
if (!rustup.isNightlyToolchainInstalled()) {
131+
await handleMissingNightlyToolchain(functionLogger, rustup);
132+
if (!rustup.isNightlyToolchainInstalled()) {
133+
functionLogger.error('nightly toolchain is not installed');
134+
return;
135+
}
136+
}
92137
async function installComponent(componentName: string, installComponent: () => Promise<boolean>): Promise<boolean> {
93138
window.showInformationMessage(`${componentName} is being installed. It can take a while`);
94139
const componentInstalled: boolean = await installComponent();

0 commit comments

Comments
 (0)