Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
89 changes: 86 additions & 3 deletions lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,40 @@ async function findDAPExecutable(): Promise<string | undefined> {
return undefined;
}

/**
* Validates the DAP environment provided in the debug configuration.
* It must be a dictionary of string keys and values OR an array of string values.
*
* @param debugConfigEnv The supposed DAP environment that will be validated
* @returns Whether or not the DAP environment is valid
*/
function validateDAPEnv(debugConfigEnv: any): boolean {
// If the env is an object, it should have string values.
// The keys are guaranteed to be strings.
if (
typeof debugConfigEnv === "object" &&
Object.values(debugConfigEnv).findIndex(
(entry) => typeof entry !== "string",
) !== -1
) {
return false;
}

// If the env is an array, it should have string values which match the regex.
if (
Array.isArray(debugConfigEnv) &&
debugConfigEnv.findIndex(
(entry) =>
typeof entry !== "string" || !/^((\\w+=.*)|^\\w+)$/.test(entry),
) !== -1
) {
return false;
}

// The env is valid.
return true;
}

/**
* Retrieves the lldb-dap executable path either from settings or the provided
* {@link vscode.DebugConfiguration}.
Expand Down Expand Up @@ -157,6 +191,51 @@ 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 (validateDAPEnv(debugConfigEnv) === false) {
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(),
);
}

// 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 All @@ -182,12 +261,16 @@ export async function createDebugAdapterExecutable(
if (log_path) {
env["LLDBDAP_LOG"] = log_path;
} else if (
vscode.workspace.getConfiguration("lldb-dap").get("captureSessionLogs", false)
vscode.workspace
.getConfiguration("lldb-dap")
.get("captureSessionLogs", false)
) {
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