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