Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runnable env #5202

Merged
merged 7 commits into from
Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,35 @@
"default": null,
"description": "Custom cargo runner extension ID."
},
"rust-analyzer.runnableEnv": {
"anyOf": [
{
"type": "null"
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"mask": {
"type": "string",
"description": "Runnable name mask"
},
"env": {
"type": "object",
"description": "Variables in form of { \"key\": \"value\"}"
}
}
}
},
{
"type": "object",
"description": "Variables in form of { \"key\": \"value\"}"
}
],
"default": null,
"description": "Environment variables passed to the runnable launched using `Test ` or `Debug` lens or `rust-analyzer.run` command."
},
"rust-analyzer.inlayHints.enable": {
"type": "boolean",
"default": true,
Expand Down
6 changes: 6 additions & 0 deletions editors/code/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type UpdatesChannel = "stable" | "nightly";

export const NIGHTLY_TAG = "nightly";

export type RunnableEnvCfg = undefined | Record<string, string> | { mask?: string; env: Record<string, string> }[];

export class Config {
readonly extensionId = "matklad.rust-analyzer";

Expand Down Expand Up @@ -114,6 +116,10 @@ export class Config {
return this.get<string | undefined>("cargoRunner");
}

get runnableEnv() {
return this.get<RunnableEnvCfg>("runnableEnv");
}

get debug() {
// "/rustc/<id>" used by suggestions only.
const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
Expand Down
16 changes: 10 additions & 6 deletions editors/code/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import * as ra from './lsp_ext';

import { Cargo } from './toolchain';
import { Ctx } from "./ctx";
import { prepareEnv } from "./run";

const debugOutput = vscode.window.createOutputChannel("Debug");
type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;

export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
const scope = ctx.activeRustEditor?.document.uri;
Expand Down Expand Up @@ -92,7 +93,8 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
}

const executable = await getDebugExecutable(runnable);
const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap);
const env = prepareEnv(runnable, ctx.config.runnableEnv);
const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, debugOptions.sourceFileMap);
if (debugConfig.type in debugOptions.engineSettings) {
const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
for (var key in settingsMap) {
Expand Down Expand Up @@ -121,7 +123,7 @@ async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
return executable;
}

function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
return {
type: "lldb",
request: "launch",
Expand All @@ -130,18 +132,20 @@ function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFil
args: runnable.args.executableArgs,
cwd: runnable.args.workspaceRoot,
sourceMap: sourceFileMap,
sourceLanguages: ["rust"]
sourceLanguages: ["rust"],
env
};
}

function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
return {
type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
request: "launch",
name: runnable.label,
program: executable,
args: runnable.args.executableArgs,
cwd: runnable.args.workspaceRoot,
sourceFileMap: sourceFileMap,
sourceFileMap,
env,
};
}
35 changes: 28 additions & 7 deletions editors/code/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as tasks from './tasks';

import { Ctx } from './ctx';
import { makeDebugConfig } from './debug';
import { Config } from './config';
import { Config, RunnableEnvCfg } from './config';

const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];

Expand Down Expand Up @@ -96,6 +96,30 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
}
}

export function prepareEnv(runnable: ra.Runnable, runnableEnvCfg: RunnableEnvCfg): Record<string, string> {
const env: Record<string, string> = { "RUST_BACKTRACE": "short" };

if (runnable.args.expectTest) {
env["UPDATE_EXPECT"] = "1";
}

Object.assign(env, process.env as { [key: string]: string });
vsrs marked this conversation as resolved.
Show resolved Hide resolved

if (runnableEnvCfg) {
if (Array.isArray(runnableEnvCfg)) {
for (const it of runnableEnvCfg) {
if (!it.mask || new RegExp(it.mask).test(runnable.label)) {
Object.assign(env, it.env);
}
}
} else {
Object.assign(env, runnableEnvCfg);
}
}

return env;
}

export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
if (runnable.kind !== "cargo") {
// rust-analyzer supports only one kind, "cargo"
Expand All @@ -108,16 +132,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
if (runnable.args.executableArgs.length > 0) {
args.push('--', ...runnable.args.executableArgs);
}
const env: { [key: string]: string } = { "RUST_BACKTRACE": "short" };
if (runnable.args.expectTest) {
env["UPDATE_EXPECT"] = "1";
}

const definition: tasks.CargoTaskDefinition = {
type: tasks.TASK_TYPE,
command: args[0], // run, test, etc...
args: args.slice(1),
cwd: runnable.args.workspaceRoot,
env: Object.assign({}, process.env as { [key: string]: string }, env),
cwd: runnable.args.workspaceRoot || ".",
env: prepareEnv(runnable, config.runnableEnv),
};

const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
Expand Down
118 changes: 118 additions & 0 deletions editors/code/tests/unit/runnable_env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as assert from 'assert';
import { prepareEnv } from '../../src/run';
import { RunnableEnvCfg } from '../../src/config';
import * as ra from '../../src/lsp_ext';

function makeRunnable(label: string): ra.Runnable {
return {
label,
kind: "cargo",
args: {
cargoArgs: [],
executableArgs: []
}
};
}

function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> {
const runnable = makeRunnable(runnableName);
return prepareEnv(runnable, config);
}

suite('Runnable env', () => {
test('Global config works', () => {
const binEnv = fakePrepareEnv("run project_name", { "GLOBAL": "g" });
assert.equal(binEnv["GLOBAL"], "g");

const testEnv = fakePrepareEnv("test some::mod::test_name", { "GLOBAL": "g" });
assert.equal(testEnv["GLOBAL"], "g");
});

test('null mask works', () => {
const config = [
{
env: { DATA: "data" }
}
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.equal(binEnv["DATA"], "data");

const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "data");
});

test('order works', () => {
const config = [
{
env: { DATA: "data" }
},
{
env: { DATA: "newdata" }
}
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.equal(binEnv["DATA"], "newdata");

const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "newdata");
});

test('mask works', () => {
const config = [
{
env: { DATA: "data" }
},
{
mask: "^run",
env: { DATA: "rundata" }
},
{
mask: "special_test$",
env: { DATA: "special_test" }
}
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.equal(binEnv["DATA"], "rundata");

const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "data");

const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config);
assert.equal(specialTestEnv["DATA"], "special_test");
});

test('exact test name works', () => {
const config = [
{
env: { DATA: "data" }
},
{
mask: "some::mod::test_name",
env: { DATA: "test special" }
}
];
const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "test special");

const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
assert.equal(specialTestEnv["DATA"], "data");
});

test('test mod name works', () => {
const config = [
{
env: { DATA: "data" }
},
{
mask: "some::mod",
env: { DATA: "mod special" }
}
];
const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "mod special");

const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
assert.equal(specialTestEnv["DATA"], "mod special");
});

});