Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions lldb/tools/lldb-dap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,29 @@
},
"markdownDescription": "The list of additional arguments used to launch the debug adapter executable. Overrides any user or workspace settings."
},
"debugAdapterEnv": {
"anyOf": [
{
"type": "object",
"markdownDescription": "Additional environment variables to set when launching the debug adapter executable. E.g. `{ \"FOO\": \"1\" }`",
"patternProperties": {
".*": {
"type": "string"
}
},
"default": {}
},
{
"type": "array",
"markdownDescription": "Additional environment variables to set when launching the debug adapter executable. E.g. `[\"FOO=1\", \"BAR\"]`",
"items": {
"type": "string",
"pattern": "^((\\w+=.*)|^\\w+)$"
},
"default": []
}
]
},
"program": {
"type": "string",
"description": "Path to the program to debug."
Expand Down
61 changes: 59 additions & 2 deletions lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,61 @@ async function getDAPArguments(
.get<string[]>("arguments", []);
}

/**
* Retrieves the environment that will be provided to lldb-dap either from settings or the provided
* {@link vscode.DebugConfiguration}.
*
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
* @param configuration The {@link vscode.DebugConfiguration} that will be launched
* @throws An {@link ErrorWithNotification} if something went wrong
* @returns The environment that will be provided to lldb-dap
*/
async function getDAPEnvironment(
workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
): Promise<{ [key: string]: string }> {
const debugConfigEnv = configuration.debugAdapterEnv;
if (debugConfigEnv) {
if (
(typeof debugConfigEnv !== "object" ||

Object.values(debugConfigEnv).findIndex(
(entry) => typeof entry !== "string",
) !== -1) &&
(!Array.isArray(debugConfigEnv) ||
debugConfigEnv.findIndex(
(entry) =>
typeof entry !== "string" || !/^((\\w+=.*)|^\\w+)$/.test(entry),
) !== -1)
) {
throw new ErrorWithNotification(
"The debugAdapterEnv property must be a dictionary of string keys and values OR an array of string values. Please update your launch configuration",
new ConfigureButton(),
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this to a helper function called validateEnv or something like that. Otherwise it's very hard to read.
Also, that regex is sadly very difficult to read, can you do a simpler check? It doesn't need to be perfect perfect

Copy link
Contributor Author

@royitaqi royitaqi Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will move to a helper function. I see no dedicated source file for util functions, so I'm thinking just put into the same file.

Re. the regex, I actually feel the other way around, that the condition here should be exactly as what's specified in the package.json, so as to avoid user confusion like "why this env is accepted by launch.json, but not when I use it in vscode.debug.startDebugging". Here I assume user experience is more important than development experience (of us). Happy to learn more your thoughts.

Copy link
Contributor Author

@royitaqi royitaqi Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@walter-erquinigo I have moved the logic into a util function validateDAPEnv(). See latest commits
(0f91d37, 34a9980).

For the above, I have manually tested that it can catch the following cases:

  const ret1 = validateDAPEnv({
    999: 888,
  }); // false

  const ret2 = validateDAPEnv({
    "999": 888,
  }); // false

--

Waiting to learn more about your thoughts regarding the regex (see my prev comment).

// Transform, so that the returned value is always a dictionary.
if (Array.isArray(debugConfigEnv)) {
const ret: { [key: string]: string } = {};
for (const envVar of debugConfigEnv as string[]) {
const equalSignPos = envVar.search("=");
if (equalSignPos >= 0) {
ret[envVar.substr(0, equalSignPos)] = envVar.substr(equalSignPos + 1);
} else {
ret[envVar] = "";
}
}
return ret;
} else {
return debugConfigEnv;
}
}

const config = vscode.workspace.workspaceFile
? vscode.workspace.getConfiguration("lldb-dap")
: vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
return config.get<{ [key: string]: string }>("environment") || {};
}

/**
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
Expand Down Expand Up @@ -186,8 +241,10 @@ export async function createDebugAdapterExecutable(
) {
env["LLDBDAP_LOG"] = logFilePath.get(LogType.DEBUG_SESSION);
}
const configEnvironment =
config.get<{ [key: string]: string }>("environment") || {};
const configEnvironment = await getDAPEnvironment(
workspaceFolder,
configuration,
);
const dapPath = await getDAPExecutable(workspaceFolder, configuration);

const dbgOptions = {
Expand Down
28 changes: 23 additions & 5 deletions lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as vscode from "vscode";
export class LLDBDapServer implements vscode.Disposable {
private serverProcess?: child_process.ChildProcessWithoutNullStreams;
private serverInfo?: Promise<{ host: string; port: number }>;
private serverSpawnInfo?: string[];

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

Expand Down Expand Up @@ -70,6 +71,7 @@ export class LLDBDapServer implements vscode.Disposable {
}
});
this.serverProcess = process;
this.serverSpawnInfo = this.getSpawnInfo(dapPath, dapArgs, options?.env);
});
return this.serverInfo;
}
Expand All @@ -85,12 +87,14 @@ export class LLDBDapServer implements vscode.Disposable {
private async shouldContinueStartup(
dapPath: string,
args: string[],
env: NodeJS.ProcessEnv | { [key: string]: string } | undefined,
): Promise<boolean> {
if (!this.serverProcess || !this.serverInfo) {
if (!this.serverProcess || !this.serverInfo || !this.serverSpawnInfo) {
return true;
}

if (isDeepStrictEqual(this.serverProcess.spawnargs, [dapPath, ...args])) {
const newSpawnInfo = this.getSpawnInfo(dapPath, args, env);
if (isDeepStrictEqual(this.serverSpawnInfo, newSpawnInfo)) {
return true;
}

Expand All @@ -102,11 +106,11 @@ export class LLDBDapServer implements vscode.Disposable {

The previous lldb-dap server was started with:

${this.serverProcess.spawnargs.join(" ")}
${this.serverSpawnInfo.join(" ")}

The new lldb-dap server will be started with:

${dapPath} ${args.join(" ")}
${newSpawnInfo.join(" ")}

Restarting the server will interrupt any existing debug sessions and start a new server.`,
},
Expand Down Expand Up @@ -143,4 +147,18 @@ Restarting the server will interrupt any existing debug sessions and start a new
this.serverInfo = undefined;
}
}

getSpawnInfo(
path: string,
args: string[],
env: NodeJS.ProcessEnv | { [key: string]: string } | undefined,
): string[] {
return [
path,
...args,
...Object.entries(env ?? {}).map(
(entry) => String(entry[0]) + "=" + String(entry[1]),
),
];
}
}
Loading