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 = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; 12 13export 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 36export 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 59async 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 138async 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 146function 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 160function 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} 172