From 0260e41283a2fdfb2d0ae25ea1405f4f2d71429b Mon Sep 17 00:00:00 2001 From: David Richey Date: Thu, 10 Oct 2024 20:00:33 -0500 Subject: [PATCH] Run subprocesses async in vscode extension --- editors/code/src/bootstrap.ts | 39 ++++++++-------- editors/code/src/util.ts | 54 ++++++++++++++++++++++- editors/code/tests/unit/bootstrap.test.ts | 22 ++++----- 3 files changed, 83 insertions(+), 32 deletions(-) diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts index 35867f710d..8fc9f09324 100644 --- a/editors/code/src/bootstrap.ts +++ b/editors/code/src/bootstrap.ts @@ -1,9 +1,9 @@ import * as vscode from "vscode"; import * as os from "os"; import type { Config } from "./config"; -import { type Env, log } from "./util"; +import { type Env, log, spawnAsync } from "./util"; import type { PersistentState } from "./persistent_state"; -import { exec, spawnSync } from "child_process"; +import { exec } from "child_process"; import { TextDecoder } from "node:util"; export async function bootstrap( @@ -61,13 +61,12 @@ async function getServer( // if so, use the rust-analyzer component const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml"); if (await hasToolchainFileWithRaDeclared(toolchainUri)) { - const res = spawnSync("rustup", ["which", "rust-analyzer"], { - encoding: "utf8", + const res = await spawnAsync("rustup", ["which", "rust-analyzer"], { env: { ...process.env }, cwd: workspaceFolder.uri.fsPath, }); if (!res.error && res.status === 0) { - toolchainServerPath = earliestToolchainPath( + toolchainServerPath = await earliestToolchainPath( toolchainServerPath, res.stdout.trim(), raVersionResolver, @@ -114,10 +113,8 @@ async function getServer( } // Given a path to a rust-analyzer executable, resolve its version and return it. -function raVersionResolver(path: string): string | undefined { - const res = spawnSync(path, ["--version"], { - encoding: "utf8", - }); +async function raVersionResolver(path: string): Promise { + const res = await spawnAsync(path, ["--version"]); if (!res.error && res.status === 0) { return res.stdout; } else { @@ -126,13 +123,16 @@ function raVersionResolver(path: string): string | undefined { } // Given a path to two rust-analyzer executables, return the earliest one by date. -function earliestToolchainPath( +async function earliestToolchainPath( path0: string | undefined, path1: string, - raVersionResolver: (path: string) => string | undefined, -): string { + raVersionResolver: (path: string) => Promise, +): Promise { if (path0) { - if (orderFromPath(path0, raVersionResolver) < orderFromPath(path1, raVersionResolver)) { + if ( + (await orderFromPath(path0, raVersionResolver)) < + (await orderFromPath(path1, raVersionResolver)) + ) { return path0; } else { return path1; @@ -150,11 +150,11 @@ function earliestToolchainPath( // nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer // versioned - /Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer // stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer -function orderFromPath( +async function orderFromPath( path: string, - raVersionResolver: (path: string) => string | undefined, -): string { - const raVersion = raVersionResolver(path); + raVersionResolver: (path: string) => Promise, +): Promise { + const raVersion = await raVersionResolver(path); const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); if (raDate?.length === 2) { const precedence = path.includes("nightly-") ? "0" : "1"; @@ -184,11 +184,10 @@ async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise } } -export function isValidExecutable(path: string, extraEnv: Env): boolean { +export async function isValidExecutable(path: string, extraEnv: Env): Promise { log.debug("Checking availability of a binary at", path); - const res = spawnSync(path, ["--version"], { - encoding: "utf8", + const res = await spawnAsync(path, ["--version"], { env: { ...process.env, ...extraEnv }, }); diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index db64a013fd..d7ca6b3557 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; import { strict as nativeAssert } from "assert"; -import { exec, type ExecOptions } from "child_process"; +import { exec, spawn, type SpawnOptionsWithoutStdio, type ExecOptions } from "child_process"; import { inspect } from "util"; import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext"; @@ -233,3 +233,55 @@ export function expectNotUndefined(input: Undefinable, msg: string): NotUn export function unwrapUndefinable(input: Undefinable): NotUndefined { return expectNotUndefined(input, `unwrapping \`undefined\``); } + +interface SpawnAsyncReturns { + stdout: string; + stderr: string; + status: number | null; + error?: Error | undefined; +} + +export async function spawnAsync( + path: string, + args?: ReadonlyArray, + options?: SpawnOptionsWithoutStdio, +): Promise { + const child = spawn(path, args, options); + const stdout: Array = []; + const stderr: Array = []; + try { + const res = await new Promise<{ stdout: string; stderr: string; status: number | null }>( + (resolve, reject) => { + child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk))); + child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk))); + child.on("error", (error) => + reject({ + stdout: Buffer.concat(stdout).toString("utf8"), + stderr: Buffer.concat(stderr).toString("utf8"), + error, + }), + ); + child.on("close", (status) => + resolve({ + stdout: Buffer.concat(stdout).toString("utf8"), + stderr: Buffer.concat(stderr).toString("utf8"), + status, + }), + ); + }, + ); + + return { + stdout: res.stdout, + stderr: res.stderr, + status: res.status, + }; + } catch (e: any) { + return { + stdout: e.stdout, + stderr: e.stderr, + status: e.status, + error: e.error, + }; + } +} diff --git a/editors/code/tests/unit/bootstrap.test.ts b/editors/code/tests/unit/bootstrap.test.ts index 8aeb72180a..baabf4f897 100644 --- a/editors/code/tests/unit/bootstrap.test.ts +++ b/editors/code/tests/unit/bootstrap.test.ts @@ -6,9 +6,9 @@ export async function getTests(ctx: Context) { await ctx.suite("Bootstrap/Select toolchain RA", (suite) => { suite.addTest("Order of nightly RA", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { assert.deepStrictEqual( path, "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", @@ -22,9 +22,9 @@ export async function getTests(ctx: Context) { suite.addTest("Order of versioned RA", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { assert.deepStrictEqual( path, "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", @@ -38,9 +38,9 @@ export async function getTests(ctx: Context) { suite.addTest("Order of versioned RA when unable to obtain version date", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", - function () { + async function () { return "rust-analyzer 1.72.1"; }, ), @@ -50,9 +50,9 @@ export async function getTests(ctx: Context) { suite.addTest("Order of stable RA", async () => { assert.deepStrictEqual( - _private.orderFromPath( + await _private.orderFromPath( "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { assert.deepStrictEqual( path, "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", @@ -66,7 +66,7 @@ export async function getTests(ctx: Context) { suite.addTest("Order with invalid path to RA", async () => { assert.deepStrictEqual( - _private.orderFromPath("some-weird-path", function () { + await _private.orderFromPath("some-weird-path", async function () { return undefined; }), "2", @@ -75,10 +75,10 @@ export async function getTests(ctx: Context) { suite.addTest("Earliest RA between nightly and stable", async () => { assert.deepStrictEqual( - _private.earliestToolchainPath( + await _private.earliestToolchainPath( "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", - function (path: string) { + async function (path: string) { if ( path === "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer"