]> git.lizzy.rs Git - rust.git/blob - editors/code/src/debug.ts
Use explicit rustc commit-hash
[rust.git] / editors / code / src / debug.ts
1 import * as os from "os";
2 import * as vscode from 'vscode';
3 import * as path from 'path';
4 import * as ra from './lsp_ext';
5
6 import { Cargo, getRustcId, getSysroot } from './toolchain';
7 import { Ctx } from "./ctx";
8 import { prepareEnv } from "./run";
9
10 const debugOutput = vscode.window.createOutputChannel("Debug");
11 type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
12
13 export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
14     const scope = ctx.activeRustEditor?.document.uri;
15     if (!scope) return;
16
17     const debugConfig = await getDebugConfiguration(ctx, runnable);
18     if (!debugConfig) return;
19
20     const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
21     const configurations = wsLaunchSection.get<any[]>("configurations") || [];
22
23     const index = configurations.findIndex(c => c.name === debugConfig.name);
24     if (index !== -1) {
25         const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
26         if (answer === "Cancel") return;
27
28         configurations[index] = debugConfig;
29     } else {
30         configurations.push(debugConfig);
31     }
32
33     await wsLaunchSection.update("configurations", configurations);
34 }
35
36 export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promise<boolean> {
37     let debugConfig: vscode.DebugConfiguration | undefined = undefined;
38     let message = "";
39
40     const wsLaunchSection = vscode.workspace.getConfiguration("launch");
41     const configurations = wsLaunchSection.get<any[]>("configurations") || [];
42
43     const index = configurations.findIndex(c => c.name === runnable.label);
44     if (-1 !== index) {
45         debugConfig = configurations[index];
46         message = " (from launch.json)";
47         debugOutput.clear();
48     } else {
49         debugConfig = await getDebugConfiguration(ctx, runnable);
50     }
51
52     if (!debugConfig) return false;
53
54     debugOutput.appendLine(`Launching debug configuration${message}:`);
55     debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
56     return vscode.debug.startDebugging(undefined, debugConfig);
57 }
58
59 async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<vscode.DebugConfiguration | undefined> {
60     const editor = ctx.activeRustEditor;
61     if (!editor) return;
62
63     const knownEngines: Record<string, DebugConfigProvider> = {
64         "vadimcn.vscode-lldb": getLldbDebugConfig,
65         "ms-vscode.cpptools": getCppvsDebugConfig
66     };
67     const debugOptions = ctx.config.debug;
68
69     let debugEngine = null;
70     if (debugOptions.engine === "auto") {
71         for (var engineId in knownEngines) {
72             debugEngine = vscode.extensions.getExtension(engineId);
73             if (debugEngine) break;
74         }
75     } else {
76         debugEngine = vscode.extensions.getExtension(debugOptions.engine);
77     }
78
79     if (!debugEngine) {
80         await vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
81             + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
82         return;
83     }
84
85     debugOutput.clear();
86     if (ctx.config.debug.openDebugPane) {
87         debugOutput.show(true);
88     }
89     // folder exists or RA is not active.
90     // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
91     const workspaceFolders = vscode.workspace.workspaceFolders!;
92     const isMultiFolderWorkspace = workspaceFolders.length > 1;
93     const firstWorkspace = workspaceFolders[0];
94     const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ?
95         firstWorkspace :
96         workspaceFolders.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace;
97
98     const wsFolder = path.normalize(workspace.uri.fsPath);
99     const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : '';
100     function simplifyPath(p: string): string {
101         // see https://github.com/rust-analyzer/rust-analyzer/pull/5513#issuecomment-663458818 for why this is needed
102         return path.normalize(p).replace(wsFolder, '${workspaceFolder' + workspaceQualifier + '}');
103     }
104
105     const executable = await getDebugExecutable(runnable);
106     const env = prepareEnv(runnable, ctx.config.runnableEnv);
107     let sourceFileMap = debugOptions.sourceFileMap;
108     if (sourceFileMap === "auto") {
109         // let's try to use the default toolchain
110         const commitHash = await getRustcId(wsFolder);
111         const sysroot = await getSysroot(wsFolder);
112         const rustlib = path.normalize(sysroot + "/lib/rustlib/src/rust");
113         sourceFileMap = {};
114         sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
115     }
116
117     const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, sourceFileMap);
118     if (debugConfig.type in debugOptions.engineSettings) {
119         const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
120         for (var key in settingsMap) {
121             debugConfig[key] = settingsMap[key];
122         }
123     }
124
125     if (debugConfig.name === "run binary") {
126         // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs,
127         // fn to_lsp_runnable(...) with RunnableKind::Bin
128         debugConfig.name = `run ${path.basename(executable)}`;
129     }
130
131     if (debugConfig.cwd) {
132         debugConfig.cwd = simplifyPath(debugConfig.cwd);
133     }
134
135     return debugConfig;
136 }
137
138 async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
139     const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput);
140     const executable = await cargo.executableFromArgs(runnable.args.cargoArgs);
141
142     // if we are here, there were no compilation errors.
143     return executable;
144 }
145
146 function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
147     return {
148         type: "lldb",
149         request: "launch",
150         name: runnable.label,
151         program: executable,
152         args: runnable.args.executableArgs,
153         cwd: runnable.args.workspaceRoot,
154         sourceMap: sourceFileMap,
155         sourceLanguages: ["rust"],
156         env
157     };
158 }
159
160 function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
161     return {
162         type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
163         request: "launch",
164         name: runnable.label,
165         program: executable,
166         args: runnable.args.executableArgs,
167         cwd: runnable.args.workspaceRoot,
168         sourceFileMap,
169         env,
170     };
171 }