Skip to content

Commit 20b8664

Browse files
authored
[vscode-lldb] Support lldb-dap environment in debug configuration (#153536)
# Changes 1. Add a new debug configuration field called `debugAdapterEnv`. It accepts a string-valued dictionary or array. 1. This input format is consistent with the (program's) `env` debug configuration. 2. In the adapter descriptor factory, honor the said field before looking at the VS Code settings `Lldb-dap: Environment `. 1. This order is consistent with how things are done for other debug configuration fields, e.g. lldb-dap path and args. 3. In the lldb-dap server, note down the environment entries as a part of the adapter spawn info (now it becomes "path + args + env"), and prompt the user to restart server if such info has changed. # Motivation 1. Adapter environment can be set in `launch.json`. 2. Other debugger extensions can invoke the lldb-dap extension with adapter environment (via debug configuration). # Tests See PR #153536.
1 parent 826780a commit 20b8664

File tree

3 files changed

+132
-8
lines changed

3 files changed

+132
-8
lines changed

lldb/tools/lldb-dap/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,29 @@
398398
},
399399
"markdownDescription": "The list of additional arguments used to launch the debug adapter executable. Overrides any user or workspace settings."
400400
},
401+
"debugAdapterEnv": {
402+
"anyOf": [
403+
{
404+
"type": "object",
405+
"markdownDescription": "Additional environment variables to set when launching the debug adapter executable. E.g. `{ \"FOO\": \"1\" }`",
406+
"patternProperties": {
407+
".*": {
408+
"type": "string"
409+
}
410+
},
411+
"default": {}
412+
},
413+
{
414+
"type": "array",
415+
"markdownDescription": "Additional environment variables to set when launching the debug adapter executable. E.g. `[\"FOO=1\", \"BAR\"]`",
416+
"items": {
417+
"type": "string",
418+
"pattern": "^((\\w+=.*)|^\\w+)$"
419+
},
420+
"default": []
421+
}
422+
]
423+
},
401424
"program": {
402425
"type": "string",
403426
"description": "Path to the program to debug."

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

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,40 @@ async function findDAPExecutable(): Promise<string | undefined> {
6868
return undefined;
6969
}
7070

71+
/**
72+
* Validates the DAP environment provided in the debug configuration.
73+
* It must be a dictionary of string keys and values OR an array of string values.
74+
*
75+
* @param debugConfigEnv The supposed DAP environment that will be validated
76+
* @returns Whether or not the DAP environment is valid
77+
*/
78+
function validateDAPEnv(debugConfigEnv: any): boolean {
79+
// If the env is an object, it should have string values.
80+
// The keys are guaranteed to be strings.
81+
if (
82+
typeof debugConfigEnv === "object" &&
83+
Object.values(debugConfigEnv).findIndex(
84+
(entry) => typeof entry !== "string",
85+
) !== -1
86+
) {
87+
return false;
88+
}
89+
90+
// If the env is an array, it should have string values which match the regex.
91+
if (
92+
Array.isArray(debugConfigEnv) &&
93+
debugConfigEnv.findIndex(
94+
(entry) =>
95+
typeof entry !== "string" || !/^((\\w+=.*)|^\\w+)$/.test(entry),
96+
) !== -1
97+
) {
98+
return false;
99+
}
100+
101+
// The env is valid.
102+
return true;
103+
}
104+
71105
/**
72106
* Retrieves the lldb-dap executable path either from settings or the provided
73107
* {@link vscode.DebugConfiguration}.
@@ -157,6 +191,51 @@ async function getDAPArguments(
157191
.get<string[]>("arguments", []);
158192
}
159193

194+
/**
195+
* Retrieves the environment that will be provided to lldb-dap either from settings or the provided
196+
* {@link vscode.DebugConfiguration}.
197+
*
198+
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
199+
* @param configuration The {@link vscode.DebugConfiguration} that will be launched
200+
* @throws An {@link ErrorWithNotification} if something went wrong
201+
* @returns The environment that will be provided to lldb-dap
202+
*/
203+
async function getDAPEnvironment(
204+
workspaceFolder: vscode.WorkspaceFolder | undefined,
205+
configuration: vscode.DebugConfiguration,
206+
): Promise<{ [key: string]: string }> {
207+
const debugConfigEnv = configuration.debugAdapterEnv;
208+
if (debugConfigEnv) {
209+
if (validateDAPEnv(debugConfigEnv) === false) {
210+
throw new ErrorWithNotification(
211+
"The debugAdapterEnv property must be a dictionary of string keys and values OR an array of string values. Please update your launch configuration",
212+
new ConfigureButton(),
213+
);
214+
}
215+
216+
// Transform, so that the returned value is always a dictionary.
217+
if (Array.isArray(debugConfigEnv)) {
218+
const ret: { [key: string]: string } = {};
219+
for (const envVar of debugConfigEnv as string[]) {
220+
const equalSignPos = envVar.search("=");
221+
if (equalSignPos >= 0) {
222+
ret[envVar.substr(0, equalSignPos)] = envVar.substr(equalSignPos + 1);
223+
} else {
224+
ret[envVar] = "";
225+
}
226+
}
227+
return ret;
228+
} else {
229+
return debugConfigEnv;
230+
}
231+
}
232+
233+
const config = vscode.workspace.workspaceFile
234+
? vscode.workspace.getConfiguration("lldb-dap")
235+
: vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
236+
return config.get<{ [key: string]: string }>("environment") || {};
237+
}
238+
160239
/**
161240
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
162241
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
@@ -182,12 +261,16 @@ export async function createDebugAdapterExecutable(
182261
if (log_path) {
183262
env["LLDBDAP_LOG"] = log_path;
184263
} else if (
185-
vscode.workspace.getConfiguration("lldb-dap").get("captureSessionLogs", false)
264+
vscode.workspace
265+
.getConfiguration("lldb-dap")
266+
.get("captureSessionLogs", false)
186267
) {
187268
env["LLDBDAP_LOG"] = logFilePath.get(LogType.DEBUG_SESSION);
188269
}
189-
const configEnvironment =
190-
config.get<{ [key: string]: string }>("environment") || {};
270+
const configEnvironment = await getDAPEnvironment(
271+
workspaceFolder,
272+
configuration,
273+
);
191274
const dapPath = await getDAPExecutable(workspaceFolder, configuration);
192275

193276
const dbgOptions = {

lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as vscode from "vscode";
1111
export class LLDBDapServer implements vscode.Disposable {
1212
private serverProcess?: child_process.ChildProcessWithoutNullStreams;
1313
private serverInfo?: Promise<{ host: string; port: number }>;
14+
private serverSpawnInfo?: string[];
1415

1516
constructor() {
1617
vscode.commands.registerCommand(
@@ -34,7 +35,7 @@ export class LLDBDapServer implements vscode.Disposable {
3435
options?: child_process.SpawnOptionsWithoutStdio,
3536
): Promise<{ host: string; port: number } | undefined> {
3637
const dapArgs = [...args, "--connection", "listen://localhost:0"];
37-
if (!(await this.shouldContinueStartup(dapPath, dapArgs))) {
38+
if (!(await this.shouldContinueStartup(dapPath, dapArgs, options?.env))) {
3839
return undefined;
3940
}
4041

@@ -70,6 +71,7 @@ export class LLDBDapServer implements vscode.Disposable {
7071
}
7172
});
7273
this.serverProcess = process;
74+
this.serverSpawnInfo = this.getSpawnInfo(dapPath, dapArgs, options?.env);
7375
});
7476
return this.serverInfo;
7577
}
@@ -85,12 +87,14 @@ export class LLDBDapServer implements vscode.Disposable {
8587
private async shouldContinueStartup(
8688
dapPath: string,
8789
args: string[],
90+
env: NodeJS.ProcessEnv | { [key: string]: string } | undefined,
8891
): Promise<boolean> {
89-
if (!this.serverProcess || !this.serverInfo) {
92+
if (!this.serverProcess || !this.serverInfo || !this.serverSpawnInfo) {
9093
return true;
9194
}
9295

93-
if (isDeepStrictEqual(this.serverProcess.spawnargs, [dapPath, ...args])) {
96+
const newSpawnInfo = this.getSpawnInfo(dapPath, args, env);
97+
if (isDeepStrictEqual(this.serverSpawnInfo, newSpawnInfo)) {
9498
return true;
9599
}
96100

@@ -102,11 +106,11 @@ export class LLDBDapServer implements vscode.Disposable {
102106
103107
The previous lldb-dap server was started with:
104108
105-
${this.serverProcess.spawnargs.join(" ")}
109+
${this.serverSpawnInfo.join(" ")}
106110
107111
The new lldb-dap server will be started with:
108112
109-
${dapPath} ${args.join(" ")}
113+
${newSpawnInfo.join(" ")}
110114
111115
Restarting the server will interrupt any existing debug sessions and start a new server.`,
112116
},
@@ -143,4 +147,18 @@ Restarting the server will interrupt any existing debug sessions and start a new
143147
this.serverInfo = undefined;
144148
}
145149
}
150+
151+
getSpawnInfo(
152+
path: string,
153+
args: string[],
154+
env: NodeJS.ProcessEnv | { [key: string]: string } | undefined,
155+
): string[] {
156+
return [
157+
path,
158+
...args,
159+
...Object.entries(env ?? {}).map(
160+
(entry) => String(entry[0]) + "=" + String(entry[1]),
161+
),
162+
];
163+
}
146164
}

0 commit comments

Comments
 (0)