From 486fe1183fbfe7ad9537b1084d39721d3f9abf50 Mon Sep 17 00:00:00 2001 From: vsrs Date: Mon, 13 Jul 2020 20:23:29 +0300 Subject: [PATCH 1/5] Initial dynamic DCP inmplementation. --- editors/code/package-lock.json | 6 +- editors/code/package.json | 106 ++++++- editors/code/src/debug.ts | 310 +++++++++++++++---- editors/code/src/main.ts | 2 + editors/code/src/run.ts | 40 ++- editors/code/src/toolchain.ts | 13 +- editors/code/tests/unit/runnable_env.test.ts | 4 +- 7 files changed, 384 insertions(+), 97 deletions(-) diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 4f94c2dc5d63..cdae5a3699c3 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -143,9 +143,9 @@ } }, "@types/vscode": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.45.0.tgz", - "integrity": "sha512-b0Gyir7sPBCqiKLygAhn/AYVfzWD+SMPkWltBrIuPEyTOxSU1wVApWY/FcxYO2EWTRacoubTl4+gvZf86RkecA==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.47.0.tgz", + "integrity": "sha512-nJA37ykkz9FYA0ZOQUSc3OZnhuzEW2vUhUEo4MiduUo82jGwwcLfyvmgd/Q7b0WrZAAceojGhZybg319L24bTA==", "dev": true }, "@typescript-eslint/eslint-plugin": { diff --git a/editors/code/package.json b/editors/code/package.json index aac4ba94f270..8c849bc36f48 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -21,7 +21,7 @@ "Programming Languages" ], "engines": { - "vscode": "^1.44.1" + "vscode": "^1.46" }, "enableProposedApi": true, "scripts": { @@ -45,7 +45,7 @@ "@types/mocha": "^7.0.2", "@types/node": "~12.7.0", "@types/node-fetch": "^2.5.7", - "@types/vscode": "^1.44.1", + "@types/vscode": "^1.46", "@typescript-eslint/eslint-plugin": "^3.4.0", "@typescript-eslint/parser": "^3.4.0", "eslint": "^7.3.1", @@ -63,10 +63,112 @@ "onCommand:rust-analyzer.analyzerStatus", "onCommand:rust-analyzer.memoryUsage", "onCommand:rust-analyzer.reloadWorkspace", + "onDebugInitialConfigurations", + "onDebugDynamicConfigurations:rust", + "onDebugResolve:rust", "workspaceContains:**/Cargo.toml" ], "main": "./out/src/main", "contributes": { + "breakpoints": [ + { + "language": "rust" + } + ], + "debuggers": [ + { + "type": "rust", + "label": "Rust (preview)", + "languages": [ + "rust" + ], + "configurationAttributes": { + "launch": { + "properties": { + "oneOf": [ + { + "required": [ + "program" + ] + }, + { + "required": [ + "cargo" + ] + } + ], + "cargo": { + "command": { + "enum": [ + "run", + "test", + "bench" + ], + "default": "run" + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "env": { + "description": "Environment variables to add to the environment for the cargo only. Example: { \"VAR\": \"VALUE\" }.", + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + }, + "default": {} + }, + "required": [ + "command" + ] + }, + "program": { + "type": "string", + "description": "Full path to program executable." + }, + "args": { + "type": "array", + "description": "Command line arguments passed to the program. In case of `cargo` launch this is the arguments after `--`.", + "items": { + "type": "string" + }, + "default": [] + }, + "cwd": { + "type": "string", + "description": "The working directory of the target.", + "default": "${workspaceFolder}" + }, + "env": { + "description": "Environment variables to add to the environment for the program. Example: { \"VAR\": \"VALUE\" }.", + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + }, + "default": {} + }, + "envFile": { + "type": "string", + "description": "Absolute path to a file containing environment variable definitions. This file has JSON with key value pairs and optional mask.", + "default": "${workspaceFolder}/.vscode/env.json" + }, + "debugEngineSettings": { + "type": "object", + "default": {}, + "description": "Optional settings passed to the underlying debug engine. E.g. { \"cppvsdbg\": { \"logging\":{ \"moduleLoad\": false } } } }" + } + } + } + } + } + ], "taskDefinitions": [ { "type": "cargo", diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index bd92c5b6d739..cc27253545f1 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -5,16 +5,17 @@ import * as ra from './lsp_ext'; import { Cargo } from './toolchain'; import { Ctx } from "./ctx"; -import { prepareEnv } from "./run"; +import { currentRunnables, isDebuggable } from "./run"; +import { RunnableEnvCfg } from "./config"; const debugOutput = vscode.window.createOutputChannel("Debug"); -type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record, sourceFileMap?: Record) => vscode.DebugConfiguration; +type DebugConfigProvider = (config: ProxyDebugConfiguration, executable: string, env: Record, sourceFileMap?: Record) => vscode.DebugConfiguration; export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise { const scope = ctx.activeRustEditor?.document.uri; if (!scope) return; - const debugConfig = await getDebugConfiguration(ctx, runnable); + const debugConfig = proxyFromRunnable(runnable); if (!debugConfig) return; const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); @@ -33,7 +34,7 @@ export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise< await wsLaunchSection.update("configurations", configurations); } -export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promise { +export async function startDebugSession(_ctx: Ctx, runnable: ra.Runnable): Promise { let debugConfig: vscode.DebugConfiguration | undefined = undefined; let message = ""; @@ -46,7 +47,11 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis message = " (from launch.json)"; debugOutput.clear(); } else { - debugConfig = await getDebugConfiguration(ctx, runnable); + try { + debugConfig = proxyFromRunnable(runnable); + } catch (err) { + vscode.window.showErrorMessage(err); + } } if (!debugConfig) return false; @@ -56,96 +61,265 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis return vscode.debug.startDebugging(undefined, debugConfig); } -async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise { - const editor = ctx.activeRustEditor; - if (!editor) return; +function simplifyPath(p: string): string { + const wsFolder = path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. + return path.normalize(p).replace(wsFolder, '${workspaceRoot}'); +} - const knownEngines: Record = { - "vadimcn.vscode-lldb": getLldbDebugConfig, - "ms-vscode.cpptools": getCppvsDebugConfig - }; - const debugOptions = ctx.config.debug; +function expandPath(p: string): string { + const wsFolder = path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. + return p.replace('${workspaceRoot}', wsFolder); +} - let debugEngine = null; - if (debugOptions.engine === "auto") { - for (var engineId in knownEngines) { - debugEngine = vscode.extensions.getExtension(engineId); - if (debugEngine) break; - } - } else { - debugEngine = vscode.extensions.getExtension(debugOptions.engine); - } - if (!debugEngine) { - vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` - + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); - return; - } +// async function getDebugConfiguration(ctx: Ctx, proxyCfg: ProxyDebugConfiguration): Promise { +// const knownEngines: Record = { +// "vadimcn.vscode-lldb": getLldbDebugConfig, +// "ms-vscode.cpptools": getCppvsDebugConfig +// }; +// const debugOptions = ctx.config.debug; - debugOutput.clear(); - if (ctx.config.debug.openDebugPane) { - debugOutput.show(true); - } +// let debugEngine = null; +// if (debugOptions.engine === "auto") { +// for (var engineId in knownEngines) { +// debugEngine = vscode.extensions.getExtension(engineId); +// if (debugEngine) break; +// } +// } else { +// debugEngine = vscode.extensions.getExtension(debugOptions.engine); +// } - const wsFolder = path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. - function simplifyPath(p: string): string { - return path.normalize(p).replace(wsFolder, '${workspaceRoot}'); - } +// if (!debugEngine) { +// throw `Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` +// + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`; +// } - const executable = await getDebugExecutable(runnable); - 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) { - debugConfig[key] = settingsMap[key]; - } - } +// debugOutput.clear(); +// if (ctx.config.debug.openDebugPane) { +// debugOutput.show(true); +// } - if (debugConfig.name === "run binary") { - // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs, - // fn to_lsp_runnable(...) with RunnableKind::Bin - debugConfig.name = `run ${path.basename(executable)}`; - } +// const executable = await getDebugExecutable(runnable); +// 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) { +// debugConfig[key] = settingsMap[key]; +// } +// } - if (debugConfig.cwd) { - debugConfig.cwd = simplifyPath(debugConfig.cwd); - } +// if (debugConfig.name === "run binary") { +// // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs, +// // fn to_lsp_runnable(...) with RunnableKind::Bin +// debugConfig.name = `run ${path.basename(executable)}`; +// } - return debugConfig; -} +// if (debugConfig.cwd) { +// debugConfig.cwd = simplifyPath(debugConfig.cwd); +// } -async function getDebugExecutable(runnable: ra.Runnable): Promise { - const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput); - const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); +// return debugConfig; +// } - // if we are here, there were no compilation errors. - return executable; -} +// async function getDebugExecutable(runnable: ra.Runnable): Promise { +// const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput); +// const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); + +// // if we are here, there were no compilation errors. +// return executable; +// } -function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record, sourceFileMap?: Record): vscode.DebugConfiguration { +function getLldbDebugConfig(proxyCfg: ProxyDebugConfiguration, executable: string, env: Record, sourceFileMap?: Record): vscode.DebugConfiguration { return { type: "lldb", request: "launch", - name: runnable.label, + name: proxyCfg.name, program: executable, - args: runnable.args.executableArgs, - cwd: runnable.args.workspaceRoot, + args: proxyCfg.args, + cwd: proxyCfg.cwd, sourceMap: sourceFileMap, sourceLanguages: ["rust"], env }; } -function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record, sourceFileMap?: Record): vscode.DebugConfiguration { +function getCppvsDebugConfig(proxyCfg: ProxyDebugConfiguration, executable: string, env: Record, sourceFileMap?: Record): vscode.DebugConfiguration { return { type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", request: "launch", - name: runnable.label, + name: proxyCfg.label, program: executable, - args: runnable.args.executableArgs, - cwd: runnable.args.workspaceRoot, + args: proxyCfg.args, + cwd: proxyCfg.cwd, sourceFileMap, env, }; } + +// These interfaces should be in sync with pacakge.json debuggers : rust : configurationAttributes +type CargoCommand = "run" | "test" | "bench"; +interface CargoDebugConfiguration { + command: CargoCommand, + args?: string[], + env?: Record, + cwd?: string, +} +interface ProxyDebugConfiguration extends vscode.DebugConfiguration { + program?: string, + cargo?: CargoDebugConfiguration, + args?: string[], + cwd?: string, + env?: Record, + envFile?: string, + debugEngineSettings?: Record, +} + +function proxyFromRunnable(runnable: ra.Runnable): ProxyDebugConfiguration | undefined { + if (!isDebuggable(runnable)) return undefined; + + const proxyConfig: ProxyDebugConfiguration = { + type: "rust", + request: "launch", + name: runnable.label, + cargo: { + command: runnable.args.cargoArgs[0] as CargoCommand, + args: runnable.args.cargoArgs.slice(1), + }, + args: runnable.args.executableArgs, + cwd: runnable.args.workspaceRoot ? simplifyPath(runnable.args.workspaceRoot) : undefined, + }; + + return proxyConfig; +} + +class ProxyConfigurationProvider implements vscode.DebugConfigurationProvider { + constructor(readonly workspaceRoot: vscode.WorkspaceFolder, readonly context: Ctx) { } + + async provideDebugConfigurations?(_folder: vscode.WorkspaceFolder | undefined, _token?: vscode.CancellationToken): Promise { + const defaultTargets: ProxyDebugConfiguration[] = + [ + { + type: "rust", + request: "launch", + name: "Main binary", + cargo: { + command: "run" + } + }, + { + type: "rust", + request: "launch", + name: "Tests", + cargo: { + command: "test" + } + }, + ]; + + const runnables = await currentRunnables(this.context); + const targets = [...defaultTargets]; + runnables.forEach(it => { + const proxyConfig = proxyFromRunnable(it); + if (proxyConfig) { + targets.push(proxyConfig); + } + }); + + return targets; + } + + async resolveDebugConfiguration?(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken): Promise { + const proxyCfg = debugConfiguration as ProxyDebugConfiguration; + const cwd = expandPath(proxyCfg.cwd || folder?.uri.fsPath || vscode.workspace.workspaceFolders![0].uri.fsPath); + + const knownEngines: Record = { + "vadimcn.vscode-lldb": getLldbDebugConfig, + "ms-vscode.cpptools": getCppvsDebugConfig + }; + const debugOptions = this.context.config.debug; + + let debugEngine = null; + if (debugOptions.engine === "auto") { + for (var engineId in knownEngines) { + debugEngine = vscode.extensions.getExtension(engineId); + if (debugEngine) break; + } + } else { + debugEngine = vscode.extensions.getExtension(debugOptions.engine); + } + + if (!debugEngine) { + throw `Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` + + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`; + } + + debugOutput.clear(); + if (this.context.config.debug.openDebugPane) { + debugOutput.show(true); + } + + let executable: string; + if (proxyCfg.cargo) { + const cargoCwd = proxyCfg.cargo.cwd ? expandPath(proxyCfg.cargo.cwd) : cwd; + const cargo = new Cargo(cargoCwd, debugOutput); + const cargoArgs: string[] = [proxyCfg.cargo.command]; + if (proxyCfg.cargo.args) { + cargoArgs.push(...proxyCfg.cargo.args); + } + executable = await cargo.executableFromArgs(cargoArgs); + } else if (proxyCfg.program) { + executable = proxyCfg.program; + } else { + throw `Invalid rust debug configuration: ${proxyCfg.name}`; + } + + const env = prepareEnv(proxyCfg.name, proxyCfg.env, this.context.config.runnableEnv); + const debugConfig = knownEngines[debugEngine.id](proxyCfg, simplifyPath(executable), env, debugOptions.sourceFileMap); + if (debugConfig.type in debugOptions.engineSettings) { + const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; + for (var key in settingsMap) { + debugConfig[key] = settingsMap[key]; + } + } + + if (debugConfig.name === "run binary") { + // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs, + // fn to_lsp_runnable(...) with RunnableKind::Bin + debugConfig.name = `run ${path.basename(executable)}`; + } + + return debugConfig; + } + + async resolveDebugConfigurationWithSubstitutedVariables?(_folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken): Promise { + return debugConfiguration; + } +} + +export function prepareEnv(name: string, explicitEnv: Record | undefined, runnableEnvCfg: RunnableEnvCfg): Record { + const env = Object.assign({}, process.env as { [key: string]: string }); + + if (runnableEnvCfg) { + if (Array.isArray(runnableEnvCfg)) { + for (const it of runnableEnvCfg) { + if (!it.mask || new RegExp(it.mask).test(name)) { + Object.assign(env, it.env); + } + } + } else { + Object.assign(env, runnableEnvCfg); + } + } + + if (explicitEnv) Object.assign(env, explicitEnv); + + return env; +} + + +export function activateDebugConfigurationProvider(workspaceRoot: vscode.WorkspaceFolder, context: Ctx) { + const provider = new ProxyConfigurationProvider(workspaceRoot, context); + + context.pushCleanup(vscode.debug.registerDebugConfigurationProvider("rust", provider, vscode.DebugConfigurationProviderTriggerKind.Dynamic)); +} diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index bd99d696ad86..ce4739005420 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -13,6 +13,7 @@ import { fetchRelease, download } from './net'; import { activateTaskProvider } from './tasks'; import { setContextValue } from './util'; import { exec } from 'child_process'; +import { activateDebugConfigurationProvider } from './debug'; let ctx: Ctx | undefined; @@ -125,6 +126,7 @@ async function tryActivate(context: vscode.ExtensionContext) { ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config)); + activateDebugConfigurationProvider(workspaceFolder, ctx); activateInlayHints(ctx); vscode.workspace.onDidChangeConfiguration( diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index de68f27aec51..13bce383961d 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -4,15 +4,15 @@ import * as ra from './lsp_ext'; import * as tasks from './tasks'; import { Ctx } from './ctx'; -import { makeDebugConfig } from './debug'; +import { makeDebugConfig, prepareEnv } from './debug'; import { Config, RunnableEnvCfg } from './config'; const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; -export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise { +export async function currentRunnables(ctx: Ctx): Promise { const editor = ctx.activeRustEditor; const client = ctx.client; - if (!editor || !client) return; + if (!editor || !client) return []; const textDocument: lc.TextDocumentIdentifier = { uri: editor.document.uri.toString(), @@ -24,6 +24,18 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, editor.selection.active, ), }); + + return runnables; +} + +export function isDebuggable(runnable: ra.Runnable): boolean { + return !(runnable.label.startsWith('doctest') || runnable.label.startsWith('cargo')); +} + +export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise { + const runnables = await currentRunnables(ctx); + if (runnables.length == 0) return; + const items: RunnableQuickPick[] = []; if (prevRunnable) { items.push(prevRunnable); @@ -36,7 +48,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, continue; } - if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) { + if (debuggeeOnly && !isDebuggable(r) ) { continue; } items.push(new RunnableQuickPick(r)); @@ -96,27 +108,13 @@ export class RunnableQuickPick implements vscode.QuickPickItem { } } -export function prepareEnv(runnable: ra.Runnable, runnableEnvCfg: RunnableEnvCfg): Record { - const env: Record = { "RUST_BACKTRACE": "short" }; +export function prepareRunnableEnv(runnable: ra.Runnable, runnableEnvCfg: RunnableEnvCfg): Record { + const env = prepareEnv(runnable.label, { "RUST_BACKTRACE": "short" }, runnableEnvCfg); if (runnable.args.expectTest) { env["UPDATE_EXPECT"] = "1"; } - Object.assign(env, process.env as { [key: string]: string }); - - 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; } @@ -138,7 +136,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise command: args[0], // run, test, etc... args: args.slice(1), cwd: runnable.args.workspaceRoot || ".", - env: prepareEnv(runnable, config.runnableEnv), + env: prepareRunnableEnv(runnable, config.runnableEnv), }; const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index 80a7915e90e8..c852d77f0dae 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -84,7 +84,18 @@ export class Cargo { if (artifacts.length === 0) { throw new Error('No compilation artifacts'); } else if (artifacts.length > 1) { - throw new Error('Multiple compilation artifacts are not supported.'); + if (artifacts[0].isTest) { + const binaries = artifacts.filter(it => it.kind == "bin").map(it => it.name).join(', '); + throw new Error('Could not determine which test suite to run.\n' + + 'Use the `--bin` option to test the specified binary, or the `--lib` option to test the library.\n\n' + + `Available binaries: ${binaries}`); + + } else { + const binaries = artifacts.map(it => it.name).join(', '); + throw new Error('Could not determine which binary to run.\n' + + 'Use the `--bin` option to specify a binary, or the `default-run` manifest key.\n\n' + + `Available binaries: ${binaries}`); + } } return artifacts[0].fileName; diff --git a/editors/code/tests/unit/runnable_env.test.ts b/editors/code/tests/unit/runnable_env.test.ts index f2f53e91ad92..50ab2b20d4fb 100644 --- a/editors/code/tests/unit/runnable_env.test.ts +++ b/editors/code/tests/unit/runnable_env.test.ts @@ -1,5 +1,5 @@ import * as assert from 'assert'; -import { prepareEnv } from '../../src/run'; +import { prepareRunnableEnv } from '../../src/run'; import { RunnableEnvCfg } from '../../src/config'; import * as ra from '../../src/lsp_ext'; @@ -16,7 +16,7 @@ function makeRunnable(label: string): ra.Runnable { function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record { const runnable = makeRunnable(runnableName); - return prepareEnv(runnable, config); + return prepareRunnableEnv(runnable, config); } suite('Runnable env', () => { From fc6511104ee6219c2b47538a16fb37f8d228947d Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 14 Jul 2020 12:00:07 +0300 Subject: [PATCH 2/5] Add debug configuration snippets. --- editors/code/package.json | 125 +++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 2 deletions(-) diff --git a/editors/code/package.json b/editors/code/package.json index 8c849bc36f48..337da60ae1f2 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -21,7 +21,7 @@ "Programming Languages" ], "engines": { - "vscode": "^1.46" + "vscode": "^1.46.0" }, "enableProposedApi": true, "scripts": { @@ -98,6 +98,7 @@ } ], "cargo": { + "description": "Cargo options.", "command": { "enum": [ "run", @@ -166,7 +167,127 @@ } } } - } + }, + "configurationSnippets": [ + { + "label": "Rust: Main binary", + "body": { + "type": "rust", + "request": "launch", + "name": "${1:Run main binary}", + "cargo": { + "command": "run" + } + } + }, + { + "label": "Rust: All tests", + "body": { + "type": "rust", + "request": "launch", + "name": "${1:Run all tests}", + "cargo": { + "command": "test" + } + } + }, + { + "label": "Rust: Example", + "body": { + "type": "rust", + "request": "launch", + "name": "${2:Run example}", + "cargo": { + "command": "run", + "args": [ + "--example", + "${1:Example name}" + ] + } + } + }, + { + "label": "Rust: Specified binary", + "body": { + "type": "rust", + "request": "launch", + "name": "${2:Run binary}", + "cargo": { + "command": "run", + "args": [ + "--bin", + "${1:Binary name}" + ] + } + } + }, + { + "label": "Rust: Library unit tests", + "body": { + "type": "rust", + "request": "launch", + "name": "${1:Run unit tests}", + "cargo": { + "command": "test", + "args": [ + "--lib" + ] + } + } + }, + { + "label": "Rust: Specified binary tests", + "body": { + "type": "rust", + "request": "launch", + "name": "${2:Run binary tests}", + "cargo": { + "command": "test", + "args": [ + "--bin", + "${1:Binary name}" + ] + } + } + }, + { + "label": "Rust: Specified library unit test", + "body": { + "type": "rust", + "request": "launch", + "name": "${2:Run unit test}", + "cargo": { + "command": "test", + "args": [ + "--lib" + ] + }, + "args": [ + "${1:Test name}", + "--nocapture" + ] + } + }, + { + "label": "Rust: Specified integration test", + "body": { + "type": "rust", + "request": "launch", + "name": "${3:Run integration test}", + "cargo": { + "command": "test", + "args": [ + "--bin", + "${1:Binary name}" + ] + }, + "args": [ + "${2:Test name}", + "--nocapture" + ] + } + } + ] } ], "taskDefinitions": [ From f92e70fc7266dffe3ba96143066bf9bd6e991076 Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 14 Jul 2020 12:00:53 +0300 Subject: [PATCH 3/5] Code cleanup. --- editors/code/src/debug.ts | 215 ++++++++++++++-------------------- editors/code/src/toolchain.ts | 5 +- 2 files changed, 92 insertions(+), 128 deletions(-) diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index cc27253545f1..446fac9e67da 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -61,74 +61,19 @@ export async function startDebugSession(_ctx: Ctx, runnable: ra.Runnable): Promi return vscode.debug.startDebugging(undefined, debugConfig); } -function simplifyPath(p: string): string { - const wsFolder = path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. - return path.normalize(p).replace(wsFolder, '${workspaceRoot}'); +function workspaceRoot() : string { + return path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. } -function expandPath(p: string): string { - const wsFolder = path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. - return p.replace('${workspaceRoot}', wsFolder); +function simplifyPath(p: string): string { + return path.normalize(p).replace(workspaceRoot(), '${workspaceRoot}'); } +function expandPath(p?: string): string | undefined { + if (!p) return undefined; -// async function getDebugConfiguration(ctx: Ctx, proxyCfg: ProxyDebugConfiguration): Promise { -// const knownEngines: Record = { -// "vadimcn.vscode-lldb": getLldbDebugConfig, -// "ms-vscode.cpptools": getCppvsDebugConfig -// }; -// const debugOptions = ctx.config.debug; - -// let debugEngine = null; -// if (debugOptions.engine === "auto") { -// for (var engineId in knownEngines) { -// debugEngine = vscode.extensions.getExtension(engineId); -// if (debugEngine) break; -// } -// } else { -// debugEngine = vscode.extensions.getExtension(debugOptions.engine); -// } - -// if (!debugEngine) { -// throw `Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` -// + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`; -// } - -// debugOutput.clear(); -// if (ctx.config.debug.openDebugPane) { -// debugOutput.show(true); -// } - -// const executable = await getDebugExecutable(runnable); -// 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) { -// debugConfig[key] = settingsMap[key]; -// } -// } - -// if (debugConfig.name === "run binary") { -// // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs, -// // fn to_lsp_runnable(...) with RunnableKind::Bin -// debugConfig.name = `run ${path.basename(executable)}`; -// } - -// if (debugConfig.cwd) { -// debugConfig.cwd = simplifyPath(debugConfig.cwd); -// } - -// return debugConfig; -// } - -// async function getDebugExecutable(runnable: ra.Runnable): Promise { -// const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput); -// const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); - -// // if we are here, there were no compilation errors. -// return executable; -// } + return p.replace('${workspaceRoot}', workspaceRoot()); +} function getLldbDebugConfig(proxyCfg: ProxyDebugConfiguration, executable: string, env: Record, sourceFileMap?: Record): vscode.DebugConfiguration { return { @@ -187,38 +132,39 @@ function proxyFromRunnable(runnable: ra.Runnable): ProxyDebugConfiguration | und args: runnable.args.cargoArgs.slice(1), }, args: runnable.args.executableArgs, - cwd: runnable.args.workspaceRoot ? simplifyPath(runnable.args.workspaceRoot) : undefined, + cwd: runnable.args.workspaceRoot, }; return proxyConfig; } +const DEFAULT_TARGETS: ProxyDebugConfiguration[] = + [ + { + type: "rust", + request: "launch", + name: "Main binary", + cargo: { + command: "run" + } + }, + { + type: "rust", + request: "launch", + name: "Tests", + cargo: { + command: "test" + } + }, + ]; + class ProxyConfigurationProvider implements vscode.DebugConfigurationProvider { - constructor(readonly workspaceRoot: vscode.WorkspaceFolder, readonly context: Ctx) { } - async provideDebugConfigurations?(_folder: vscode.WorkspaceFolder | undefined, _token?: vscode.CancellationToken): Promise { - const defaultTargets: ProxyDebugConfiguration[] = - [ - { - type: "rust", - request: "launch", - name: "Main binary", - cargo: { - command: "run" - } - }, - { - type: "rust", - request: "launch", - name: "Tests", - cargo: { - command: "test" - } - }, - ]; + constructor(readonly workspaceRoot: vscode.WorkspaceFolder, readonly context: Ctx) { } + async provideDebugConfigurations(_folder: vscode.WorkspaceFolder | undefined, _token?: vscode.CancellationToken): Promise { const runnables = await currentRunnables(this.context); - const targets = [...defaultTargets]; + const targets = [...DEFAULT_TARGETS]; runnables.forEach(it => { const proxyConfig = proxyFromRunnable(it); if (proxyConfig) { @@ -229,71 +175,88 @@ class ProxyConfigurationProvider implements vscode.DebugConfigurationProvider { return targets; } - async resolveDebugConfiguration?(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken): Promise { - const proxyCfg = debugConfiguration as ProxyDebugConfiguration; - const cwd = expandPath(proxyCfg.cwd || folder?.uri.fsPath || vscode.workspace.workspaceFolders![0].uri.fsPath); + async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken): Promise { + // debugConfiguration is {} if the user clicks Run\Debug button on the Run panel and there is no launch.json. + const proxyCfg = Object.keys(debugConfiguration).length == 0 ? DEFAULT_TARGETS[0] : debugConfiguration as ProxyDebugConfiguration; + const configProvider = this.selectDebugConfigProvider(); - const knownEngines: Record = { - "vadimcn.vscode-lldb": getLldbDebugConfig, - "ms-vscode.cpptools": getCppvsDebugConfig - }; + debugOutput.clear(); + if (this.context.config.debug.openDebugPane) { + debugOutput.show(true); + } + + const cwd = expandPath(proxyCfg.cwd) || folder?.uri.fsPath || workspaceRoot(); + const executable = await this.getExecutable(proxyCfg, cwd); + const env = prepareEnv(proxyCfg.name, proxyCfg.env, this.context.config.runnableEnv); const debugOptions = this.context.config.debug; + const debugConfig = configProvider(proxyCfg, simplifyPath(executable), env, debugOptions.sourceFileMap); - let debugEngine = null; - if (debugOptions.engine === "auto") { - for (var engineId in knownEngines) { - debugEngine = vscode.extensions.getExtension(engineId); - if (debugEngine) break; + const customEngineSettings = proxyCfg.debugEngineSettings ?? debugOptions.engineSettings; + if (debugConfig.type in customEngineSettings) { + const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; + for (var key in settingsMap) { + debugConfig[key] = settingsMap[key]; } - } else { - debugEngine = vscode.extensions.getExtension(debugOptions.engine); } - if (!debugEngine) { - throw `Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` - + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`; + if (debugConfig.name === "run binary") { + // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs, + // fn to_lsp_runnable(...) with RunnableKind::Bin + debugConfig.name = `run ${path.basename(executable)}`; } - debugOutput.clear(); - if (this.context.config.debug.openDebugPane) { - debugOutput.show(true); - } + return debugConfig; + } + private async getExecutable(proxyCfg: ProxyDebugConfiguration, cwd: string): Promise { let executable: string; if (proxyCfg.cargo) { - const cargoCwd = proxyCfg.cargo.cwd ? expandPath(proxyCfg.cargo.cwd) : cwd; - const cargo = new Cargo(cargoCwd, debugOutput); + const cargoCwd = expandPath(proxyCfg.cargo.cwd) || cwd; + const cargo = new Cargo(cargoCwd, proxyCfg.cargo.env, debugOutput); const cargoArgs: string[] = [proxyCfg.cargo.command]; if (proxyCfg.cargo.args) { cargoArgs.push(...proxyCfg.cargo.args); } executable = await cargo.executableFromArgs(cargoArgs); - } else if (proxyCfg.program) { + } + else if (proxyCfg.program) { executable = proxyCfg.program; - } else { + } + else { throw `Invalid rust debug configuration: ${proxyCfg.name}`; } + return executable; + } - const env = prepareEnv(proxyCfg.name, proxyCfg.env, this.context.config.runnableEnv); - const debugConfig = knownEngines[debugEngine.id](proxyCfg, simplifyPath(executable), env, debugOptions.sourceFileMap); - if (debugConfig.type in debugOptions.engineSettings) { - const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; - for (var key in settingsMap) { - debugConfig[key] = settingsMap[key]; + async resolveDebugConfigurationWithSubstitutedVariables?(_folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken): Promise { + return debugConfiguration; + } + + private selectDebugConfigProvider(): DebugConfigProvider { + const knownEngines: Record = { + "vadimcn.vscode-lldb": getLldbDebugConfig, + "ms-vscode.cpptools": getCppvsDebugConfig + }; + const debugOptions = this.context.config.debug; + + let debugEngine = null; + if (debugOptions.engine === "auto") { + for (var engineId in knownEngines) { + debugEngine = vscode.extensions.getExtension(engineId); + if (debugEngine) break; } + } else { + debugEngine = vscode.extensions.getExtension(debugOptions.engine); } - if (debugConfig.name === "run binary") { - // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs, - // fn to_lsp_runnable(...) with RunnableKind::Bin - debugConfig.name = `run ${path.basename(executable)}`; - } + if (!debugEngine) { + vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` + + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); - return debugConfig; - } + throw "No debug engine!"; + } - async resolveDebugConfigurationWithSubstitutedVariables?(_folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken): Promise { - return debugConfiguration; + return knownEngines[debugEngine.id]; } } diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index c852d77f0dae..1fef98072c94 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -19,7 +19,7 @@ export interface ArtifactSpec { } export class Cargo { - constructor(readonly rootFolder: string, readonly output: OutputChannel) { } + constructor(readonly rootFolder: string, readonly env: Record | undefined, readonly output: OutputChannel) { } // Made public for testing purposes static artifactSpec(args: readonly string[]): ArtifactSpec { @@ -109,7 +109,8 @@ export class Cargo { return new Promise((resolve, reject) => { const cargo = cp.spawn(cargoPath(), cargoArgs, { stdio: ['ignore', 'pipe', 'pipe'], - cwd: this.rootFolder + cwd: this.rootFolder, + env: this.env }); cargo.on('error', err => reject(new Error(`could not launch cargo: ${err}`))); From f5eb7d20e2c6020796298511c0db66552266d14e Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 14 Jul 2020 13:17:34 +0300 Subject: [PATCH 4/5] Add cwd simplification to the proxyFromRunnable. --- editors/code/src/debug.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 446fac9e67da..11b7d16d1750 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -65,7 +65,9 @@ function workspaceRoot() : string { return path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. } -function simplifyPath(p: string): string { +function simplifyPath(p?: string): string | undefined { + if (!p) return undefined; + return path.normalize(p).replace(workspaceRoot(), '${workspaceRoot}'); } @@ -132,7 +134,7 @@ function proxyFromRunnable(runnable: ra.Runnable): ProxyDebugConfiguration | und args: runnable.args.cargoArgs.slice(1), }, args: runnable.args.executableArgs, - cwd: runnable.args.workspaceRoot, + cwd: simplifyPath(runnable.args.workspaceRoot), }; return proxyConfig; @@ -189,7 +191,7 @@ class ProxyConfigurationProvider implements vscode.DebugConfigurationProvider { const executable = await this.getExecutable(proxyCfg, cwd); const env = prepareEnv(proxyCfg.name, proxyCfg.env, this.context.config.runnableEnv); const debugOptions = this.context.config.debug; - const debugConfig = configProvider(proxyCfg, simplifyPath(executable), env, debugOptions.sourceFileMap); + const debugConfig = configProvider(proxyCfg, simplifyPath(executable)!, env, debugOptions.sourceFileMap); const customEngineSettings = proxyCfg.debugEngineSettings ?? debugOptions.engineSettings; if (debugConfig.type in customEngineSettings) { From 1f39191cbd3cd653814309052020d9aa209b4a0d Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 14 Jul 2020 14:10:05 +0300 Subject: [PATCH 5/5] Fix lints --- editors/code/src/debug.ts | 26 +++++++++++++------------- editors/code/src/run.ts | 4 ++-- editors/code/src/toolchain.ts | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 11b7d16d1750..c0e08822bfe9 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -61,7 +61,7 @@ export async function startDebugSession(_ctx: Ctx, runnable: ra.Runnable): Promi return vscode.debug.startDebugging(undefined, debugConfig); } -function workspaceRoot() : string { +function workspaceRoot(): string { return path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active. } @@ -107,19 +107,19 @@ function getCppvsDebugConfig(proxyCfg: ProxyDebugConfiguration, executable: stri // These interfaces should be in sync with pacakge.json debuggers : rust : configurationAttributes type CargoCommand = "run" | "test" | "bench"; interface CargoDebugConfiguration { - command: CargoCommand, - args?: string[], - env?: Record, - cwd?: string, + command: CargoCommand; + args?: string[]; + env?: Record; + cwd?: string; } interface ProxyDebugConfiguration extends vscode.DebugConfiguration { - program?: string, - cargo?: CargoDebugConfiguration, - args?: string[], - cwd?: string, - env?: Record, - envFile?: string, - debugEngineSettings?: Record, + program?: string; + cargo?: CargoDebugConfiguration; + args?: string[]; + cwd?: string; + env?: Record; + envFile?: string; + debugEngineSettings?: Record; } function proxyFromRunnable(runnable: ra.Runnable): ProxyDebugConfiguration | undefined { @@ -179,7 +179,7 @@ class ProxyConfigurationProvider implements vscode.DebugConfigurationProvider { async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken): Promise { // debugConfiguration is {} if the user clicks Run\Debug button on the Run panel and there is no launch.json. - const proxyCfg = Object.keys(debugConfiguration).length == 0 ? DEFAULT_TARGETS[0] : debugConfiguration as ProxyDebugConfiguration; + const proxyCfg = Object.keys(debugConfiguration).length === 0 ? DEFAULT_TARGETS[0] : debugConfiguration as ProxyDebugConfiguration; const configProvider = this.selectDebugConfigProvider(); debugOutput.clear(); diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 13bce383961d..9e819f11e864 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -34,7 +34,7 @@ export function isDebuggable(runnable: ra.Runnable): boolean { export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise { const runnables = await currentRunnables(ctx); - if (runnables.length == 0) return; + if (runnables.length === 0) return; const items: RunnableQuickPick[] = []; if (prevRunnable) { @@ -48,7 +48,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, continue; } - if (debuggeeOnly && !isDebuggable(r) ) { + if (debuggeeOnly && !isDebuggable(r)) { continue; } items.push(new RunnableQuickPick(r)); diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index 1fef98072c94..1878ca1ad84b 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -19,7 +19,7 @@ export interface ArtifactSpec { } export class Cargo { - constructor(readonly rootFolder: string, readonly env: Record | undefined, readonly output: OutputChannel) { } + constructor(readonly rootFolder: string, readonly env: Record | undefined, readonly output: OutputChannel) { } // Made public for testing purposes static artifactSpec(args: readonly string[]): ArtifactSpec { @@ -85,7 +85,7 @@ export class Cargo { throw new Error('No compilation artifacts'); } else if (artifacts.length > 1) { if (artifacts[0].isTest) { - const binaries = artifacts.filter(it => it.kind == "bin").map(it => it.name).join(', '); + const binaries = artifacts.filter(it => it.kind === "bin").map(it => it.name).join(', '); throw new Error('Could not determine which test suite to run.\n' + 'Use the `--bin` option to test the specified binary, or the `--lib` option to test the library.\n\n' + `Available binaries: ${binaries}`);