https://github.com/matthewbastien updated 
https://github.com/llvm/llvm-project/pull/129262

>From b40c3e7e4ebb154c5f231676451acbd17e1f39f7 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Fri, 28 Feb 2025 11:08:25 -0500
Subject: [PATCH 1/5] allow providing debug adapter arguments

---
 lldb/tools/lldb-dap/package.json              | 23 ++++++
 .../lldb-dap/src-ts/debug-adapter-factory.ts  | 72 +++++++++++++------
 2 files changed, 72 insertions(+), 23 deletions(-)

diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index cd450a614b3f7..aa11d8bcaa66e 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -75,6 +75,15 @@
           "type": "string",
           "description": "The path to the lldb-dap binary."
         },
+        "lldb-dap.arguments": {
+          "scope": "resource",
+          "type": "array",
+          "default": [],
+          "items": {
+            "type": "string"
+          },
+          "description": "The arguments provided to the lldb-dap process."
+        },
         "lldb-dap.log-path": {
           "scope": "resource",
           "type": "string",
@@ -162,6 +171,13 @@
                 "type": "string",
                 "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use."
               },
+              "debugAdapterArgs": {
+                "type": "array",
+                "items": {
+                  "type": "string"
+                },
+                "markdownDescription": "The list of arguments used to launch 
the debug adapter executable."
+              },
               "program": {
                 "type": "string",
                 "description": "Path to the program to debug."
@@ -352,6 +368,13 @@
                 "type": "string",
                 "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use."
               },
+              "debugAdapterArgs": {
+                "type": "array",
+                "items": {
+                  "type": "string"
+                },
+                "markdownDescription": "The list of arguments used to launch 
the debug adapter executable."
+              },
               "program": {
                 "type": "string",
                 "description": "Path to the program to attach to."
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts 
b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 1f76fe31b00ad..51f45f87d660a 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -25,7 +25,7 @@ async function findWithXcrun(executable: string): 
Promise<string | undefined> {
       if (stdout) {
         return stdout.toString().trimEnd();
       }
-    } catch (error) { }
+    } catch (error) {}
   }
   return undefined;
 }
@@ -93,13 +93,33 @@ async function getDAPExecutable(
   return undefined;
 }
 
+function getDAPArguments(session: vscode.DebugSession): string[] {
+  // Check the debug configuration for arguments first
+  const debugConfigArgs = session.configuration.debugAdapterArgs;
+  if (
+    Array.isArray(debugConfigArgs) &&
+    debugConfigArgs.findIndex((entry) => typeof entry !== "string") === -1
+  ) {
+    return debugConfigArgs;
+  }
+  // Fall back on the workspace configuration
+  return vscode.workspace
+    .getConfiguration("lldb-dap")
+    .get<string[]>("arguments", []);
+}
+
 /**
  * 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, vscode.Disposable {
-  private server?: Promise<{ process: child_process.ChildProcess, host: 
string, port: number }>;
+  implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable
+{
+  private server?: Promise<{
+    process: child_process.ChildProcess;
+    host: string;
+    port: number;
+  }>;
 
   dispose() {
     this.server?.then(({ process }) => {
@@ -109,7 +129,7 @@ export class LLDBDapDescriptorFactory
 
   async createDebugAdapterDescriptor(
     session: vscode.DebugSession,
-    executable: vscode.DebugAdapterExecutable | undefined,
+    _executable: vscode.DebugAdapterExecutable | undefined,
   ): Promise<vscode.DebugAdapterDescriptor | undefined> {
     const config = vscode.workspace.getConfiguration(
       "lldb-dap",
@@ -123,7 +143,7 @@ export class LLDBDapDescriptorFactory
     }
     const configEnvironment =
       config.get<{ [key: string]: string }>("environment") || {};
-    const dapPath = (await getDAPExecutable(session)) ?? executable?.command;
+    const dapPath = await getDAPExecutable(session);
 
     if (!dapPath) {
       LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage();
@@ -137,32 +157,38 @@ export class LLDBDapDescriptorFactory
 
     const dbgOptions = {
       env: {
-        ...executable?.options?.env,
         ...configEnvironment,
         ...env,
       },
     };
-    const dbgArgs = executable?.args ?? [];
+    const dbgArgs = getDAPArguments(session);
 
-    const serverMode = config.get<boolean>('serverMode', false);
+    const serverMode = config.get<boolean>("serverMode", false);
     if (serverMode) {
-      const { host, port } = await this.startServer(dapPath, dbgArgs, 
dbgOptions);
+      const { host, port } = await this.startServer(
+        dapPath,
+        dbgArgs,
+        dbgOptions,
+      );
       return new vscode.DebugAdapterServer(port, host);
     }
 
     return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
   }
 
-  startServer(dapPath: string, args: string[], options: 
child_process.CommonSpawnOptions): Promise<{ host: string, port: number }> {
-    if (this.server) return this.server;
+  startServer(
+    dapPath: string,
+    args: string[],
+    options: child_process.CommonSpawnOptions,
+  ): Promise<{ host: string; port: number }> {
+    if (this.server) {
+      return this.server;
+    }
 
-    this.server = new Promise(resolve => {
-      args.push(
-        '--connection',
-        'connect://localhost:0'
-      );
+    this.server = new Promise((resolve) => {
+      args.push("--connection", "connect://localhost:0");
       const server = child_process.spawn(dapPath, args, options);
-      server.stdout!.setEncoding('utf8').once('data', (data: string) => {
+      server.stdout!.setEncoding("utf8").once("data", (data: string) => {
         const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
         if (connection) {
           const host = connection[1];
@@ -170,9 +196,9 @@ export class LLDBDapDescriptorFactory
           resolve({ process: server, host, port });
         }
       });
-      server.on('exit', () => {
+      server.on("exit", () => {
         this.server = undefined;
-      })
+      });
     });
     return this.server;
   }
@@ -180,11 +206,11 @@ export class LLDBDapDescriptorFactory
   /**
    * Shows a message box when the debug adapter's path is not found
    */
-  static async showLLDBDapNotFoundMessage(path?: string) {
+  static async showLLDBDapNotFoundMessage(path?: string | undefined) {
     const message =
-      path
-        ? `Debug adapter path: ${path} is not a valid file.`
-        : "Unable to find the path to the LLDB debug adapter executable.";
+      path !== undefined
+        ? `Debug adapter path: ${path} is not a valid file`
+        : "Unable to find the LLDB debug adapter executable.";
     const openSettingsAction = "Open Settings";
     const callbackValue = await vscode.window.showErrorMessage(
       message,

>From 057ff4c9eed5c2344f5377e9199814c55f6748b1 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Fri, 28 Feb 2025 11:22:41 -0500
Subject: [PATCH 2/5] update wording

---
 lldb/tools/lldb-dap/package.json                    | 6 +++---
 lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index aa11d8bcaa66e..75d52786b01e8 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -82,7 +82,7 @@
           "items": {
             "type": "string"
           },
-          "description": "The arguments provided to the lldb-dap process."
+          "description": "The list of additional arguments used to launch the 
debug adapter executable."
         },
         "lldb-dap.log-path": {
           "scope": "resource",
@@ -176,7 +176,7 @@
                 "items": {
                   "type": "string"
                 },
-                "markdownDescription": "The list of arguments used to launch 
the debug adapter executable."
+                "markdownDescription": "The list of additional arguments used 
to launch the debug adapter executable."
               },
               "program": {
                 "type": "string",
@@ -373,7 +373,7 @@
                 "items": {
                   "type": "string"
                 },
-                "markdownDescription": "The list of arguments used to launch 
the debug adapter executable."
+                "markdownDescription": "The list of additional arguments used 
to launch the debug adapter executable."
               },
               "program": {
                 "type": "string",
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts 
b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 51f45f87d660a..8f1e8ad6b019a 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -210,7 +210,7 @@ export class LLDBDapDescriptorFactory
     const message =
       path !== undefined
         ? `Debug adapter path: ${path} is not a valid file`
-        : "Unable to find the LLDB debug adapter executable.";
+        : "Unable to find the path to the LLDB debug adapter executable.";
     const openSettingsAction = "Open Settings";
     const callbackValue = await vscode.window.showErrorMessage(
       message,

>From 14866434337e00a077945f63ebf3005fd3f8f61e Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Fri, 7 Mar 2025 17:45:19 -0500
Subject: [PATCH 3/5] prompt the user to restart the server if the executable
 or arguments change

---
 lldb/tools/lldb-dap/package.json              |  18 +-
 .../lldb-dap/src-ts/debug-adapter-factory.ts  | 205 ++++++++----------
 .../src-ts/debug-configuration-provider.ts    |  88 ++++++++
 lldb/tools/lldb-dap/src-ts/extension.ts       |  38 ++--
 lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts | 130 +++++++++++
 5 files changed, 346 insertions(+), 133 deletions(-)
 create mode 100644 lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
 create mode 100644 lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts

diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 75d52786b01e8..c3361ed3d0bf8 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -167,6 +167,14 @@
               "program"
             ],
             "properties": {
+              "debugAdapterHostname": {
+                "type": "string",
+                "markdownDescription": "The hostname that an existing lldb-dap 
executable is listening on."
+              },
+              "debugAdapterPort": {
+                "type": "number",
+                "markdownDescription": "The port that an existing lldb-dap 
executable is listening on."
+              },
               "debugAdapterExecutable": {
                 "type": "string",
                 "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use."
@@ -364,6 +372,14 @@
           },
           "attach": {
             "properties": {
+              "debugAdapterHostname": {
+                "type": "string",
+                "markdownDescription": "The hostname that an existing lldb-dap 
executable is listening on."
+              },
+              "debugAdapterPort": {
+                "type": "number",
+                "markdownDescription": "The port that an existing lldb-dap 
executable is listening on."
+              },
               "debugAdapterExecutable": {
                 "type": "string",
                 "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use."
@@ -572,4 +588,4 @@
       }
     ]
   }
-}
\ No newline at end of file
+}
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts 
b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 8f1e8ad6b019a..61c4b95efb8a7 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -6,7 +6,7 @@ import * as fs from "node:fs/promises";
 
 const exec = util.promisify(child_process.execFile);
 
-export async function isExecutable(path: string): Promise<Boolean> {
+async function isExecutable(path: string): Promise<Boolean> {
   try {
     await fs.access(path, fs.constants.X_OK);
   } catch {
@@ -66,19 +66,17 @@ async function findDAPExecutable(): Promise<string | 
undefined> {
 }
 
 async function getDAPExecutable(
-  session: vscode.DebugSession,
+  folder: vscode.WorkspaceFolder | undefined,
+  configuration: vscode.DebugConfiguration,
 ): Promise<string | undefined> {
   // Check if the executable was provided in the launch configuration.
-  const launchConfigPath = session.configuration["debugAdapterExecutable"];
+  const launchConfigPath = configuration["debugAdapterExecutable"];
   if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) {
     return launchConfigPath;
   }
 
   // Check if the executable was provided in the extension's configuration.
-  const config = vscode.workspace.getConfiguration(
-    "lldb-dap",
-    session.workspaceFolder,
-  );
+  const config = vscode.workspace.getConfiguration("lldb-dap", folder);
   const configPath = config.get<string>("executable-path");
   if (configPath && configPath.length !== 0) {
     return configPath;
@@ -93,9 +91,12 @@ async function getDAPExecutable(
   return undefined;
 }
 
-function getDAPArguments(session: vscode.DebugSession): string[] {
+function getDAPArguments(
+  folder: vscode.WorkspaceFolder | undefined,
+  configuration: vscode.DebugConfiguration,
+): string[] {
   // Check the debug configuration for arguments first
-  const debugConfigArgs = session.configuration.debugAdapterArgs;
+  const debugConfigArgs = configuration.debugAdapterArgs;
   if (
     Array.isArray(debugConfigArgs) &&
     debugConfigArgs.findIndex((entry) => typeof entry !== "string") === -1
@@ -104,124 +105,110 @@ function getDAPArguments(session: vscode.DebugSession): 
string[] {
   }
   // Fall back on the workspace configuration
   return vscode.workspace
-    .getConfiguration("lldb-dap")
+    .getConfiguration("lldb-dap", folder)
     .get<string[]>("arguments", []);
 }
 
 /**
- * This class defines a factory used to find the lldb-dap binary to use
- * depending on the session configuration.
+ * Shows a modal when the debug adapter's path is not found
  */
-export class LLDBDapDescriptorFactory
-  implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable
-{
-  private server?: Promise<{
-    process: child_process.ChildProcess;
-    host: string;
-    port: number;
-  }>;
-
-  dispose() {
-    this.server?.then(({ process }) => {
-      process.kill();
-    });
-  }
+async function showLLDBDapNotFoundMessage(path?: string) {
+  const message =
+    path !== undefined
+      ? `Debug adapter path: ${path} is not a valid file`
+      : "Unable to find the path to the LLDB debug adapter executable.";
+  const openSettingsAction = "Open Settings";
+  const callbackValue = await vscode.window.showErrorMessage(
+    message,
+    { modal: true },
+    openSettingsAction,
+  );
 
-  async createDebugAdapterDescriptor(
-    session: vscode.DebugSession,
-    _executable: vscode.DebugAdapterExecutable | undefined,
-  ): Promise<vscode.DebugAdapterDescriptor | undefined> {
-    const config = vscode.workspace.getConfiguration(
-      "lldb-dap",
-      session.workspaceFolder,
+  if (openSettingsAction === callbackValue) {
+    vscode.commands.executeCommand(
+      "workbench.action.openSettings",
+      "lldb-dap.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") || {};
-    const dapPath = await getDAPExecutable(session);
-
-    if (!dapPath) {
-      LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage();
-      return undefined;
-    }
+/**
+ * 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.
+ *
+ * @param folder The {@link vscode.WorkspaceFolder} that the debug session 
will be launched within
+ * @param configuration The {@link vscode.DebugConfiguration}
+ * @param userInteractive Whether or not this was called due to user 
interaction (determines if modals should be shown)
+ * @returns
+ */
+export async function createDebugAdapterExecutable(
+  folder: vscode.WorkspaceFolder | undefined,
+  configuration: vscode.DebugConfiguration,
+  userInteractive?: boolean,
+): Promise<vscode.DebugAdapterExecutable | undefined> {
+  const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+  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(folder, configuration);
 
-    if (!(await isExecutable(dapPath))) {
-      LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
-      return;
+  if (!dapPath) {
+    if (userInteractive) {
+      showLLDBDapNotFoundMessage();
     }
+    return undefined;
+  }
 
-    const dbgOptions = {
-      env: {
-        ...configEnvironment,
-        ...env,
-      },
-    };
-    const dbgArgs = getDAPArguments(session);
-
-    const serverMode = config.get<boolean>("serverMode", false);
-    if (serverMode) {
-      const { host, port } = await this.startServer(
-        dapPath,
-        dbgArgs,
-        dbgOptions,
-      );
-      return new vscode.DebugAdapterServer(port, host);
+  if (!(await isExecutable(dapPath))) {
+    if (userInteractive) {
+      showLLDBDapNotFoundMessage(dapPath);
     }
-
-    return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
+    return undefined;
   }
 
-  startServer(
-    dapPath: string,
-    args: string[],
-    options: child_process.CommonSpawnOptions,
-  ): Promise<{ host: string; port: number }> {
-    if (this.server) {
-      return this.server;
-    }
+  const dbgOptions = {
+    env: {
+      ...configEnvironment,
+      ...env,
+    },
+  };
+  const dbgArgs = getDAPArguments(folder, configuration);
 
-    this.server = new Promise((resolve) => {
-      args.push("--connection", "connect://localhost:0");
-      const server = child_process.spawn(dapPath, args, options);
-      server.stdout!.setEncoding("utf8").once("data", (data: string) => {
-        const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
-        if (connection) {
-          const host = connection[1];
-          const port = Number(connection[2]);
-          resolve({ process: server, host, port });
-        }
-      });
-      server.on("exit", () => {
-        this.server = undefined;
-      });
-    });
-    return this.server;
-  }
+  return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
+}
 
-  /**
-   * Shows a message box when the debug adapter's path is not found
-   */
-  static async showLLDBDapNotFoundMessage(path?: string | undefined) {
-    const message =
-      path !== undefined
-        ? `Debug adapter path: ${path} is not a valid file`
-        : "Unable to find the path to the LLDB debug adapter executable.";
-    const openSettingsAction = "Open Settings";
-    const callbackValue = await vscode.window.showErrorMessage(
-      message,
-      openSettingsAction,
-    );
+/**
+ * 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,
+  ): Promise<vscode.DebugAdapterDescriptor | undefined> {
+    if (executable) {
+      throw new Error(
+        "Setting the debug adapter executable in the package.json is not 
supported.",
+      );
+    }
 
-    if (openSettingsAction === callbackValue) {
-      vscode.commands.executeCommand(
-        "workbench.action.openSettings",
-        "lldb-dap.executable-path",
+    // Use a server connection if the debugAdapterPort is provided
+    if (session.configuration.debugAdapterPort) {
+      return new vscode.DebugAdapterServer(
+        session.configuration.debugAdapterPort,
+        session.configuration.debugAdapterHost,
       );
     }
+
+    return createDebugAdapterExecutable(
+      session.workspaceFolder,
+      session.configuration,
+    );
   }
 }
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts 
b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
new file mode 100644
index 0000000000000..d14393afe6658
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -0,0 +1,88 @@
+import * as vscode from "vscode";
+import { LLDBDapServer } from "./lldb-dap-server";
+import { createDebugAdapterExecutable } from "./debug-adapter-factory";
+
+/**
+ * Shows an error message to the user that optionally allows them to open their
+ * launch.json to configure it.
+ *
+ * @param message The error message to display to the user
+ * @returns `undefined` if the debug session should stop or `null` if the 
launch.json should be opened
+ */
+async function showErrorWithConfigureButton(
+  message: string,
+): Promise<null | undefined> {
+  const userSelection = await vscode.window.showErrorMessage(
+    message,
+    { modal: true },
+    "Configure",
+  );
+
+  if (userSelection === "Configure") {
+    return null; // Stops the debug session and opens the launch.json for 
editing
+  }
+
+  return undefined; // Only stops the debug session
+}
+
+export class LLDBDapConfigurationProvider
+  implements vscode.DebugConfigurationProvider
+{
+  constructor(private readonly server: LLDBDapServer) {}
+
+  async resolveDebugConfiguration(
+    folder: vscode.WorkspaceFolder | undefined,
+    debugConfiguration: vscode.DebugConfiguration,
+    _token?: vscode.CancellationToken,
+  ): Promise<vscode.DebugConfiguration | null | undefined> {
+    if (
+      "debugAdapterHost" in debugConfiguration &&
+      !("debugAdapterPort" in debugConfiguration)
+    ) {
+      return showErrorWithConfigureButton(
+        "A debugAdapterPort must be provided when debugAdapterHost is set. 
Please update your launch configuration.",
+      );
+    }
+
+    if (
+      "debugAdapterPort" in debugConfiguration &&
+      ("debugAdapterExecutable" in debugConfiguration ||
+        "debugAdapterArgs" in debugConfiguration)
+    ) {
+      return showErrorWithConfigureButton(
+        "The debugAdapterPort property is incompatible with 
debugAdapterExecutable and debugAdapterArgs. Please update your launch 
configuration.",
+      );
+    }
+
+    // Server mode needs to be handled here since DebugAdapterDescriptorFactory
+    // will show an unhelpful error if it returns undefined. We'd rather show a
+    // nicer error message here and allow stopping the debug session 
gracefully.
+    const config = vscode.workspace.getConfiguration("lldb-dap", folder);
+    if (config.get<boolean>("serverMode", false)) {
+      const executable = await createDebugAdapterExecutable(
+        folder,
+        debugConfiguration,
+        /* userInteractive */ true,
+      );
+      if (!executable) {
+        return undefined;
+      }
+      const serverInfo = await this.server.start(
+        executable.command,
+        executable.args,
+        executable.options,
+      );
+      if (!serverInfo) {
+        return undefined;
+      }
+      // Use a debug adapter host and port combination rather than an 
executable
+      // and list of arguments.
+      delete debugConfiguration.debugAdapterExecutable;
+      delete debugConfiguration.debugAdapterArgs;
+      debugConfiguration.debugAdapterHost = serverInfo.host;
+      debugConfiguration.debugAdapterPort = serverInfo.port;
+    }
+
+    return debugConfiguration;
+  }
+}
diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts 
b/lldb/tools/lldb-dap/src-ts/extension.ts
index a07bcdebcb68b..e29e2bee2f1fa 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -1,10 +1,9 @@
 import * as vscode from "vscode";
 
-import {
-  LLDBDapDescriptorFactory,
-  isExecutable,
-} from "./debug-adapter-factory";
+import { LLDBDapDescriptorFactory } from "./debug-adapter-factory";
 import { DisposableContext } from "./disposable-context";
+import { LLDBDapConfigurationProvider } from "./debug-configuration-provider";
+import { LLDBDapServer } from "./lldb-dap-server";
 
 /**
  * This class represents the extension and manages its life cycle. Other 
extensions
@@ -13,29 +12,22 @@ import { DisposableContext } from "./disposable-context";
 export class LLDBDapExtension extends DisposableContext {
   constructor() {
     super();
-    const factory = new LLDBDapDescriptorFactory();
-    this.pushSubscription(factory);
+
+    const lldbDapServer = new LLDBDapServer();
+    this.pushSubscription(lldbDapServer);
+
     this.pushSubscription(
-      vscode.debug.registerDebugAdapterDescriptorFactory(
+      vscode.debug.registerDebugConfigurationProvider(
         "lldb-dap",
-        factory,
-      )
+        new LLDBDapConfigurationProvider(lldbDapServer),
+      ),
     );
-    this.pushSubscription(
-      vscode.workspace.onDidChangeConfiguration(async (event) => {
-        if (event.affectsConfiguration("lldb-dap.executable-path")) {
-          const dapPath = vscode.workspace
-            .getConfiguration("lldb-dap")
-            .get<string>("executable-path");
 
-          if (dapPath) {
-            if (await isExecutable(dapPath)) {
-              return;
-            }
-          }
-          LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath || "");
-        }
-      }),
+    this.pushSubscription(
+      vscode.debug.registerDebugAdapterDescriptorFactory(
+        "lldb-dap",
+        new LLDBDapDescriptorFactory(),
+      ),
     );
   }
 }
diff --git a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts 
b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
new file mode 100644
index 0000000000000..2241a8676e46f
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
@@ -0,0 +1,130 @@
+import * as child_process from "node:child_process";
+import * as vscode from "vscode";
+
+function areArraysEqual<T>(lhs: T[], rhs: T[]): boolean {
+  if (lhs.length !== rhs.length) {
+    return false;
+  }
+  for (let i = 0; i < lhs.length; i++) {
+    if (lhs[i] !== rhs[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * Represents a running lldb-dap process that is accepting connections (i.e. 
in "server mode").
+ *
+ * Handles startup of the process if it isn't running already as well as 
prompting the user
+ * to restart when arguments have changed.
+ */
+export class LLDBDapServer implements vscode.Disposable {
+  private serverProcess?: child_process.ChildProcessWithoutNullStreams;
+  private serverInfo?: Promise<{ host: string; port: number }>;
+
+  /**
+   * Starts the server with the provided options. The server will be restarted 
or reused as
+   * necessary.
+   *
+   * @param dapPath the path to the debug adapter executable
+   * @param args the list of arguments to provide to the debug adapter
+   * @param options the options to provide to the debug adapter process
+   * @returns a promise that resolves with the host and port information or 
`undefined` if unable to launch the server.
+   */
+  async start(
+    dapPath: string,
+    args: string[],
+    options?: child_process.SpawnOptionsWithoutStdio,
+  ): Promise<{ host: string; port: number } | undefined> {
+    const dapArgs = [...args, "--connection", "connect://localhost:0"];
+    if (!(await this.shouldContinueStartup(dapPath, dapArgs))) {
+      return undefined;
+    }
+
+    if (this.serverInfo) {
+      return this.serverInfo;
+    }
+
+    this.serverInfo = new Promise((resolve, reject) => {
+      const process = child_process.spawn(dapPath, dapArgs, options);
+      process.on("error", (error) => {
+        reject(error);
+        this.serverProcess = undefined;
+        this.serverInfo = undefined;
+      });
+      process.on("exit", (code, signal) => {
+        let errorMessage = "Server process exited early";
+        if (code !== undefined) {
+          errorMessage += ` with code ${code}`;
+        } else if (signal !== undefined) {
+          errorMessage += ` due to signal ${signal}`;
+        }
+        reject(new Error(errorMessage));
+        this.serverProcess = undefined;
+        this.serverInfo = undefined;
+      });
+      process.stdout.setEncoding("utf8").on("data", (data) => {
+        const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(
+          data.toString(),
+        );
+        if (connection) {
+          const host = connection[1];
+          const port = Number(connection[2]);
+          resolve({ host, port });
+          process.stdout.removeAllListeners();
+        }
+      });
+      this.serverProcess = process;
+    });
+    return this.serverInfo;
+  }
+
+  /**
+   * Checks to see if the server needs to be restarted. If so, it will prompt 
the user
+   * to ask if they wish to restart.
+   *
+   * @param dapPath the path to the debug adapter
+   * @param args the arguments for the debug adapter
+   * @returns whether or not startup should continue depending on user input
+   */
+  private async shouldContinueStartup(
+    dapPath: string,
+    args: string[],
+  ): Promise<boolean> {
+    if (!this.serverProcess || !this.serverInfo) {
+      return true;
+    }
+
+    if (areArraysEqual(this.serverProcess.spawnargs, [dapPath, ...args])) {
+      return true;
+    }
+
+    const userInput = await vscode.window.showInformationMessage(
+      "A server mode instance of lldb-dap is already running, but the 
arguments are different from what is requested in your debug configuration or 
settings. Would you like to restart the server?",
+      { modal: true },
+      "Restart",
+      "Use Existing",
+    );
+    switch (userInput) {
+      case "Restart":
+        this.serverProcess.kill();
+        this.serverProcess = undefined;
+        this.serverInfo = undefined;
+        return true;
+      case "Use Existing":
+        return true;
+      case undefined:
+        return false;
+    }
+  }
+
+  dispose() {
+    if (!this.serverProcess) {
+      return;
+    }
+    this.serverProcess.kill();
+    this.serverProcess = undefined;
+    this.serverInfo = undefined;
+  }
+}

>From 4c639360892f974c1e93f37425e477387a57123a Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Fri, 7 Mar 2025 18:00:03 -0500
Subject: [PATCH 4/5] add more checks and error messages

---
 .../lldb-dap/src-ts/debug-adapter-factory.ts  | 51 +++++++++----------
 .../src-ts/debug-configuration-provider.ts    | 24 +--------
 .../lldb-dap/src-ts/ui/error-messages.ts      | 49 ++++++++++++++++++
 3 files changed, 75 insertions(+), 49 deletions(-)
 create mode 100644 lldb/tools/lldb-dap/src-ts/ui/error-messages.ts

diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts 
b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 61c4b95efb8a7..11a1cb776b0a3 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -3,6 +3,10 @@ import * as util from "util";
 import * as vscode from "vscode";
 import * as child_process from "child_process";
 import * as fs from "node:fs/promises";
+import {
+  showErrorWithConfigureButton,
+  showLLDBDapNotFoundMessage,
+} from "./ui/error-messages";
 
 const exec = util.promisify(child_process.execFile);
 
@@ -91,12 +95,27 @@ async function getDAPExecutable(
   return undefined;
 }
 
-function getDAPArguments(
+async function getDAPArguments(
   folder: vscode.WorkspaceFolder | undefined,
   configuration: vscode.DebugConfiguration,
-): string[] {
+  userInteractive?: boolean,
+): Promise<string[] | null | undefined> {
   // Check the debug configuration for arguments first
   const debugConfigArgs = configuration.debugAdapterArgs;
+  if (debugConfigArgs) {
+    if (
+      !Array.isArray(debugConfigArgs) ||
+      debugConfigArgs.findIndex((entry) => typeof entry !== "string") !== -1
+    ) {
+      if (!userInteractive) {
+        return undefined;
+      }
+      return showErrorWithConfigureButton(
+        "The debugAdapterArgs property must be an array of string values.",
+      );
+    }
+    return debugConfigArgs;
+  }
   if (
     Array.isArray(debugConfigArgs) &&
     debugConfigArgs.findIndex((entry) => typeof entry !== "string") === -1
@@ -109,29 +128,6 @@ function getDAPArguments(
     .get<string[]>("arguments", []);
 }
 
-/**
- * Shows a modal when the debug adapter's path is not found
- */
-async function showLLDBDapNotFoundMessage(path?: string) {
-  const message =
-    path !== undefined
-      ? `Debug adapter path: ${path} is not a valid file`
-      : "Unable to find the path to the LLDB debug adapter executable.";
-  const openSettingsAction = "Open Settings";
-  const callbackValue = await vscode.window.showErrorMessage(
-    message,
-    { modal: true },
-    openSettingsAction,
-  );
-
-  if (openSettingsAction === callbackValue) {
-    vscode.commands.executeCommand(
-      "workbench.action.openSettings",
-      "lldb-dap.executable-path",
-    );
-  }
-}
-
 /**
  * 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.
@@ -176,7 +172,10 @@ export async function createDebugAdapterExecutable(
       ...env,
     },
   };
-  const dbgArgs = getDAPArguments(folder, configuration);
+  const dbgArgs = await getDAPArguments(folder, configuration, 
userInteractive);
+  if (!dbgArgs) {
+    return undefined;
+  }
 
   return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
 }
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts 
b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
index d14393afe6658..06517f05629aa 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -1,29 +1,7 @@
 import * as vscode from "vscode";
 import { LLDBDapServer } from "./lldb-dap-server";
 import { createDebugAdapterExecutable } from "./debug-adapter-factory";
-
-/**
- * Shows an error message to the user that optionally allows them to open their
- * launch.json to configure it.
- *
- * @param message The error message to display to the user
- * @returns `undefined` if the debug session should stop or `null` if the 
launch.json should be opened
- */
-async function showErrorWithConfigureButton(
-  message: string,
-): Promise<null | undefined> {
-  const userSelection = await vscode.window.showErrorMessage(
-    message,
-    { modal: true },
-    "Configure",
-  );
-
-  if (userSelection === "Configure") {
-    return null; // Stops the debug session and opens the launch.json for 
editing
-  }
-
-  return undefined; // Only stops the debug session
-}
+import { showErrorWithConfigureButton } from "./ui/error-messages";
 
 export class LLDBDapConfigurationProvider
   implements vscode.DebugConfigurationProvider
diff --git a/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts 
b/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
new file mode 100644
index 0000000000000..0127ca5e288cc
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/ui/error-messages.ts
@@ -0,0 +1,49 @@
+import * as vscode from "vscode";
+
+/**
+ * Shows a modal when the debug adapter's path is not found
+ */
+export async function showLLDBDapNotFoundMessage(path?: string) {
+  const message =
+    path !== undefined
+      ? `Debug adapter path: ${path} is not a valid file`
+      : "Unable to find the path to the LLDB debug adapter executable.";
+  const openSettingsAction = "Open Settings";
+  const callbackValue = await vscode.window.showErrorMessage(
+    message,
+    { modal: true },
+    openSettingsAction,
+  );
+
+  if (openSettingsAction === callbackValue) {
+    vscode.commands.executeCommand(
+      "workbench.action.openSettings",
+      "lldb-dap.executable-path",
+    );
+  }
+}
+
+/**
+ * Shows an error message to the user that optionally allows them to open their
+ * launch.json to configure it.
+ *
+ * Expected to be used in the context of a {@link 
vscode.DebugConfigurationProvider}.
+ *
+ * @param message The error message to display to the user
+ * @returns `undefined` if the debug session should stop or `null` if the 
launch.json should be opened
+ */
+export async function showErrorWithConfigureButton(
+  message: string,
+): Promise<null | undefined> {
+  const userSelection = await vscode.window.showErrorMessage(
+    message,
+    { modal: true },
+    "Configure",
+  );
+
+  if (userSelection === "Configure") {
+    return null; // Stops the debug session and opens the launch.json for 
editing
+  }
+
+  return undefined; // Only stops the debug session
+}

>From 34f3875ddb00d1c963154c47a5696cc1d0e761f9 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Fri, 7 Mar 2025 18:09:15 -0500
Subject: [PATCH 5/5] mention that debug configuration properties override VS
 Code settings

---
 lldb/tools/lldb-dap/package.json | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index c3361ed3d0bf8..c2d1bceac1a07 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -177,14 +177,14 @@
               },
               "debugAdapterExecutable": {
                 "type": "string",
-                "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use."
+                "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use. Overrides any user or workspace settings."
               },
               "debugAdapterArgs": {
                 "type": "array",
                 "items": {
                   "type": "string"
                 },
-                "markdownDescription": "The list of additional arguments used 
to launch the debug adapter executable."
+                "markdownDescription": "The list of additional arguments used 
to launch the debug adapter executable. Overrides any user or workspace 
settings."
               },
               "program": {
                 "type": "string",
@@ -382,14 +382,14 @@
               },
               "debugAdapterExecutable": {
                 "type": "string",
-                "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use."
+                "markdownDescription": "The absolute path to the LLDB debug 
adapter executable to use. Overrides any user or workspace settings."
               },
               "debugAdapterArgs": {
                 "type": "array",
                 "items": {
                   "type": "string"
                 },
-                "markdownDescription": "The list of additional arguments used 
to launch the debug adapter executable."
+                "markdownDescription": "The list of additional arguments used 
to launch the debug adapter executable. Overrides any user or workspace 
settings."
               },
               "program": {
                 "type": "string",

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to