diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 31d808eda4c35..cd450a614b3f7 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -88,6 +88,12 @@ "additionalProperties": { "type": "string" } + }, + "lldb-dap.serverMode": { + "scope": "resource", + "type": "boolean", + "markdownDescription": "Run lldb-dap in server mode.\n\nWhen enabled, lldb-dap will start a background server that will be reused between debug sessions. This allows caching of debug symbols between sessions and improves launch performance.", + "default": false } } }, @@ -543,4 +549,4 @@ } ] } -} +} \ No newline at end of file diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts index 36107336ebc4d..1f76fe31b00ad 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts @@ -4,6 +4,8 @@ import * as vscode from "vscode"; import * as child_process from "child_process"; import * as fs from "node:fs/promises"; +const exec = util.promisify(child_process.execFile); + export async function isExecutable(path: string): Promise { try { await fs.access(path, fs.constants.X_OK); @@ -16,7 +18,6 @@ export async function isExecutable(path: string): Promise { async function findWithXcrun(executable: string): Promise { if (process.platform === "darwin") { try { - const exec = util.promisify(child_process.execFile); let { stdout, stderr } = await exec("/usr/bin/xcrun", [ "-find", executable, @@ -24,7 +25,7 @@ async function findWithXcrun(executable: string): Promise { if (stdout) { return stdout.toString().trimEnd(); } - } catch (error) {} + } catch (error) { } } return undefined; } @@ -97,8 +98,15 @@ async function getDAPExecutable( * depending on the session configuration. */ export class LLDBDapDescriptorFactory - implements vscode.DebugAdapterDescriptorFactory -{ + implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable { + private server?: Promise<{ process: child_process.ChildProcess, host: string, port: number }>; + + dispose() { + this.server?.then(({ process }) => { + process.kill(); + }); + } + async createDebugAdapterDescriptor( session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined, @@ -115,7 +123,18 @@ export class LLDBDapDescriptorFactory } const configEnvironment = config.get<{ [key: string]: string }>("environment") || {}; - const dapPath = await getDAPExecutable(session); + const dapPath = (await getDAPExecutable(session)) ?? executable?.command; + + if (!dapPath) { + LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(); + return undefined; + } + + if (!(await isExecutable(dapPath))) { + LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath); + return; + } + const dbgOptions = { env: { ...executable?.options?.env, @@ -123,33 +142,52 @@ export class LLDBDapDescriptorFactory ...env, }, }; - if (dapPath) { - if (!(await isExecutable(dapPath))) { - LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath); - return undefined; - } - return new vscode.DebugAdapterExecutable(dapPath, [], dbgOptions); - } else if (executable) { - if (!(await isExecutable(executable.command))) { - LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(executable.command); - return undefined; - } - return new vscode.DebugAdapterExecutable( - executable.command, - executable.args, - dbgOptions, - ); + const dbgArgs = executable?.args ?? []; + + const serverMode = config.get('serverMode', false); + if (serverMode) { + const { host, port } = await this.startServer(dapPath, dbgArgs, dbgOptions); + return new vscode.DebugAdapterServer(port, host); } - return undefined; + + return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions); + } + + startServer(dapPath: string, args: string[], options: child_process.CommonSpawnOptions): Promise<{ host: string, port: number }> { + if (this.server) return this.server; + + this.server = new Promise(resolve => { + args.push( + '--connection', + 'connect://localhost:0' + ); + const server = child_process.spawn(dapPath, args, options); + server.stdout!.setEncoding('utf8').once('data', (data: string) => { + const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data); + if (connection) { + const host = connection[1]; + const port = Number(connection[2]); + resolve({ process: server, host, port }); + } + }); + server.on('exit', () => { + this.server = undefined; + }) + }); + return this.server; } /** * Shows a message box when the debug adapter's path is not found */ - static async showLLDBDapNotFoundMessage(path: string) { + static async showLLDBDapNotFoundMessage(path?: string) { + const message = + path + ? `Debug adapter path: ${path} is not a valid file.` + : "Unable to find the path to the LLDB debug adapter executable."; const openSettingsAction = "Open Settings"; const callbackValue = await vscode.window.showErrorMessage( - `Debug adapter path: ${path} is not a valid file`, + message, openSettingsAction, ); diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts b/lldb/tools/lldb-dap/src-ts/extension.ts index 71fd48298f8f5..a07bcdebcb68b 100644 --- a/lldb/tools/lldb-dap/src-ts/extension.ts +++ b/lldb/tools/lldb-dap/src-ts/extension.ts @@ -1,5 +1,3 @@ -import * as path from "path"; -import * as util from "util"; import * as vscode from "vscode"; import { @@ -15,13 +13,14 @@ import { DisposableContext } from "./disposable-context"; export class LLDBDapExtension extends DisposableContext { constructor() { super(); + const factory = new LLDBDapDescriptorFactory(); + this.pushSubscription(factory); this.pushSubscription( vscode.debug.registerDebugAdapterDescriptorFactory( "lldb-dap", - new LLDBDapDescriptorFactory(), - ), + factory, + ) ); - this.pushSubscription( vscode.workspace.onDidChangeConfiguration(async (event) => { if (event.affectsConfiguration("lldb-dap.executable-path")) {