diff --git a/src/components/configuration/Configuration.ts b/src/components/configuration/Configuration.ts index 3519078..b3cf37a 100644 --- a/src/components/configuration/Configuration.ts +++ b/src/components/configuration/Configuration.ts @@ -81,18 +81,16 @@ export class Configuration { * @param logger a logger to log messages */ public static async create(logger: ChildLogger): Promise { - const rustcSysRoot: string | undefined = await this.loadRustcSysRoot(); - const createRustInstallationPromise = async () => { - if (!rustcSysRoot) { - return undefined; - } - if (Rustup.doesManageRustcSysRoot(rustcSysRoot)) { - return await Rustup.create(logger.createChildLogger('Rustup: '), rustcSysRoot); - } else { - return new NotRustup(rustcSysRoot); + const rustup: Rustup | undefined = await Rustup.create(logger.createChildLogger('Rustup: ')); + let rustInstallation: Rustup | NotRustup | undefined = undefined; + if (rustup) { + rustInstallation = rustup; + } else { + const rustcSysRoot: string | undefined = await this.loadRustcSysRoot(); + if (rustcSysRoot) { + rustInstallation = new NotRustup(rustcSysRoot); } - }; - const rustInstallation: Rustup | NotRustup | undefined = await createRustInstallationPromise(); + } const pathToRustSourceCodeSpecifiedByUser = await this.checkPathToRustSourceCodeSpecifiedByUser(); const configuration = new Configuration( logger, @@ -176,16 +174,13 @@ export class Configuration { if (this.rlsPathSpecifiedByUser) { return this.rlsPathSpecifiedByUser; } - - if (this.rustInstallation instanceof Rustup) { - const pathToRlsExecutable = this.rustInstallation.getPathToRlsExecutable(); - - if (pathToRlsExecutable) { - return pathToRlsExecutable; - } + if (!(this.rustInstallation instanceof Rustup)) { + return undefined; } - - return undefined; + if (!this.rustInstallation.isRlsInstalled()) { + return undefined; + } + return 'rustup'; } /** @@ -198,16 +193,21 @@ export class Configuration { const getRlsArgsSpecifiedByUser = () => { const rlsConfiguration: any = this.getRlsConfiguration(); if (!rlsConfiguration) { - return undefined; + return []; } const rlsArgsSpecifiedByUser: any = rlsConfiguration.args; if (!rlsArgsSpecifiedByUser) { - return undefined; + return []; } return rlsArgsSpecifiedByUser; }; - const rlsArgs = getRlsArgsSpecifiedByUser() || []; - return rlsArgs; + if (!(this.rustInstallation instanceof Rustup)) { + return getRlsArgsSpecifiedByUser(); + } + if (!this.rustInstallation.isRlsInstalled()) { + return getRlsArgsSpecifiedByUser(); + } + return ['run', 'nightly', 'rls'].concat(getRlsArgsSpecifiedByUser()); } /** diff --git a/src/components/configuration/Rustup.ts b/src/components/configuration/Rustup.ts index ac1198f..f4daf4c 100644 --- a/src/components/configuration/Rustup.ts +++ b/src/components/configuration/Rustup.ts @@ -62,9 +62,12 @@ export class Rustup { * 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, pathToRustcSysRoot: string): Promise { - logger.createChildLogger('create: ').debug(`sysroot=${pathToRustcSysRoot}`); - const rustup = new Rustup(logger, pathToRustcSysRoot, undefined, undefined); + public static async create(logger: ChildLogger): Promise { + const sysrootPath: string | undefined = await this.invokeGettingSysrootPath('nightly', logger); + if (!sysrootPath) { + return undefined; + } + const rustup = new Rustup(logger, sysrootPath, undefined, undefined); await rustup.updatePathToRustSourceCodePath(); await rustup.updateComponents(); await rustup.updatePathToRlsExecutable(); @@ -125,7 +128,7 @@ export class Rustup { */ public async updateComponents(): Promise { const logger = this.logger.createChildLogger('updateComponents: '); - const stdoutData: string | undefined = await this.invokeRustup(['component', 'list']); + const stdoutData: string | undefined = await Rustup.invoke(['component', 'list', '--toolchain', 'nightly'], logger); if (!stdoutData) { logger.error(`stdoutData=${stdoutData}`); return undefined; @@ -192,6 +195,14 @@ export class Rustup { return true; } + /** + * Returns if RLS is installed + * @return true if RLS is installed otherwise false + */ + public isRlsInstalled(): boolean { + return this.isComponentInstalled(Rustup.getRlsComponentName()); + } + /** * Returns true if the component `rust-analysis` can be installed otherwise false. * If the component is already installed, the method returns false @@ -233,6 +244,52 @@ export class Rustup { return ' (installed)'; } + /** + * Invokes rustup to get the path to the sysroot of the specified toolchain. + * Checks if the invocation exited successfully and returns the output of the invocation + * @param toolchain The toolchain to get the path to the sysroot for + * @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 output: string | undefined = await this.invokeRun(toolchain, ['rustc', '--print', 'sysroot'], logger); + if (!output) { + return undefined; + } + return output.trim(); + } + + /** + * 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 + * @param args The arguments to invoke rustup with + * @param logger The logger to log messages + */ + private static async invokeRun(toolchain: string, args: string[], logger: ChildLogger): Promise { + return await this.invoke(['run', toolchain, ...args], logger); + } + + /** + * Invokes Rustup with specified arguments, checks if it exited successfully and returns its output + * @param args Arguments to invoke Rustup with + * @param logger The logger to log messages + * @returns an output if invocation exited successfully otherwise undefined + */ + private static async invoke(args: string[], logger: ChildLogger): Promise { + const rustupExe = Rustup.getRustupExecutable(); + const functionLogger = logger.createChildLogger(`invoke: rustupExe=${rustupExe}, args=${JSON.stringify(args)}: `); + const result = await OutputtingProcess.spawn(rustupExe, args, undefined); + if (!result.success) { + functionLogger.error('failed'); + return undefined; + } + if (result.exitCode !== 0) { + functionLogger.error(`exited unexpectedly; exitCode=${result.exitCode}, stderrData=${result.stderrData}`); + return undefined; + } + return result.stdoutData; + } + /** * Constructs a new instance of the class. * The constructor is private because creating a new instance should be done via the method `create` @@ -258,36 +315,6 @@ export class Rustup { this.components = []; } - /** - * Invokes Rustup with specified arguments, checks it exited successfully and returns its output - * @param args Arguments to invoke Rustup with - * @returns an output if invokation Rustup exited successfully otherwise undefined - */ - private async invokeRustup(args: string[]): Promise { - const logger = this.logger.createChildLogger('invokeRustup: '); - - const rustupExe = Rustup.getRustupExecutable(); - - // We assume that the executable of Rustup can be called since usually both `rustc` and `rustup` are placed in the same directory - const result = await OutputtingProcess.spawn(rustupExe, args, undefined); - - if (!result.success) { - // It actually shouldn't happen. - // If it happens, then there is some problem and we need to know about it - logger.error(`failed to execute ${rustupExe}. This should not have happened`); - - return undefined; - } - - if (result.exitCode !== 0) { - logger.error(`${rustupExe} ${args.join(' ')} exited with code=${result.exitCode}, but zero is expected. This should not have happened. stderrData=${result.stderrData}`); - - return undefined; - } - - return result.stdoutData; - } - /** * Takes from the field `components` only installed components * @returns a list of installed components @@ -317,8 +344,8 @@ export class Rustup { // We return true because the component is installed, but anyway it is an exceptional situation return true; } - const args = ['component', 'add', componentName]; - const stdoutData: string | undefined = await this.invokeRustup(args); + const args = ['component', 'add', componentName, '--toolchain', 'nightly']; + const stdoutData: string | undefined = await Rustup.invoke(args, logger); // Some error occurred. It is already logged in the method invokeRustup. // So we just need to notify a caller that the installation failed if (stdoutData === undefined) {