Skip to content

[lldb-dap] Support finding the lldb-dap binary #118547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 4, 2024
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
2 changes: 1 addition & 1 deletion lldb/tools/lldb-dap/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "lldb-dap",
"displayName": "LLDB DAP",
"version": "0.2.6",
"version": "0.2.7",
"publisher": "llvm-vs-code-extensions",
"homepage": "https://lldb.llvm.org",
"description": "LLDB debugging from VSCode",
Expand Down
146 changes: 119 additions & 27 deletions lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,99 @@
import * as path from "path";
import * as util from "util";
import * as vscode from "vscode";
import { LLDBDapOptions } from "./types";
import * as child_process from "child_process";
import * as fs from "node:fs/promises";

/**
* This class defines a factory used to find the lldb-dap binary to use
* depending on the session configuration.
*/
export class LLDBDapDescriptorFactory
implements vscode.DebugAdapterDescriptorFactory
{
private lldbDapOptions: LLDBDapOptions;

constructor(lldbDapOptions: LLDBDapOptions) {
this.lldbDapOptions = lldbDapOptions;
export async function isExecutable(path: string): Promise<Boolean> {
try {
await fs.access(path, fs.constants.X_OK);
} catch {
return false;
}
return true;
}

static async isValidDebugAdapterPath(
pathUri: vscode.Uri,
): Promise<Boolean> {
async function findWithXcrun(executable: string): Promise<string | undefined> {
if (process.platform === "darwin") {
try {
Copy link
Contributor

Choose a reason for hiding this comment

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

We may want to validate that the file is executable. It looks like the VS Code FS API doesn't have that info but we could use the node FS instead, something like:

import { access, constants } from 'node:fs/promises';

try {
  await access('<path>', constants.X_OK);
  console.log('can execute');
} catch {
  console.error('cannot execute');
} 

const fileStats = await vscode.workspace.fs.stat(pathUri);
if (!(fileStats.type & vscode.FileType.File)) {
return false;
const exec = util.promisify(child_process.execFile);
let { stdout, stderr } = await exec("/usr/bin/xcrun", [
"-find",
executable,
]);
if (stdout) {
return stdout.toString().trimEnd();
}
} catch (err) {
return false;
} catch (error) {}
}
return undefined;
}

async function findInPath(executable: string): Promise<string | undefined> {
const env_path =
process.platform === "win32" ? process.env["Path"] : process.env["PATH"];
if (!env_path) {
return undefined;
}

const paths = env_path.split(path.delimiter);
for (const p of paths) {
const exe_path = path.join(p, executable);
if (await isExecutable(exe_path)) {
return exe_path;
}
return true;
}
return undefined;
}

async function findDAPExecutable(): Promise<string | undefined> {
const executable = process.platform === "win32" ? "lldb-dap.exe" : "lldb-dap";

// Prefer lldb-dap from Xcode on Darwin.
const xcrun_dap = findWithXcrun(executable);
if (xcrun_dap) {
return xcrun_dap;
}

// Find lldb-dap in the user's path.
const path_dap = findInPath(executable);
if (path_dap) {
return path_dap;
}

return undefined;
}

async function getDAPExecutable(
session: vscode.DebugSession,
): Promise<string | undefined> {
const config = vscode.workspace.getConfiguration(
"lldb-dap",
session.workspaceFolder,
);

// Prefer the explicitly specified path in the extension's configuration.
const configPath = config.get<string>("executable-path");
if (configPath && configPath.length !== 0) {
return configPath;
}

// Try finding the lldb-dap binary.
const foundPath = await findDAPExecutable();
if (foundPath) {
return foundPath;
}

return undefined;
}

/**
* This class defines a factory used to find the lldb-dap binary to use
* depending on the session configuration.
*/
export class LLDBDapDescriptorFactory
implements vscode.DebugAdapterDescriptorFactory
{
async createDebugAdapterDescriptor(
session: vscode.DebugSession,
executable: vscode.DebugAdapterExecutable | undefined,
Expand All @@ -36,14 +102,40 @@ export class LLDBDapDescriptorFactory
"lldb-dap",
session.workspaceFolder,
);
const customPath = config.get<string>("executable-path");
const path: string = customPath || executable!!.command;

const fileUri = vscode.Uri.file(path);
if (!(await LLDBDapDescriptorFactory.isValidDebugAdapterPath(fileUri))) {
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(fileUri.path);
const log_path = config.get<string>("log-path");
let env: { [key: string]: string } = {};
if (log_path) {
env["LLDBDAP_LOG"] = log_path;
}
const configEnvironment =
config.get<{ [key: string]: string }>("environment") || {};
const dapPath = await getDAPExecutable(session);
const dbgOptions = {
env: {
...executable?.options?.env,
...configEnvironment,
...env,
},
};
if (dapPath) {
if (!(await isExecutable(dapPath))) {
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
return undefined;
}
return new vscode.DebugAdapterExecutable(dapPath, [], dbgOptions);
} else if (executable) {
if (!(await isExecutable(executable.command))) {
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(executable.command);
return undefined;
}
return new vscode.DebugAdapterExecutable(
executable.command,
executable.args,
dbgOptions,
);
}
return this.lldbDapOptions.createDapExecutableCommand(session, executable);
return undefined;
}

/**
Expand Down
79 changes: 12 additions & 67 deletions lldb/tools/lldb-dap/src-ts/extension.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,24 @@
import * as path from "path";
import * as util from "util";
import * as vscode from "vscode";
import { LLDBDapOptions } from "./types";
import { DisposableContext } from "./disposable-context";
import { LLDBDapDescriptorFactory } from "./debug-adapter-factory";

/**
* This creates the configurations for this project if used as a standalone
* extension.
*/
function createDefaultLLDBDapOptions(): LLDBDapOptions {
return {
debuggerType: "lldb-dap",
async createDapExecutableCommand(
session: vscode.DebugSession,
packageJSONExecutable: vscode.DebugAdapterExecutable | undefined,
): Promise<vscode.DebugAdapterExecutable | undefined> {
const config = vscode.workspace.getConfiguration(
"lldb-dap",
session.workspaceFolder,
);
const path = config.get<string>("executable-path");
const log_path = config.get<string>("log-path");

let env: { [key: string]: string } = {};
if (log_path) {
env["LLDBDAP_LOG"] = log_path;
}
const configEnvironment = config.get<{ [key: string]: string }>("environment") || {};
if (path) {
const dbgOptions = {
env: {
...configEnvironment,
...env,
}
};
return new vscode.DebugAdapterExecutable(path, [], dbgOptions);
} else if (packageJSONExecutable) {
return new vscode.DebugAdapterExecutable(
packageJSONExecutable.command,
packageJSONExecutable.args,
{
...packageJSONExecutable.options,
env: {
...packageJSONExecutable.options?.env,
...configEnvironment,
...env,
},
},
);
} else {
return undefined;
}
},
};
}
import {
LLDBDapDescriptorFactory,
isExecutable,
} from "./debug-adapter-factory";
import { DisposableContext } from "./disposable-context";

/**
* This class represents the extension and manages its life cycle. Other extensions
* using it as as library should use this class as the main entry point.
*/
export class LLDBDapExtension extends DisposableContext {
private lldbDapOptions: LLDBDapOptions;

constructor(lldbDapOptions: LLDBDapOptions) {
constructor() {
super();
this.lldbDapOptions = lldbDapOptions;

this.pushSubscription(
vscode.debug.registerDebugAdapterDescriptorFactory(
this.lldbDapOptions.debuggerType,
new LLDBDapDescriptorFactory(this.lldbDapOptions),
"lldb-dap",
new LLDBDapDescriptorFactory(),
),
);

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

if (dapPath) {
const fileUri = vscode.Uri.file(dapPath);
if (
await LLDBDapDescriptorFactory.isValidDebugAdapterPath(fileUri)
) {
if (await isExecutable(dapPath)) {
return;
}
}
Expand All @@ -98,7 +45,5 @@ export class LLDBDapExtension extends DisposableContext {
* This is the entry point when initialized by VS Code.
*/
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
new LLDBDapExtension(createDefaultLLDBDapOptions()),
);
context.subscriptions.push(new LLDBDapExtension());
}
23 changes: 0 additions & 23 deletions lldb/tools/lldb-dap/src-ts/types.ts

This file was deleted.

Loading