Reorganize debug.ts

This commit is contained in:
Lukas Wirth 2024-08-02 15:39:05 +02:00
parent 670a5ab4a9
commit 7e94f3fd3c
3 changed files with 187 additions and 99 deletions

View file

@ -7,21 +7,15 @@ import { Cargo } from "./toolchain";
import type { Ctx } from "./ctx"; import type { Ctx } from "./ctx";
import { prepareEnv } from "./run"; import { prepareEnv } from "./run";
import { execute, isCargoRunnableArgs, unwrapUndefinable } from "./util"; import { execute, isCargoRunnableArgs, unwrapUndefinable } from "./util";
import type { Config } from "./config";
const debugOutput = vscode.window.createOutputChannel("Debug"); const debugOutput = vscode.window.createOutputChannel("Debug");
type DebugConfigProvider = (
runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs,
executable: string,
env: Record<string, string>,
sourceFileMap?: Record<string, string>,
) => vscode.DebugConfiguration;
export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> { export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
const scope = ctx.activeRustEditor?.document.uri; const scope = ctx.activeRustEditor?.document.uri;
if (!scope) return; if (!scope) return;
const debugConfig = await getDebugConfiguration(ctx, runnable); const debugConfig = await getDebugConfiguration(ctx.config, runnable, false);
if (!debugConfig) return; if (!debugConfig) return;
const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
@ -57,7 +51,7 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis
message = " (from launch.json)"; message = " (from launch.json)";
debugOutput.clear(); debugOutput.clear();
} else { } else {
debugConfig = await getDebugConfiguration(ctx, runnable); debugConfig = await getDebugConfiguration(ctx.config, runnable);
} }
if (!debugConfig) return false; if (!debugConfig) return false;
@ -74,35 +68,35 @@ function createCommandLink(extensionId: string): string {
} }
async function getDebugConfiguration( async function getDebugConfiguration(
ctx: Ctx, config: Config,
runnable: ra.Runnable, runnable: ra.Runnable,
inheritEnv: boolean = true,
): Promise<vscode.DebugConfiguration | undefined> { ): Promise<vscode.DebugConfiguration | undefined> {
if (!isCargoRunnableArgs(runnable.args)) { if (!isCargoRunnableArgs(runnable.args)) {
return; return;
} }
const runnableArgs: ra.CargoRunnableArgs = runnable.args; const runnableArgs: ra.CargoRunnableArgs = runnable.args;
const editor = ctx.activeRustEditor; const debugOptions = config.debug;
if (!editor) return;
const knownEngines: Record<string, DebugConfigProvider> = { let provider: null | KnownEnginesType = null;
"vadimcn.vscode-lldb": getCodeLldbDebugConfig,
"ms-vscode.cpptools": getCCppDebugConfig,
"webfreak.debug": getNativeDebugConfig,
};
const debugOptions = ctx.config.debug;
let debugEngine = null;
if (debugOptions.engine === "auto") { if (debugOptions.engine === "auto") {
for (var engineId in knownEngines) { for (const engineId in knownEngines) {
debugEngine = vscode.extensions.getExtension(engineId); const debugEngine = vscode.extensions.getExtension(engineId);
if (debugEngine) break; if (debugEngine) {
provider = knownEngines[engineId as keyof typeof knownEngines];
break;
}
} }
} else if (debugOptions.engine) { } else if (debugOptions.engine) {
debugEngine = vscode.extensions.getExtension(debugOptions.engine); const debugEngine = vscode.extensions.getExtension(debugOptions.engine);
if (debugEngine && Object.keys(knownEngines).includes(debugOptions.engine)) {
provider = knownEngines[debugOptions.engine as keyof typeof knownEngines];
}
} }
if (!debugEngine) { if (!provider) {
const commandCCpp: string = createCommandLink("ms-vscode.cpptools"); const commandCCpp: string = createCommandLink("ms-vscode.cpptools");
const commandCodeLLDB: string = createCommandLink("vadimcn.vscode-lldb"); const commandCodeLLDB: string = createCommandLink("vadimcn.vscode-lldb");
const commandNativeDebug: string = createCommandLink("webfreak.debug"); const commandNativeDebug: string = createCommandLink("webfreak.debug");
@ -116,7 +110,7 @@ async function getDebugConfiguration(
} }
debugOutput.clear(); debugOutput.clear();
if (ctx.config.debug.openDebugPane) { if (config.debug.openDebugPane) {
debugOutput.show(true); debugOutput.show(true);
} }
// folder exists or RA is not active. // folder exists or RA is not active.
@ -131,37 +125,36 @@ async function getDebugConfiguration(
firstWorkspace; firstWorkspace;
const workspace = unwrapUndefinable(maybeWorkspace); const workspace = unwrapUndefinable(maybeWorkspace);
const wsFolder = path.normalize(workspace.uri.fsPath); let wsFolder = path.normalize(workspace.uri.fsPath);
if (os.platform() === "win32") {
// in windows, the drive letter can vary in casing for VSCode, so we gotta normalize that first
wsFolder = wsFolder.replace(/^[a-z]:\\/, (c) => c.toUpperCase());
}
const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : ""; const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : "";
function simplifyPath(p: string): string { function simplifyPath(p: string): string {
// in windows, the drive letter can vary in casing for VSCode, so we gotta normalize that first
if (os.platform() === "win32") {
p = p.replace(/^[a-z]:\\/, (c) => c.toUpperCase());
}
// see https://github.com/rust-lang/rust-analyzer/pull/5513#issuecomment-663458818 for why this is needed // see https://github.com/rust-lang/rust-analyzer/pull/5513#issuecomment-663458818 for why this is needed
return path.normalize(p).replace(wsFolder, `\${workspaceFolder${workspaceQualifier}}`); return path.normalize(p).replace(wsFolder, `\${workspaceFolder${workspaceQualifier}}`);
} }
const env = prepareEnv(runnable.label, runnableArgs, ctx.config.runnablesExtraEnv); const env = prepareEnv(inheritEnv, runnable.label, runnableArgs, config.runnablesExtraEnv);
const executable = await getDebugExecutable(runnableArgs, env); const executable = await getDebugExecutable(runnableArgs, env);
let sourceFileMap = debugOptions.sourceFileMap; let sourceFileMap = debugOptions.sourceFileMap;
if (sourceFileMap === "auto") { if (sourceFileMap === "auto") {
sourceFileMap = {}; sourceFileMap = {};
const sysroot = env["RUSTC_TOOLCHAIN"]; await discoverSourceFileMap(sourceFileMap, env, wsFolder);
if (sysroot) {
// let's try to use the default toolchain
const data = await execute(`rustc -V -v`, { cwd: wsFolder, env });
const rx = /commit-hash:\s(.*)$/m;
const commitHash = rx.exec(data)?.[1];
if (commitHash) {
const rustlib = path.normalize(sysroot + "/lib/rustlib/src/rust");
sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
}
}
} }
const provider = unwrapUndefinable(knownEngines[debugEngine.id]); const debugConfig = getDebugConfig(
const debugConfig = provider( provider,
simplifyPath,
runnable, runnable,
runnableArgs, runnableArgs,
simplifyPath(executable), executable,
env, env,
sourceFileMap, sourceFileMap,
); );
@ -186,6 +179,92 @@ async function getDebugConfiguration(
return debugConfig; return debugConfig;
} }
async function discoverSourceFileMap(
sourceFileMap: Record<string, string>,
env: Record<string, string>,
cwd: string,
) {
const sysroot = env["RUSTC_TOOLCHAIN"];
if (sysroot) {
// let's try to use the default toolchain
const data = await execute(`rustc -V -v`, { cwd, env });
const rx = /commit-hash:\s(.*)$/m;
const commitHash = rx.exec(data)?.[1];
if (commitHash) {
const rustlib = path.normalize(sysroot + "/lib/rustlib/src/rust");
sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
}
}
}
type PropertyFetcher<Config, Input, Key extends keyof Config> = (
input: Input,
) => [Key, Config[Key]];
type DebugConfigProvider<Type extends string, DebugConfig extends BaseDebugConfig<Type>> = {
executableProperty: keyof DebugConfig;
environmentProperty: PropertyFetcher<DebugConfig, Record<string, string>, keyof DebugConfig>;
runnableArgsProperty: PropertyFetcher<DebugConfig, ra.CargoRunnableArgs, keyof DebugConfig>;
sourceFileMapProperty?: keyof DebugConfig;
type: Type;
additional?: Record<string, unknown>;
};
type KnownEnginesType = (typeof knownEngines)[keyof typeof knownEngines];
const knownEngines: {
"vadimcn.vscode-lldb": DebugConfigProvider<"lldb", CodeLldbDebugConfig>;
"ms-vscode.cpptools": DebugConfigProvider<"cppvsdbg" | "cppdbg", CCppDebugConfig>;
"webfreak.debug": DebugConfigProvider<"gdb", NativeDebugConfig>;
} = {
"vadimcn.vscode-lldb": {
type: "lldb",
executableProperty: "program",
environmentProperty: (env) => ["env", env],
runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [
"args",
runnableArgs.executableArgs,
],
sourceFileMapProperty: "sourceMap",
additional: {
sourceLanguages: ["rust"],
},
},
"ms-vscode.cpptools": {
type: os.platform() === "win32" ? "cppvsdbg" : "cppdbg",
executableProperty: "program",
environmentProperty: (env) => [
"environment",
Object.entries(env).map((entry) => ({
name: entry[0],
value: entry[1],
})),
],
runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [
"args",
runnableArgs.executableArgs,
],
sourceFileMapProperty: "sourceFileMap",
additional: {
osx: {
MIMode: "lldb",
},
},
},
"webfreak.debug": {
type: "gdb",
executableProperty: "target",
runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [
"arguments",
quote(runnableArgs.executableArgs),
],
environmentProperty: (env) => ["env", env],
additional: {
valuesFormatting: "prettyPrinters",
},
},
};
async function getDebugExecutable( async function getDebugExecutable(
runnableArgs: ra.CargoRunnableArgs, runnableArgs: ra.CargoRunnableArgs,
env: Record<string, string>, env: Record<string, string>,
@ -197,71 +276,74 @@ async function getDebugExecutable(
return executable; return executable;
} }
function getCCppDebugConfig( type BaseDebugConfig<type extends string> = {
type: type;
request: "launch";
name: string;
cwd: string;
};
function getDebugConfig(
provider: KnownEnginesType,
simplifyPath: (p: string) => string,
runnable: ra.Runnable, runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs, runnableArgs: ra.CargoRunnableArgs,
executable: string, executable: string,
env: Record<string, string>, env: Record<string, string>,
sourceFileMap?: Record<string, string>, sourceFileMap?: Record<string, string>,
): vscode.DebugConfiguration { ): vscode.DebugConfiguration {
const {
environmentProperty,
executableProperty,
runnableArgsProperty,
type,
additional,
sourceFileMapProperty,
} = provider;
const [envProperty, envValue] = environmentProperty(env);
const [argsProperty, argsValue] = runnableArgsProperty(runnableArgs);
return { return {
type: os.platform() === "win32" ? "cppvsdbg" : "cppdbg", type,
request: "launch", request: "launch",
name: runnable.label, name: runnable.label,
program: executable, cwd: simplifyPath(runnable.args.cwd || runnableArgs.workspaceRoot || "."),
args: runnableArgs.executableArgs, [executableProperty]: simplifyPath(executable),
cwd: runnable.args.cwd || runnableArgs.workspaceRoot || ".", [envProperty]: envValue,
sourceFileMap, [argsProperty]: argsValue,
environment: Object.entries(env).map((entry) => ({ ...(sourceFileMapProperty ? { [sourceFileMapProperty]: sourceFileMap } : {}),
name: entry[0], ...additional,
value: entry[1], };
})), }
type CCppDebugConfig = {
program: string;
args: string[];
sourceFileMap: Record<string, string> | undefined;
environment: {
name: string;
value: string;
}[];
// See https://github.com/rust-lang/rust-analyzer/issues/16901#issuecomment-2024486941 // See https://github.com/rust-lang/rust-analyzer/issues/16901#issuecomment-2024486941
osx: { osx: {
MIMode: "lldb", MIMode: "lldb";
},
}; };
} } & BaseDebugConfig<"cppvsdbg" | "cppdbg">;
function getCodeLldbDebugConfig( type CodeLldbDebugConfig = {
runnable: ra.Runnable, program: string;
runnableArgs: ra.CargoRunnableArgs, args: string[];
executable: string, sourceMap: Record<string, string> | undefined;
env: Record<string, string>, sourceLanguages: ["rust"];
sourceFileMap?: Record<string, string>, env: Record<string, string>;
): vscode.DebugConfiguration { } & BaseDebugConfig<"lldb">;
return {
type: "lldb",
request: "launch",
name: runnable.label,
program: executable,
args: runnableArgs.executableArgs,
cwd: runnable.args.cwd || runnableArgs.workspaceRoot || ".",
sourceMap: sourceFileMap,
sourceLanguages: ["rust"],
env,
};
}
function getNativeDebugConfig( type NativeDebugConfig = {
runnable: ra.Runnable, target: string;
runnableArgs: ra.CargoRunnableArgs,
executable: string,
env: Record<string, string>,
_sourceFileMap?: Record<string, string>,
): vscode.DebugConfiguration {
return {
type: "gdb",
request: "launch",
name: runnable.label,
target: executable,
// See https://github.com/WebFreak001/code-debug/issues/359 // See https://github.com/WebFreak001/code-debug/issues/359
arguments: quote(runnableArgs.executableArgs), arguments: string;
cwd: runnable.args.cwd || runnableArgs.workspaceRoot || ".", env: Record<string, string>;
env, valuesFormatting: "prettyPrinters";
valuesFormatting: "prettyPrinters", } & BaseDebugConfig<"gdb">;
};
}
// Based on https://github.com/ljharb/shell-quote/blob/main/quote.js // Based on https://github.com/ljharb/shell-quote/blob/main/quote.js
function quote(xs: string[]) { function quote(xs: string[]) {

View file

@ -65,9 +65,14 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
} }
} }
export function prepareBaseEnv(base?: Record<string, string>): Record<string, string> { export function prepareBaseEnv(
inheritEnv: boolean,
base?: Record<string, string>,
): Record<string, string> {
const env: Record<string, string> = { RUST_BACKTRACE: "short" }; const env: Record<string, string> = { RUST_BACKTRACE: "short" };
if (inheritEnv) {
Object.assign(env, process.env); Object.assign(env, process.env);
}
if (base) { if (base) {
Object.assign(env, base); Object.assign(env, base);
} }
@ -75,11 +80,12 @@ export function prepareBaseEnv(base?: Record<string, string>): Record<string, st
} }
export function prepareEnv( export function prepareEnv(
inheritEnv: boolean,
label: string, label: string,
runnableArgs: ra.CargoRunnableArgs, runnableArgs: ra.CargoRunnableArgs,
runnableEnvCfg?: RunnableEnvCfg, runnableEnvCfg?: RunnableEnvCfg,
): Record<string, string> { ): Record<string, string> {
const env = prepareBaseEnv(runnableArgs.environment); const env = prepareBaseEnv(inheritEnv, runnableArgs.environment);
const platform = process.platform; const platform = process.platform;
const checkPlatform = (it: RunnableEnvCfgItem) => { const checkPlatform = (it: RunnableEnvCfgItem) => {
@ -134,7 +140,7 @@ export async function createTaskFromRunnable(
}; };
options = { options = {
cwd: runnableArgs.workspaceRoot || ".", cwd: runnableArgs.workspaceRoot || ".",
env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv), env: prepareEnv(true, runnable.label, runnableArgs, config.runnablesExtraEnv),
}; };
} else { } else {
const runnableArgs = runnable.args; const runnableArgs = runnable.args;
@ -145,7 +151,7 @@ export async function createTaskFromRunnable(
}; };
options = { options = {
cwd: runnableArgs.cwd, cwd: runnableArgs.cwd,
env: prepareBaseEnv(), env: prepareBaseEnv(true),
}; };
} }

View file

@ -19,7 +19,7 @@ function makeRunnable(label: string): ra.Runnable {
function fakePrepareEnv(runnableName: string, config?: RunnableEnvCfg): Record<string, string> { function fakePrepareEnv(runnableName: string, config?: RunnableEnvCfg): Record<string, string> {
const runnable = makeRunnable(runnableName); const runnable = makeRunnable(runnableName);
const runnableArgs = runnable.args as ra.CargoRunnableArgs; const runnableArgs = runnable.args as ra.CargoRunnableArgs;
return prepareEnv(runnable.label, runnableArgs, config); return prepareEnv(false, runnable.label, runnableArgs, config);
} }
export async function getTests(ctx: Context) { export async function getTests(ctx: Context) {