1import * as path from "path"; 2import * as util from "util"; 3import * as vscode from "vscode"; 4import * as child_process from "child_process"; 5import * as fs from "node:fs/promises"; 6 7export async function isExecutable(path: string): Promise<Boolean> { 8 try { 9 await fs.access(path, fs.constants.X_OK); 10 } catch { 11 return false; 12 } 13 return true; 14} 15 16async function findWithXcrun(executable: string): Promise<string | undefined> { 17 if (process.platform === "darwin") { 18 try { 19 const exec = util.promisify(child_process.execFile); 20 let { stdout, stderr } = await exec("/usr/bin/xcrun", [ 21 "-find", 22 executable, 23 ]); 24 if (stdout) { 25 return stdout.toString().trimEnd(); 26 } 27 } catch (error) {} 28 } 29 return undefined; 30} 31 32async function findInPath(executable: string): Promise<string | undefined> { 33 const env_path = 34 process.platform === "win32" ? process.env["Path"] : process.env["PATH"]; 35 if (!env_path) { 36 return undefined; 37 } 38 39 const paths = env_path.split(path.delimiter); 40 for (const p of paths) { 41 const exe_path = path.join(p, executable); 42 if (await isExecutable(exe_path)) { 43 return exe_path; 44 } 45 } 46 return undefined; 47} 48 49async function findDAPExecutable(): Promise<string | undefined> { 50 const executable = process.platform === "win32" ? "lldb-dap.exe" : "lldb-dap"; 51 52 // Prefer lldb-dap from Xcode on Darwin. 53 const xcrun_dap = findWithXcrun(executable); 54 if (xcrun_dap) { 55 return xcrun_dap; 56 } 57 58 // Find lldb-dap in the user's path. 59 const path_dap = findInPath(executable); 60 if (path_dap) { 61 return path_dap; 62 } 63 64 return undefined; 65} 66 67async function getDAPExecutable( 68 session: vscode.DebugSession, 69): Promise<string | undefined> { 70 const config = vscode.workspace.getConfiguration( 71 "lldb-dap", 72 session.workspaceFolder, 73 ); 74 75 // Prefer the explicitly specified path in the extension's configuration. 76 const configPath = config.get<string>("executable-path"); 77 if (configPath && configPath.length !== 0) { 78 return configPath; 79 } 80 81 // Try finding the lldb-dap binary. 82 const foundPath = await findDAPExecutable(); 83 if (foundPath) { 84 return foundPath; 85 } 86 87 return undefined; 88} 89 90/** 91 * This class defines a factory used to find the lldb-dap binary to use 92 * depending on the session configuration. 93 */ 94export class LLDBDapDescriptorFactory 95 implements vscode.DebugAdapterDescriptorFactory 96{ 97 async createDebugAdapterDescriptor( 98 session: vscode.DebugSession, 99 executable: vscode.DebugAdapterExecutable | undefined, 100 ): Promise<vscode.DebugAdapterDescriptor | undefined> { 101 const config = vscode.workspace.getConfiguration( 102 "lldb-dap", 103 session.workspaceFolder, 104 ); 105 106 const log_path = config.get<string>("log-path"); 107 let env: { [key: string]: string } = {}; 108 if (log_path) { 109 env["LLDBDAP_LOG"] = log_path; 110 } 111 const configEnvironment = 112 config.get<{ [key: string]: string }>("environment") || {}; 113 const dapPath = await getDAPExecutable(session); 114 const dbgOptions = { 115 env: { 116 ...executable?.options?.env, 117 ...configEnvironment, 118 ...env, 119 }, 120 }; 121 if (dapPath) { 122 if (!(await isExecutable(dapPath))) { 123 LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath); 124 return undefined; 125 } 126 return new vscode.DebugAdapterExecutable(dapPath, [], dbgOptions); 127 } else if (executable) { 128 if (!(await isExecutable(executable.command))) { 129 LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(executable.command); 130 return undefined; 131 } 132 return new vscode.DebugAdapterExecutable( 133 executable.command, 134 executable.args, 135 dbgOptions, 136 ); 137 } 138 return undefined; 139 } 140 141 /** 142 * Shows a message box when the debug adapter's path is not found 143 */ 144 static async showLLDBDapNotFoundMessage(path: string) { 145 const openSettingsAction = "Open Settings"; 146 const callbackValue = await vscode.window.showErrorMessage( 147 `Debug adapter path: ${path} is not a valid file`, 148 openSettingsAction, 149 ); 150 151 if (openSettingsAction === callbackValue) { 152 vscode.commands.executeCommand( 153 "workbench.action.openSettings", 154 "lldb-dap.executable-path", 155 ); 156 } 157 } 158} 159