Run subprocesses async in vscode extension

This commit is contained in:
David Richey 2024-10-10 20:00:33 -05:00
parent 0fb804acb3
commit 0260e41283
3 changed files with 83 additions and 32 deletions

View file

@ -1,9 +1,9 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import * as os from "os"; import * as os from "os";
import type { Config } from "./config"; 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 type { PersistentState } from "./persistent_state";
import { exec, spawnSync } from "child_process"; import { exec } from "child_process";
import { TextDecoder } from "node:util"; import { TextDecoder } from "node:util";
export async function bootstrap( export async function bootstrap(
@ -61,13 +61,12 @@ async function getServer(
// if so, use the rust-analyzer component // if so, use the rust-analyzer component
const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml"); const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml");
if (await hasToolchainFileWithRaDeclared(toolchainUri)) { if (await hasToolchainFileWithRaDeclared(toolchainUri)) {
const res = spawnSync("rustup", ["which", "rust-analyzer"], { const res = await spawnAsync("rustup", ["which", "rust-analyzer"], {
encoding: "utf8",
env: { ...process.env }, env: { ...process.env },
cwd: workspaceFolder.uri.fsPath, cwd: workspaceFolder.uri.fsPath,
}); });
if (!res.error && res.status === 0) { if (!res.error && res.status === 0) {
toolchainServerPath = earliestToolchainPath( toolchainServerPath = await earliestToolchainPath(
toolchainServerPath, toolchainServerPath,
res.stdout.trim(), res.stdout.trim(),
raVersionResolver, raVersionResolver,
@ -114,10 +113,8 @@ async function getServer(
} }
// Given a path to a rust-analyzer executable, resolve its version and return it. // Given a path to a rust-analyzer executable, resolve its version and return it.
function raVersionResolver(path: string): string | undefined { async function raVersionResolver(path: string): Promise<string | undefined> {
const res = spawnSync(path, ["--version"], { const res = await spawnAsync(path, ["--version"]);
encoding: "utf8",
});
if (!res.error && res.status === 0) { if (!res.error && res.status === 0) {
return res.stdout; return res.stdout;
} else { } 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. // Given a path to two rust-analyzer executables, return the earliest one by date.
function earliestToolchainPath( async function earliestToolchainPath(
path0: string | undefined, path0: string | undefined,
path1: string, path1: string,
raVersionResolver: (path: string) => string | undefined, raVersionResolver: (path: string) => Promise<string | undefined>,
): string { ): Promise<string> {
if (path0) { if (path0) {
if (orderFromPath(path0, raVersionResolver) < orderFromPath(path1, raVersionResolver)) { if (
(await orderFromPath(path0, raVersionResolver)) <
(await orderFromPath(path1, raVersionResolver))
) {
return path0; return path0;
} else { } else {
return path1; return path1;
@ -150,11 +150,11 @@ function earliestToolchainPath(
// nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer // 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 // 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 // stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer
function orderFromPath( async function orderFromPath(
path: string, path: string,
raVersionResolver: (path: string) => string | undefined, raVersionResolver: (path: string) => Promise<string | undefined>,
): string { ): Promise<string> {
const raVersion = raVersionResolver(path); const raVersion = await raVersionResolver(path);
const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/);
if (raDate?.length === 2) { if (raDate?.length === 2) {
const precedence = path.includes("nightly-") ? "0" : "1"; const precedence = path.includes("nightly-") ? "0" : "1";
@ -184,11 +184,10 @@ async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise<boolean>
} }
} }
export function isValidExecutable(path: string, extraEnv: Env): boolean { export async function isValidExecutable(path: string, extraEnv: Env): Promise<boolean> {
log.debug("Checking availability of a binary at", path); log.debug("Checking availability of a binary at", path);
const res = spawnSync(path, ["--version"], { const res = await spawnAsync(path, ["--version"], {
encoding: "utf8",
env: { ...process.env, ...extraEnv }, env: { ...process.env, ...extraEnv },
}); });

View file

@ -1,6 +1,6 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { strict as nativeAssert } from "assert"; 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 { inspect } from "util";
import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext"; import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext";
@ -233,3 +233,55 @@ export function expectNotUndefined<T>(input: Undefinable<T>, msg: string): NotUn
export function unwrapUndefinable<T>(input: Undefinable<T>): NotUndefined<T> { export function unwrapUndefinable<T>(input: Undefinable<T>): NotUndefined<T> {
return expectNotUndefined(input, `unwrapping \`undefined\``); 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<string>,
options?: SpawnOptionsWithoutStdio,
): Promise<SpawnAsyncReturns> {
const child = spawn(path, args, options);
const stdout: Array<Buffer> = [];
const stderr: Array<Buffer> = [];
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,
};
}
}

View file

@ -6,9 +6,9 @@ export async function getTests(ctx: Context) {
await ctx.suite("Bootstrap/Select toolchain RA", (suite) => { await ctx.suite("Bootstrap/Select toolchain RA", (suite) => {
suite.addTest("Order of nightly RA", async () => { suite.addTest("Order of nightly RA", async () => {
assert.deepStrictEqual( assert.deepStrictEqual(
_private.orderFromPath( await _private.orderFromPath(
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-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) {
assert.deepStrictEqual( assert.deepStrictEqual(
path, path,
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", "/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 () => { suite.addTest("Order of versioned RA", async () => {
assert.deepStrictEqual( assert.deepStrictEqual(
_private.orderFromPath( await _private.orderFromPath(
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
function (path: string) { async function (path: string) {
assert.deepStrictEqual( assert.deepStrictEqual(
path, path,
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", "/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 () => { suite.addTest("Order of versioned RA when unable to obtain version date", async () => {
assert.deepStrictEqual( assert.deepStrictEqual(
_private.orderFromPath( await _private.orderFromPath(
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer", "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
function () { async function () {
return "rust-analyzer 1.72.1"; return "rust-analyzer 1.72.1";
}, },
), ),
@ -50,9 +50,9 @@ export async function getTests(ctx: Context) {
suite.addTest("Order of stable RA", async () => { suite.addTest("Order of stable RA", async () => {
assert.deepStrictEqual( assert.deepStrictEqual(
_private.orderFromPath( await _private.orderFromPath(
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
function (path: string) { async function (path: string) {
assert.deepStrictEqual( assert.deepStrictEqual(
path, path,
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", "/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 () => { suite.addTest("Order with invalid path to RA", async () => {
assert.deepStrictEqual( assert.deepStrictEqual(
_private.orderFromPath("some-weird-path", function () { await _private.orderFromPath("some-weird-path", async function () {
return undefined; return undefined;
}), }),
"2", "2",
@ -75,10 +75,10 @@ export async function getTests(ctx: Context) {
suite.addTest("Earliest RA between nightly and stable", async () => { suite.addTest("Earliest RA between nightly and stable", async () => {
assert.deepStrictEqual( assert.deepStrictEqual(
_private.earliestToolchainPath( await _private.earliestToolchainPath(
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", "/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", "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
function (path: string) { async function (path: string) {
if ( if (
path === path ===
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer" "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer"