Skip to content

Commit 0d4f12e

Browse files
[lldb-dap] Allow providing debug adapter arguments in the extension (#129262)
Added a new setting called `lldb-dap.arguments` and a debug configuration attribute called `debugAdapterArgs` that can be used to set the arguments used to launch the debug adapter. Right now this is mostly useful for debugging purposes to add the `--wait-for-debugger` option to lldb-dap. Additionally, the extension will now check for a changed lldb-dap executable or arguments when launching a debug session in server mode. I had to add a new `DebugConfigurationProvider` to do this because VSCode will show an unhelpful error modal when the `DebugAdapterDescriptorFactory` returns `undefined`. In order to facilitate this, I had to add two new properties to the launch configuration that are used by the `DebugAdapterDescriptorFactory` to tell VS Code how to launch the debug adapter: - `debugAdapterHostname` - the hostname for an existing lldb-dap server - `debugAdapterPort` - the port for an existing lldb-dap server I've also removed the check for the `executable` argument in `LLDBDapDescriptorFactory.createDebugAdapterDescriptor()`. This argument is only set by VS Code when the debug adapter executable properties are set in the `package.json`. The LLDB DAP extension does not currently do this (and I don't think it ever will). So, this makes the debug adapter descriptor factory a little easier to read. The check for whether or not `lldb-dap` exists has been moved into the new `DebugConfigurationProvider` as well. This way the extension won't get in the user's way unless they actually try to start a debugging session. The error will show up as a modal which will also make it more obvious when something goes wrong, rather than popping up as a warning at the bottom right of the screen.
1 parent 8244f82 commit 0d4f12e

File tree

7 files changed

+578
-133
lines changed

7 files changed

+578
-133
lines changed

lldb/tools/lldb-dap/package.json

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@
7676
"type": "string",
7777
"description": "The path to the lldb-dap binary."
7878
},
79+
"lldb-dap.arguments": {
80+
"scope": "resource",
81+
"type": "array",
82+
"default": [],
83+
"items": {
84+
"type": "string"
85+
},
86+
"description": "The list of additional arguments used to launch the debug adapter executable."
87+
},
7988
"lldb-dap.log-path": {
8089
"scope": "resource",
8190
"type": "string",
@@ -149,19 +158,30 @@
149158
{
150159
"type": "lldb-dap",
151160
"label": "LLDB DAP Debugger",
152-
"program": "./bin/lldb-dap",
153-
"windows": {
154-
"program": "./bin/lldb-dap.exe"
155-
},
156161
"configurationAttributes": {
157162
"launch": {
158163
"required": [
159164
"program"
160165
],
161166
"properties": {
167+
"debugAdapterHostname": {
168+
"type": "string",
169+
"markdownDescription": "The hostname that an existing lldb-dap executable is listening on."
170+
},
171+
"debugAdapterPort": {
172+
"type": "number",
173+
"markdownDescription": "The port that an existing lldb-dap executable is listening on."
174+
},
162175
"debugAdapterExecutable": {
163176
"type": "string",
164-
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
177+
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use. Overrides any user or workspace settings."
178+
},
179+
"debugAdapterArgs": {
180+
"type": "array",
181+
"items": {
182+
"type": "string"
183+
},
184+
"markdownDescription": "The list of additional arguments used to launch the debug adapter executable. Overrides any user or workspace settings."
165185
},
166186
"program": {
167187
"type": "string",
@@ -349,9 +369,24 @@
349369
},
350370
"attach": {
351371
"properties": {
372+
"debugAdapterHostname": {
373+
"type": "string",
374+
"markdownDescription": "The hostname that an existing lldb-dap executable is listening on."
375+
},
376+
"debugAdapterPort": {
377+
"type": "number",
378+
"markdownDescription": "The port that an existing lldb-dap executable is listening on."
379+
},
352380
"debugAdapterExecutable": {
353381
"type": "string",
354-
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use."
382+
"markdownDescription": "The absolute path to the LLDB debug adapter executable to use. Overrides any user or workspace settings."
383+
},
384+
"debugAdapterArgs": {
385+
"type": "array",
386+
"items": {
387+
"type": "string"
388+
},
389+
"markdownDescription": "The list of additional arguments used to launch the debug adapter executable. Overrides any user or workspace settings."
355390
},
356391
"program": {
357392
"type": "string",

lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts

Lines changed: 120 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import * as util from "util";
33
import * as vscode from "vscode";
44
import * as child_process from "child_process";
55
import * as fs from "node:fs/promises";
6+
import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message";
7+
import { ErrorWithNotification } from "./ui/error-with-notification";
68

79
const exec = util.promisify(child_process.execFile);
810

9-
export async function isExecutable(path: string): Promise<Boolean> {
11+
async function isExecutable(path: string): Promise<Boolean> {
1012
try {
1113
await fs.access(path, fs.constants.X_OK);
1214
} catch {
@@ -25,7 +27,7 @@ async function findWithXcrun(executable: string): Promise<string | undefined> {
2527
if (stdout) {
2628
return stdout.toString().trimEnd();
2729
}
28-
} catch (error) { }
30+
} catch (error) {}
2931
}
3032
return undefined;
3133
}
@@ -65,142 +67,157 @@ async function findDAPExecutable(): Promise<string | undefined> {
6567
return undefined;
6668
}
6769

70+
/**
71+
* Retrieves the lldb-dap executable path either from settings or the provided
72+
* {@link vscode.DebugConfiguration}.
73+
*
74+
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
75+
* @param configuration The {@link vscode.DebugConfiguration} that will be launched
76+
* @throws An {@link ErrorWithNotification} if something went wrong
77+
* @returns The path to the lldb-dap executable
78+
*/
6879
async function getDAPExecutable(
69-
session: vscode.DebugSession,
70-
): Promise<string | undefined> {
80+
workspaceFolder: vscode.WorkspaceFolder | undefined,
81+
configuration: vscode.DebugConfiguration,
82+
): Promise<string> {
7183
// Check if the executable was provided in the launch configuration.
72-
const launchConfigPath = session.configuration["debugAdapterExecutable"];
84+
const launchConfigPath = configuration["debugAdapterExecutable"];
7385
if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) {
86+
if (!(await isExecutable(launchConfigPath))) {
87+
throw new ErrorWithNotification(
88+
`Debug adapter path "${launchConfigPath}" is not a valid file. The path comes from your launch configuration.`,
89+
new ConfigureButton(),
90+
);
91+
}
7492
return launchConfigPath;
7593
}
7694

7795
// Check if the executable was provided in the extension's configuration.
78-
const config = vscode.workspace.getConfiguration(
79-
"lldb-dap",
80-
session.workspaceFolder,
81-
);
96+
const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
8297
const configPath = config.get<string>("executable-path");
8398
if (configPath && configPath.length !== 0) {
99+
if (!(await isExecutable(configPath))) {
100+
throw new ErrorWithNotification(
101+
`Debug adapter path "${configPath}" is not a valid file. The path comes from your settings.`,
102+
new OpenSettingsButton("lldb-dap.executable-path"),
103+
);
104+
}
84105
return configPath;
85106
}
86107

87108
// Try finding the lldb-dap binary.
88109
const foundPath = await findDAPExecutable();
89110
if (foundPath) {
111+
if (!(await isExecutable(foundPath))) {
112+
throw new ErrorWithNotification(
113+
`Found a potential debug adapter on your system at "${configPath}", but it is not a valid file.`,
114+
new OpenSettingsButton("lldb-dap.executable-path"),
115+
);
116+
}
90117
return foundPath;
91118
}
92119

93-
return undefined;
120+
throw new ErrorWithNotification(
121+
"Unable to find the path to the LLDB debug adapter executable.",
122+
new OpenSettingsButton("lldb-dap.executable-path"),
123+
);
94124
}
95125

96-
async function isServerModeSupported(exe: string): Promise<boolean> {
97-
const { stdout } = await exec(exe, ['--help']);
98-
return /--connection/.test(stdout);
126+
/**
127+
* Retrieves the arguments that will be provided to lldb-dap either from settings or the provided
128+
* {@link vscode.DebugConfiguration}.
129+
*
130+
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
131+
* @param configuration The {@link vscode.DebugConfiguration} that will be launched
132+
* @throws An {@link ErrorWithNotification} if something went wrong
133+
* @returns The arguments that will be provided to lldb-dap
134+
*/
135+
async function getDAPArguments(
136+
workspaceFolder: vscode.WorkspaceFolder | undefined,
137+
configuration: vscode.DebugConfiguration,
138+
): Promise<string[]> {
139+
// Check the debug configuration for arguments first.
140+
const debugConfigArgs = configuration.debugAdapterArgs;
141+
if (debugConfigArgs) {
142+
if (
143+
!Array.isArray(debugConfigArgs) ||
144+
debugConfigArgs.findIndex((entry) => typeof entry !== "string") !== -1
145+
) {
146+
throw new ErrorWithNotification(
147+
"The debugAdapterArgs property must be an array of string values. Please update your launch configuration",
148+
new ConfigureButton(),
149+
);
150+
}
151+
return debugConfigArgs;
152+
}
153+
// Fall back on the workspace configuration.
154+
return vscode.workspace
155+
.getConfiguration("lldb-dap", workspaceFolder)
156+
.get<string[]>("arguments", []);
157+
}
158+
159+
/**
160+
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
161+
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
162+
*
163+
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
164+
* @param configuration The {@link vscode.DebugConfiguration} that will be launched
165+
* @throws An {@link ErrorWithNotification} if something went wrong
166+
* @returns The {@link vscode.DebugAdapterExecutable} that can be used to launch lldb-dap
167+
*/
168+
export async function createDebugAdapterExecutable(
169+
workspaceFolder: vscode.WorkspaceFolder | undefined,
170+
configuration: vscode.DebugConfiguration,
171+
): Promise<vscode.DebugAdapterExecutable> {
172+
const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
173+
const log_path = config.get<string>("log-path");
174+
let env: { [key: string]: string } = {};
175+
if (log_path) {
176+
env["LLDBDAP_LOG"] = log_path;
177+
}
178+
const configEnvironment =
179+
config.get<{ [key: string]: string }>("environment") || {};
180+
const dapPath = await getDAPExecutable(workspaceFolder, configuration);
181+
182+
const dbgOptions = {
183+
env: {
184+
...configEnvironment,
185+
...env,
186+
},
187+
};
188+
const dbgArgs = await getDAPArguments(workspaceFolder, configuration);
189+
190+
return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
99191
}
100192

101193
/**
102194
* This class defines a factory used to find the lldb-dap binary to use
103195
* depending on the session configuration.
104196
*/
105197
export class LLDBDapDescriptorFactory
106-
implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable {
107-
private server?: Promise<{ process: child_process.ChildProcess, host: string, port: number }>;
108-
109-
dispose() {
110-
this.server?.then(({ process }) => {
111-
process.kill();
112-
});
113-
}
114-
198+
implements vscode.DebugAdapterDescriptorFactory
199+
{
115200
async createDebugAdapterDescriptor(
116201
session: vscode.DebugSession,
117202
executable: vscode.DebugAdapterExecutable | undefined,
118203
): Promise<vscode.DebugAdapterDescriptor | undefined> {
119-
const config = vscode.workspace.getConfiguration(
120-
"lldb-dap",
121-
session.workspaceFolder,
122-
);
123-
124-
const log_path = config.get<string>("log-path");
125-
let env: { [key: string]: string } = {};
126-
if (log_path) {
127-
env["LLDBDAP_LOG"] = log_path;
128-
}
129-
const configEnvironment =
130-
config.get<{ [key: string]: string }>("environment") || {};
131-
const dapPath = (await getDAPExecutable(session)) ?? executable?.command;
132-
133-
if (!dapPath) {
134-
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage();
135-
return undefined;
136-
}
137-
138-
if (!(await isExecutable(dapPath))) {
139-
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
140-
return;
141-
}
142-
143-
const dbgOptions = {
144-
env: {
145-
...executable?.options?.env,
146-
...configEnvironment,
147-
...env,
148-
},
149-
};
150-
const dbgArgs = executable?.args ?? [];
151-
152-
const serverMode = config.get<boolean>('serverMode', false);
153-
if (serverMode && await isServerModeSupported(dapPath)) {
154-
const { host, port } = await this.startServer(dapPath, dbgArgs, dbgOptions);
155-
return new vscode.DebugAdapterServer(port, host);
204+
if (executable) {
205+
throw new Error(
206+
"Setting the debug adapter executable in the package.json is not supported.",
207+
);
156208
}
157209

158-
return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
159-
}
160-
161-
startServer(dapPath: string, args: string[], options: child_process.CommonSpawnOptions): Promise<{ host: string, port: number }> {
162-
if (this.server) return this.server;
163-
164-
this.server = new Promise(resolve => {
165-
args.push(
166-
'--connection',
167-
'connect://localhost:0'
210+
// Use a server connection if the debugAdapterPort is provided
211+
if (session.configuration.debugAdapterPort) {
212+
return new vscode.DebugAdapterServer(
213+
session.configuration.debugAdapterPort,
214+
session.configuration.debugAdapterHost,
168215
);
169-
const server = child_process.spawn(dapPath, args, options);
170-
server.stdout!.setEncoding('utf8').once('data', (data: string) => {
171-
const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
172-
if (connection) {
173-
const host = connection[1];
174-
const port = Number(connection[2]);
175-
resolve({ process: server, host, port });
176-
}
177-
});
178-
server.on('exit', () => {
179-
this.server = undefined;
180-
})
181-
});
182-
return this.server;
183-
}
216+
}
184217

185-
/**
186-
* Shows a message box when the debug adapter's path is not found
187-
*/
188-
static async showLLDBDapNotFoundMessage(path?: string) {
189-
const message =
190-
path
191-
? `Debug adapter path: ${path} is not a valid file.`
192-
: "Unable to find the path to the LLDB debug adapter executable.";
193-
const openSettingsAction = "Open Settings";
194-
const callbackValue = await vscode.window.showErrorMessage(
195-
message,
196-
openSettingsAction,
218+
return createDebugAdapterExecutable(
219+
session.workspaceFolder,
220+
session.configuration,
197221
);
198-
199-
if (openSettingsAction === callbackValue) {
200-
vscode.commands.executeCommand(
201-
"workbench.action.openSettings",
202-
"lldb-dap.executable-path",
203-
);
204-
}
205222
}
206223
}

0 commit comments

Comments
 (0)