Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--editors/code/package.json8
-rw-r--r--editors/code/src/bootstrap.ts28
-rw-r--r--editors/code/src/config.ts27
-rw-r--r--editors/code/src/ctx.ts2
-rw-r--r--editors/code/src/run.ts22
-rw-r--r--editors/code/src/tasks.ts102
-rw-r--r--editors/code/tests/unit/settings.test.ts8
-rw-r--r--editors/code/tests/unit/tasks.test.ts139
8 files changed, 223 insertions, 113 deletions
diff --git a/editors/code/package.json b/editors/code/package.json
index 1c41114239..0ad5e20af7 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -323,14 +323,6 @@
{
"title": "general",
"properties": {
- "rust-analyzer.cargoRunner": {
- "type": [
- "null",
- "string"
- ],
- "default": null,
- "description": "Custom cargo runner extension ID."
- },
"rust-analyzer.restartServerOnConfigChange": {
"markdownDescription": "Whether to restart the server automatically when certain settings that require a restart are changed.",
"default": false,
diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts
index 5a92b285ae..f2884ad0b0 100644
--- a/editors/code/src/bootstrap.ts
+++ b/editors/code/src/bootstrap.ts
@@ -36,6 +36,12 @@ async function getServer(
config: Config,
state: PersistentState,
): Promise<string | undefined> {
+ const packageJson: {
+ version: string;
+ releaseTag: string | null;
+ enableProposedApi: boolean | undefined;
+ } = context.extension.packageJSON;
+
const explicitPath = process.env["__RA_LSP_SERVER_DEBUG"] ?? config.serverPath;
if (explicitPath) {
if (explicitPath.startsWith("~/")) {
@@ -43,7 +49,7 @@ async function getServer(
}
return explicitPath;
}
- if (config.package.releaseTag === null) return "rust-analyzer";
+ if (packageJson.releaseTag === null) return "rust-analyzer";
const ext = process.platform === "win32" ? ".exe" : "";
const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`);
@@ -54,8 +60,15 @@ async function getServer(
if (bundledExists) {
let server = bundled;
if (await isNixOs()) {
- server = await getNixOsServer(config, ext, state, bundled, server);
- await state.updateServerVersion(config.package.version);
+ server = await getNixOsServer(
+ context.globalStorageUri,
+ packageJson.version,
+ ext,
+ state,
+ bundled,
+ server,
+ );
+ await state.updateServerVersion(packageJson.version);
}
return server.fsPath;
}
@@ -86,19 +99,20 @@ export function isValidExecutable(path: string, extraEnv: Env): boolean {
}
async function getNixOsServer(
- config: Config,
+ globalStorageUri: vscode.Uri,
+ version: string,
ext: string,
state: PersistentState,
bundled: vscode.Uri,
server: vscode.Uri,
) {
- await vscode.workspace.fs.createDirectory(config.globalStorageUri).then();
- const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`);
+ await vscode.workspace.fs.createDirectory(globalStorageUri).then();
+ const dest = vscode.Uri.joinPath(globalStorageUri, `rust-analyzer${ext}`);
let exists = await vscode.workspace.fs.stat(dest).then(
() => true,
() => false,
);
- if (exists && config.package.version !== state.serverVersion) {
+ if (exists && version !== state.serverVersion) {
await vscode.workspace.fs.delete(dest);
exists = false;
}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 527fad944f..ca77215004 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -4,6 +4,7 @@ import * as path from "path";
import * as vscode from "vscode";
import { type Env, log, unwrapUndefinable, expectNotUndefined } from "./util";
import type { JsonProject } from "./rust_project";
+import type { Disposable } from "./ctx";
export type RunnableEnvCfgItem = {
mask?: string;
@@ -29,22 +30,9 @@ export class Config {
(opt) => `${this.rootSection}.${opt}`,
);
- readonly package: {
- version: string;
- releaseTag: string | null;
- enableProposedApi: boolean | undefined;
- } = vscode.extensions.getExtension(this.extensionId)!.packageJSON;
-
- readonly globalStorageUri: vscode.Uri;
-
- constructor(ctx: vscode.ExtensionContext) {
- this.globalStorageUri = ctx.globalStorageUri;
+ constructor(disposables: Disposable[]) {
this.discoveredWorkspaces = [];
- vscode.workspace.onDidChangeConfiguration(
- this.onDidChangeConfiguration,
- this,
- ctx.subscriptions,
- );
+ vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, disposables);
this.refreshLogging();
this.configureLanguage();
}
@@ -55,7 +43,10 @@ export class Config {
private refreshLogging() {
log.setEnabled(this.traceExtension ?? false);
- log.info("Extension version:", this.package.version);
+ log.info(
+ "Extension version:",
+ vscode.extensions.getExtension(this.extensionId)!.packageJSON.version,
+ );
const cfg = Object.entries(this.cfg).filter(([_, val]) => !(val instanceof Function));
log.info("Using configuration", Object.fromEntries(cfg));
@@ -277,10 +268,6 @@ export class Config {
return this.get<string[]>("runnables.problemMatcher") || [];
}
- get cargoRunner() {
- return this.get<string | undefined>("cargoRunner");
- }
-
get testExplorer() {
return this.get<boolean | undefined>("testExplorer");
}
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index bf0b84ec35..caa99d7619 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -118,7 +118,7 @@ export class Ctx implements RustAnalyzerExtensionApi {
extCtx.subscriptions.push(this);
this.version = extCtx.extension.packageJSON.version ?? "<unknown>";
this._serverVersion = "<not running>";
- this.config = new Config(extCtx);
+ this.config = new Config(extCtx.subscriptions);
this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
if (this.config.testExplorer) {
this.testController = vscode.tests.createTestController(
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index 583f803d9a..783bbc1607 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -111,26 +111,31 @@ export async function createTaskFromRunnable(
runnable: ra.Runnable,
config: Config,
): Promise<vscode.Task> {
- let definition: tasks.RustTargetDefinition;
+ const target = vscode.workspace.workspaceFolders?.[0];
+
+ let definition: tasks.TaskDefinition;
+ let options;
+ let cargo;
if (runnable.kind === "cargo") {
const runnableArgs = runnable.args;
let args = createCargoArgs(runnableArgs);
- let program: string;
if (runnableArgs.overrideCargo) {
// Split on spaces to allow overrides like "wrapper cargo".
const cargoParts = runnableArgs.overrideCargo.split(" ");
- program = unwrapUndefinable(cargoParts[0]);
+ cargo = unwrapUndefinable(cargoParts[0]);
args = [...cargoParts.slice(1), ...args];
} else {
- program = await toolchain.cargoPath();
+ cargo = await toolchain.cargoPath();
}
definition = {
type: tasks.CARGO_TASK_TYPE,
- command: program,
- args,
+ command: unwrapUndefinable(args[0]),
+ args: args.slice(1),
+ };
+ options = {
cwd: runnableArgs.workspaceRoot || ".",
env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv),
};
@@ -140,13 +145,14 @@ export async function createTaskFromRunnable(
type: tasks.SHELL_TASK_TYPE,
command: runnableArgs.program,
args: runnableArgs.args,
+ };
+ options = {
cwd: runnableArgs.cwd,
env: prepareBaseEnv(),
};
}
- const target = vscode.workspace.workspaceFolders?.[0];
- const exec = await tasks.targetToExecution(definition, config.cargoRunner, true);
+ const exec = await tasks.targetToExecution(definition, options, cargo);
const task = await tasks.buildRustTask(
target,
definition,
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index 6f4fbf9188..fac1cc6394 100644
--- a/editors/code/src/tasks.ts
+++ b/editors/code/src/tasks.ts
@@ -1,6 +1,5 @@
import * as vscode from "vscode";
import type { Config } from "./config";
-import { log, unwrapUndefinable } from "./util";
import * as toolchain from "./toolchain";
// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
@@ -10,21 +9,21 @@ export const SHELL_TASK_TYPE = "shell";
export const RUST_TASK_SOURCE = "rust";
-export type RustTargetDefinition = {
+export type TaskDefinition = vscode.TaskDefinition & {
readonly type: typeof CARGO_TASK_TYPE | typeof SHELL_TASK_TYPE;
-} & vscode.TaskDefinition &
- RustTarget;
-export type RustTarget = {
- // The command to run, usually `cargo`.
- command: string;
- // Additional arguments passed to the command.
args?: string[];
- // The working directory to run the command in.
- cwd?: string;
- // The shell environment.
- env?: { [key: string]: string };
+ command: string;
};
+export type CargoTaskDefinition = {
+ env?: Record<string, string>;
+ type: typeof CARGO_TASK_TYPE;
+} & TaskDefinition;
+
+function isCargoTask(definition: vscode.TaskDefinition): definition is CargoTaskDefinition {
+ return definition.type === CARGO_TASK_TYPE;
+}
+
class RustTaskProvider implements vscode.TaskProvider {
private readonly config: Config;
@@ -58,13 +57,13 @@ class RustTaskProvider implements vscode.TaskProvider {
for (const workspaceTarget of vscode.workspace.workspaceFolders) {
for (const def of defs) {
const definition = {
- command: cargo,
- args: [def.command],
- };
- const exec = await targetToExecution(definition, this.config.cargoRunner);
+ command: def.command,
+ type: CARGO_TASK_TYPE,
+ } as const;
+ const exec = await targetToExecution(definition, {}, cargo);
const vscodeTask = await buildRustTask(
workspaceTarget,
- { ...definition, type: CARGO_TASK_TYPE },
+ definition,
`cargo ${def.command}`,
this.config.problemMatcher,
exec,
@@ -81,23 +80,13 @@ class RustTaskProvider implements vscode.TaskProvider {
// VSCode calls this for every cargo task in the user's tasks.json,
// we need to inform VSCode how to execute that command by creating
// a ShellExecution for it.
- if (task.definition.type === CARGO_TASK_TYPE) {
- const taskDefinition = task.definition as RustTargetDefinition;
- const cargo = await toolchain.cargoPath();
- const exec = await targetToExecution(
- {
- command: cargo,
- args: [taskDefinition.command].concat(taskDefinition.args || []),
- cwd: taskDefinition.cwd,
- env: taskDefinition.env,
- },
- this.config.cargoRunner,
- );
- return await buildRustTask(
+ if (isCargoTask(task.definition)) {
+ const exec = await targetToExecution(task.definition, { env: task.definition.env });
+ return buildRustTask(
task.scope,
- taskDefinition,
+ task.definition,
task.name,
- this.config.problemMatcher,
+ task.problemMatchers,
exec,
);
}
@@ -108,7 +97,7 @@ class RustTaskProvider implements vscode.TaskProvider {
export async function buildRustTask(
scope: vscode.WorkspaceFolder | vscode.TaskScope | undefined,
- definition: RustTargetDefinition,
+ definition: TaskDefinition,
name: string,
problemMatcher: string[],
exec: vscode.ProcessExecution | vscode.ShellExecution,
@@ -126,40 +115,23 @@ export async function buildRustTask(
}
export async function targetToExecution(
- definition: RustTarget,
- customRunner?: string,
- throwOnError: boolean = false,
+ definition: TaskDefinition,
+ options?: {
+ env?: { [key: string]: string };
+ cwd?: string;
+ },
+ cargo?: string,
): Promise<vscode.ProcessExecution | vscode.ShellExecution> {
- if (customRunner) {
- const runnerCommand = `${customRunner}.buildShellExecution`;
-
- try {
- const runnerArgs = {
- kind: CARGO_TASK_TYPE,
- args: definition.args,
- cwd: definition.cwd,
- env: definition.env,
- };
- const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs);
- if (customExec) {
- if (customExec instanceof vscode.ShellExecution) {
- return customExec;
- } else {
- log.debug("Invalid cargo ShellExecution", customExec);
- throw "Invalid cargo ShellExecution.";
- }
- }
- // fallback to default processing
- } catch (e) {
- if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`;
- // fallback to default processing
- }
+ let command, args;
+ if (isCargoTask(definition)) {
+ // FIXME: The server should provide cargo
+ command = cargo || (await toolchain.cargoPath());
+ args = [definition.command].concat(definition.args || []);
+ } else {
+ command = definition.command;
+ args = definition.args || [];
}
- const args = unwrapUndefinable(definition.args);
- return new vscode.ProcessExecution(definition.command, args, {
- cwd: definition.cwd,
- env: definition.env,
- });
+ return new vscode.ProcessExecution(command, args, options);
}
export function activateTaskProvider(config: Config): vscode.Disposable {
diff --git a/editors/code/tests/unit/settings.test.ts b/editors/code/tests/unit/settings.test.ts
index bafb9569a1..84501dde6c 100644
--- a/editors/code/tests/unit/settings.test.ts
+++ b/editors/code/tests/unit/settings.test.ts
@@ -13,7 +13,7 @@ export async function getTests(ctx: Context) {
USING_MY_VAR: "test test test",
MY_VAR: "test",
};
- const actualEnv = await substituteVariablesInEnv(envJson);
+ const actualEnv = substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv, expectedEnv);
});
@@ -34,7 +34,7 @@ export async function getTests(ctx: Context) {
E_IS_ISOLATED: "test",
F_USES_E: "test",
};
- const actualEnv = await substituteVariablesInEnv(envJson);
+ const actualEnv = substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv, expectedEnv);
});
@@ -47,7 +47,7 @@ export async function getTests(ctx: Context) {
USING_EXTERNAL_VAR: "test test test",
};
- const actualEnv = await substituteVariablesInEnv(envJson);
+ const actualEnv = substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv, expectedEnv);
delete process.env["TEST_VARIABLE"];
});
@@ -56,7 +56,7 @@ export async function getTests(ctx: Context) {
const envJson = {
USING_VSCODE_VAR: "${workspaceFolderBasename}",
};
- const actualEnv = await substituteVariablesInEnv(envJson);
+ const actualEnv = substituteVariablesInEnv(envJson);
assert.deepStrictEqual(actualEnv["USING_VSCODE_VAR"], "code");
});
});
diff --git a/editors/code/tests/unit/tasks.test.ts b/editors/code/tests/unit/tasks.test.ts
new file mode 100644
index 0000000000..9bccaaf3d4
--- /dev/null
+++ b/editors/code/tests/unit/tasks.test.ts
@@ -0,0 +1,139 @@
+import type { Context } from ".";
+import * as vscode from "vscode";
+import * as assert from "assert";
+import { targetToExecution } from "../../src/tasks";
+
+export async function getTests(ctx: Context) {
+ await ctx.suite("Tasks", (suite) => {
+ suite.addTest("cargo targetToExecution", async () => {
+ assert.deepStrictEqual(
+ await targetToExecution({
+ type: "cargo",
+ command: "check",
+ args: ["foo"],
+ }).then(executionToSimple),
+ {
+ process: "cargo",
+ args: ["check", "foo"],
+ },
+ );
+ });
+
+ suite.addTest("shell targetToExecution", async () => {
+ assert.deepStrictEqual(
+ await targetToExecution({
+ type: "shell",
+ command: "thing",
+ args: ["foo"],
+ }).then(executionToSimple),
+ {
+ process: "thing",
+ args: ["foo"],
+ },
+ );
+ });
+
+ suite.addTest("base tasks", async () => {
+ const tasks = await vscode.tasks.fetchTasks({ type: "cargo" });
+ const expectedTasks = [
+ {
+ definition: { type: "cargo", command: "build" },
+ name: "cargo build",
+ execution: {
+ process: "cargo",
+ args: ["build"],
+ },
+ },
+ {
+ definition: {
+ type: "cargo",
+ command: "check",
+ },
+ name: "cargo check",
+ execution: {
+ process: "cargo",
+ args: ["check"],
+ },
+ },
+ {
+ definition: { type: "cargo", command: "clippy" },
+ name: "cargo clippy",
+ execution: {
+ process: "cargo",
+ args: ["clippy"],
+ },
+ },
+ {
+ definition: { type: "cargo", command: "test" },
+ name: "cargo test",
+ execution: {
+ process: "cargo",
+ args: ["test"],
+ },
+ },
+ {
+ definition: {
+ type: "cargo",
+ command: "clean",
+ },
+ name: "cargo clean",
+ execution: {
+ process: "cargo",
+ args: ["clean"],
+ },
+ },
+ {
+ definition: { type: "cargo", command: "run" },
+ name: "cargo run",
+ execution: {
+ process: "cargo",
+ args: ["run"],
+ },
+ },
+ ];
+ tasks.map(f).forEach((actual, i) => {
+ const expected = expectedTasks[i];
+ assert.deepStrictEqual(actual, expected);
+ });
+ });
+ });
+}
+
+function f(task: vscode.Task): {
+ definition: vscode.TaskDefinition;
+ name: string;
+ execution: {
+ args: string[];
+ } & ({ command: string } | { process: string });
+} {
+ const execution = executionToSimple(task.execution!);
+
+ return {
+ definition: task.definition,
+ name: task.name,
+ execution,
+ };
+}
+function executionToSimple(
+ taskExecution: vscode.ProcessExecution | vscode.ShellExecution | vscode.CustomExecution,
+): {
+ args: string[];
+} & ({ command: string } | { process: string }) {
+ const exec = taskExecution as vscode.ProcessExecution | vscode.ShellExecution;
+ if (exec instanceof vscode.ShellExecution) {
+ return {
+ command: typeof exec.command === "string" ? exec.command : exec.command.value,
+ args: exec.args.map((arg) => {
+ if (typeof arg === "string") {
+ return arg;
+ }
+ return arg.value;
+ }),
+ };
+ } else {
+ return {
+ process: exec.process,
+ args: exec.args,
+ };
+ }
+}