Skip to content

Commit ff59538

Browse files
authored
[lldb-dap] Support finding the lldb-dap binary (#118547)
Support finding the lldb-dap binary with `xcrun` on Darwin or in PATH on all other platforms. Unfortunately, this PR is larger than I would like because it removes the `lldbDapOptions`. I believe these options are not necessary, and as previously implemented, they caused a spurious warning with this change. The problem was that the options were created before the custom factory. By moving the creation logic into the factory, we make sure it's only called after the factory has been registered. The upside is that this simplifies the code and removes a level of indirection.
1 parent ed2db3b commit ff59538

File tree

4 files changed

+132
-118
lines changed

4 files changed

+132
-118
lines changed

lldb/tools/lldb-dap/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "lldb-dap",
33
"displayName": "LLDB DAP",
4-
"version": "0.2.6",
4+
"version": "0.2.7",
55
"publisher": "llvm-vs-code-extensions",
66
"homepage": "https://lldb.llvm.org",
77
"description": "LLDB debugging from VSCode",

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

Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,99 @@
1+
import * as path from "path";
2+
import * as util from "util";
13
import * as vscode from "vscode";
2-
import { LLDBDapOptions } from "./types";
4+
import * as child_process from "child_process";
5+
import * as fs from "node:fs/promises";
36

4-
/**
5-
* This class defines a factory used to find the lldb-dap binary to use
6-
* depending on the session configuration.
7-
*/
8-
export class LLDBDapDescriptorFactory
9-
implements vscode.DebugAdapterDescriptorFactory
10-
{
11-
private lldbDapOptions: LLDBDapOptions;
12-
13-
constructor(lldbDapOptions: LLDBDapOptions) {
14-
this.lldbDapOptions = lldbDapOptions;
7+
export async function isExecutable(path: string): Promise<Boolean> {
8+
try {
9+
await fs.access(path, fs.constants.X_OK);
10+
} catch {
11+
return false;
1512
}
13+
return true;
14+
}
1615

17-
static async isValidDebugAdapterPath(
18-
pathUri: vscode.Uri,
19-
): Promise<Boolean> {
16+
async function findWithXcrun(executable: string): Promise<string | undefined> {
17+
if (process.platform === "darwin") {
2018
try {
21-
const fileStats = await vscode.workspace.fs.stat(pathUri);
22-
if (!(fileStats.type & vscode.FileType.File)) {
23-
return false;
19+
const exec = util.promisify(child_process.execFile);
20+
let { stdout, stderr } = await exec("/usr/bin/xcrun", [
21+
"-find",
22+
executable,
23+
]);
24+
if (stdout) {
25+
return stdout.toString().trimEnd();
2426
}
25-
} catch (err) {
26-
return false;
27+
} catch (error) {}
28+
}
29+
return undefined;
30+
}
31+
32+
async function findInPath(executable: string): Promise<string | undefined> {
33+
const env_path =
34+
process.platform === "win32" ? process.env["Path"] : process.env["PATH"];
35+
if (!env_path) {
36+
return undefined;
37+
}
38+
39+
const paths = env_path.split(path.delimiter);
40+
for (const p of paths) {
41+
const exe_path = path.join(p, executable);
42+
if (await isExecutable(exe_path)) {
43+
return exe_path;
2744
}
28-
return true;
45+
}
46+
return undefined;
47+
}
48+
49+
async function findDAPExecutable(): Promise<string | undefined> {
50+
const executable = process.platform === "win32" ? "lldb-dap.exe" : "lldb-dap";
51+
52+
// Prefer lldb-dap from Xcode on Darwin.
53+
const xcrun_dap = findWithXcrun(executable);
54+
if (xcrun_dap) {
55+
return xcrun_dap;
56+
}
57+
58+
// Find lldb-dap in the user's path.
59+
const path_dap = findInPath(executable);
60+
if (path_dap) {
61+
return path_dap;
2962
}
3063

64+
return undefined;
65+
}
66+
67+
async function getDAPExecutable(
68+
session: vscode.DebugSession,
69+
): Promise<string | undefined> {
70+
const config = vscode.workspace.getConfiguration(
71+
"lldb-dap",
72+
session.workspaceFolder,
73+
);
74+
75+
// Prefer the explicitly specified path in the extension's configuration.
76+
const configPath = config.get<string>("executable-path");
77+
if (configPath && configPath.length !== 0) {
78+
return configPath;
79+
}
80+
81+
// Try finding the lldb-dap binary.
82+
const foundPath = await findDAPExecutable();
83+
if (foundPath) {
84+
return foundPath;
85+
}
86+
87+
return undefined;
88+
}
89+
90+
/**
91+
* This class defines a factory used to find the lldb-dap binary to use
92+
* depending on the session configuration.
93+
*/
94+
export class LLDBDapDescriptorFactory
95+
implements vscode.DebugAdapterDescriptorFactory
96+
{
3197
async createDebugAdapterDescriptor(
3298
session: vscode.DebugSession,
3399
executable: vscode.DebugAdapterExecutable | undefined,
@@ -36,14 +102,40 @@ export class LLDBDapDescriptorFactory
36102
"lldb-dap",
37103
session.workspaceFolder,
38104
);
39-
const customPath = config.get<string>("executable-path");
40-
const path: string = customPath || executable!!.command;
41105

42-
const fileUri = vscode.Uri.file(path);
43-
if (!(await LLDBDapDescriptorFactory.isValidDebugAdapterPath(fileUri))) {
44-
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(fileUri.path);
106+
const log_path = config.get<string>("log-path");
107+
let env: { [key: string]: string } = {};
108+
if (log_path) {
109+
env["LLDBDAP_LOG"] = log_path;
110+
}
111+
const configEnvironment =
112+
config.get<{ [key: string]: string }>("environment") || {};
113+
const dapPath = await getDAPExecutable(session);
114+
const dbgOptions = {
115+
env: {
116+
...executable?.options?.env,
117+
...configEnvironment,
118+
...env,
119+
},
120+
};
121+
if (dapPath) {
122+
if (!(await isExecutable(dapPath))) {
123+
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
124+
return undefined;
125+
}
126+
return new vscode.DebugAdapterExecutable(dapPath, [], dbgOptions);
127+
} else if (executable) {
128+
if (!(await isExecutable(executable.command))) {
129+
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(executable.command);
130+
return undefined;
131+
}
132+
return new vscode.DebugAdapterExecutable(
133+
executable.command,
134+
executable.args,
135+
dbgOptions,
136+
);
45137
}
46-
return this.lldbDapOptions.createDapExecutableCommand(session, executable);
138+
return undefined;
47139
}
48140

49141
/**
Lines changed: 12 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,24 @@
1+
import * as path from "path";
2+
import * as util from "util";
13
import * as vscode from "vscode";
2-
import { LLDBDapOptions } from "./types";
3-
import { DisposableContext } from "./disposable-context";
4-
import { LLDBDapDescriptorFactory } from "./debug-adapter-factory";
5-
6-
/**
7-
* This creates the configurations for this project if used as a standalone
8-
* extension.
9-
*/
10-
function createDefaultLLDBDapOptions(): LLDBDapOptions {
11-
return {
12-
debuggerType: "lldb-dap",
13-
async createDapExecutableCommand(
14-
session: vscode.DebugSession,
15-
packageJSONExecutable: vscode.DebugAdapterExecutable | undefined,
16-
): Promise<vscode.DebugAdapterExecutable | undefined> {
17-
const config = vscode.workspace.getConfiguration(
18-
"lldb-dap",
19-
session.workspaceFolder,
20-
);
21-
const path = config.get<string>("executable-path");
22-
const log_path = config.get<string>("log-path");
234

24-
let env: { [key: string]: string } = {};
25-
if (log_path) {
26-
env["LLDBDAP_LOG"] = log_path;
27-
}
28-
const configEnvironment = config.get<{ [key: string]: string }>("environment") || {};
29-
if (path) {
30-
const dbgOptions = {
31-
env: {
32-
...configEnvironment,
33-
...env,
34-
}
35-
};
36-
return new vscode.DebugAdapterExecutable(path, [], dbgOptions);
37-
} else if (packageJSONExecutable) {
38-
return new vscode.DebugAdapterExecutable(
39-
packageJSONExecutable.command,
40-
packageJSONExecutable.args,
41-
{
42-
...packageJSONExecutable.options,
43-
env: {
44-
...packageJSONExecutable.options?.env,
45-
...configEnvironment,
46-
...env,
47-
},
48-
},
49-
);
50-
} else {
51-
return undefined;
52-
}
53-
},
54-
};
55-
}
5+
import {
6+
LLDBDapDescriptorFactory,
7+
isExecutable,
8+
} from "./debug-adapter-factory";
9+
import { DisposableContext } from "./disposable-context";
5610

5711
/**
5812
* This class represents the extension and manages its life cycle. Other extensions
5913
* using it as as library should use this class as the main entry point.
6014
*/
6115
export class LLDBDapExtension extends DisposableContext {
62-
private lldbDapOptions: LLDBDapOptions;
63-
64-
constructor(lldbDapOptions: LLDBDapOptions) {
16+
constructor() {
6517
super();
66-
this.lldbDapOptions = lldbDapOptions;
67-
6818
this.pushSubscription(
6919
vscode.debug.registerDebugAdapterDescriptorFactory(
70-
this.lldbDapOptions.debuggerType,
71-
new LLDBDapDescriptorFactory(this.lldbDapOptions),
20+
"lldb-dap",
21+
new LLDBDapDescriptorFactory(),
7222
),
7323
);
7424

@@ -80,10 +30,7 @@ export class LLDBDapExtension extends DisposableContext {
8030
.get<string>("executable-path");
8131

8232
if (dapPath) {
83-
const fileUri = vscode.Uri.file(dapPath);
84-
if (
85-
await LLDBDapDescriptorFactory.isValidDebugAdapterPath(fileUri)
86-
) {
33+
if (await isExecutable(dapPath)) {
8734
return;
8835
}
8936
}
@@ -98,7 +45,5 @@ export class LLDBDapExtension extends DisposableContext {
9845
* This is the entry point when initialized by VS Code.
9946
*/
10047
export function activate(context: vscode.ExtensionContext) {
101-
context.subscriptions.push(
102-
new LLDBDapExtension(createDefaultLLDBDapOptions()),
103-
);
48+
context.subscriptions.push(new LLDBDapExtension());
10449
}

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

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)