[Lldb-commits] [lldb] [lldb-dap] Add support for launching supported clients (PR #165941)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Mon Nov 3 12:08:42 PST 2025
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/165941
>From 11baa5c73b5441cd5c35296353b7a1dc28378c60 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 31 Oct 2025 16:34:58 -0700
Subject: [PATCH 1/3] [lldb-dap] Add support for launching supported clients
Support launching a supported DAP client using the lldb-dap binary.
Currently, only the official LLDB-DAP Visual Studio Code extension is
supported. It uses the VS Code launch URL format.
Fixes #125777
---
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
lldb/tools/lldb-dap/ClientLauncher.cpp | 57 +++++++++++++++++++++++
lldb/tools/lldb-dap/ClientLauncher.h | 40 ++++++++++++++++
lldb/tools/lldb-dap/tool/Options.td | 8 ++++
lldb/tools/lldb-dap/tool/lldb-dap.cpp | 38 +++++++++++++++
lldb/unittests/DAP/CMakeLists.txt | 1 +
lldb/unittests/DAP/ClientLauncherTest.cpp | 48 +++++++++++++++++++
7 files changed, 193 insertions(+)
create mode 100644 lldb/tools/lldb-dap/ClientLauncher.cpp
create mode 100644 lldb/tools/lldb-dap/ClientLauncher.h
create mode 100644 lldb/unittests/DAP/ClientLauncherTest.cpp
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index dd1bbbdddfc59..fa940b7b73943 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS Support)
add_lldb_library(lldbDAP
Breakpoint.cpp
BreakpointBase.cpp
+ ClientLauncher.cpp
CommandPlugins.cpp
DAP.cpp
DAPError.cpp
diff --git a/lldb/tools/lldb-dap/ClientLauncher.cpp b/lldb/tools/lldb-dap/ClientLauncher.cpp
new file mode 100644
index 0000000000000..301bbde61edb9
--- /dev/null
+++ b/lldb/tools/lldb-dap/ClientLauncher.cpp
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClientLauncher.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/FormatVariadic.h"
+
+using namespace lldb_dap;
+
+std::optional<ClientLauncher::Client>
+ClientLauncher::GetClientFrom(llvm::StringRef str) {
+ return llvm::StringSwitch<std::optional<ClientLauncher::Client>>(str.lower())
+ .Case("vscode", ClientLauncher::VSCode)
+ .Default(std::nullopt);
+}
+
+std::unique_ptr<ClientLauncher>
+ClientLauncher::GetLauncher(ClientLauncher::Client client) {
+ switch (client) {
+ case ClientLauncher::VSCode:
+ return std::make_unique<VSCodeLauncher>();
+ }
+ return nullptr;
+}
+
+static std::string URLEncode(llvm::StringRef in) {
+ std::string out;
+ llvm::raw_string_ostream os(out);
+ for (char c : in) {
+ if (std::isalnum(c) || llvm::StringRef("-_.~").contains(c))
+ os << c;
+ else
+ os << '%' << llvm::utohexstr(c, false, 2);
+ }
+ return os.str();
+}
+
+llvm::Error VSCodeLauncher::Launch(const std::vector<llvm::StringRef> args) {
+ std::vector<std::string> encoded_launch_args;
+ for (llvm::StringRef arg : args)
+ encoded_launch_args.push_back(URLEncode(arg));
+
+ const std::string args_str = llvm::join(args, "&args=");
+ const std::string launch_url = llvm::formatv(
+ "vscode://llvm-vs-code-extensions.lldb-dap/start?program={0}", args_str);
+ const std::string command =
+ llvm::formatv("code --open-url {0}", launch_url).str();
+
+ std::system(command.c_str());
+ return llvm::Error::success();
+}
diff --git a/lldb/tools/lldb-dap/ClientLauncher.h b/lldb/tools/lldb-dap/ClientLauncher.h
new file mode 100644
index 0000000000000..c9f249e00a8ca
--- /dev/null
+++ b/lldb/tools/lldb-dap/ClientLauncher.h
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H
+#define LLDB_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <vector>
+
+namespace lldb_dap {
+
+class ClientLauncher {
+public:
+ enum Client {
+ VSCode,
+ };
+
+ virtual ~ClientLauncher() = default;
+ virtual llvm::Error Launch(const std::vector<llvm::StringRef> args) = 0;
+
+ static std::optional<Client> GetClientFrom(llvm::StringRef str);
+ static std::unique_ptr<ClientLauncher> GetLauncher(Client client);
+};
+
+class VSCodeLauncher final : public ClientLauncher {
+public:
+ using ClientLauncher::ClientLauncher;
+
+ llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
+};
+
+} // namespace lldb_dap
+
+#endif
diff --git a/lldb/tools/lldb-dap/tool/Options.td b/lldb/tools/lldb-dap/tool/Options.td
index 5e9dd7a1d6419..339a64fed6c32 100644
--- a/lldb/tools/lldb-dap/tool/Options.td
+++ b/lldb/tools/lldb-dap/tool/Options.td
@@ -82,3 +82,11 @@ def connection_timeout: S<"connection-timeout">,
"timeout is reached, the server will be closed and the process will exit. "
"Not specifying this argument or specifying non-positive values will "
"cause the server to wait for new connections indefinitely.">;
+
+def client
+ : S<"client">,
+ MetaVarName<"<client>">,
+ HelpText<
+ "Use lldb-dap as a launcher for a curated number of DAP client.">;
+
+def REM : R<["--"], "">;
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index 45caa1a81059b..f10ed12344cbd 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
+#include "ClientLauncher.h"
#include "DAP.h"
#include "DAPLog.h"
#include "EventHelper.h"
@@ -141,6 +142,12 @@ static void PrintHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {
debugger to attach to the process.
lldb-dap -g
+
+ You can also use lldb-dap to launch a supported client, for example the
+ LLDB-DAP Visual Studio Code extension.
+
+ lldb-dap --client vscode -- /path/to/binary <args>
+
)___";
}
@@ -150,6 +157,29 @@ static void PrintVersion() {
llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n';
}
+static llvm::Error LaunchClient(const llvm::opt::InputArgList &args) {
+ auto *client_arg = args.getLastArg(OPT_client);
+ assert(client_arg && "must have client arg");
+
+ std::optional<ClientLauncher::Client> client =
+ ClientLauncher::GetClientFrom(client_arg->getValue());
+ if (!client)
+ return llvm::createStringError(
+ llvm::formatv("unsupported client: {0}", client_arg->getValue()));
+
+ std::vector<llvm::StringRef> launch_args;
+ if (auto *arg = args.getLastArgNoClaim(OPT_REM)) {
+ for (auto *value : arg->getValues()) {
+ launch_args.push_back(value);
+ }
+ }
+
+ if (launch_args.empty())
+ return llvm::createStringError("no launch arguments provided");
+
+ return ClientLauncher::GetLauncher(*client)->Launch(launch_args);
+}
+
#if not defined(_WIN32)
struct FDGroup {
int GetFlags() const {
@@ -541,6 +571,14 @@ int main(int argc, char *argv[]) {
return EXIT_SUCCESS;
}
+ if (input_args.hasArg(OPT_client)) {
+ if (llvm::Error error = LaunchClient(input_args)) {
+ llvm::WithColor::error() << llvm::toString(std::move(error)) << '\n';
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+ }
+
ReplMode default_repl_mode = ReplMode::Auto;
if (input_args.hasArg(OPT_repl_mode)) {
llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode);
diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt
index a08414c30e6cd..b1fdef18fddba 100644
--- a/lldb/unittests/DAP/CMakeLists.txt
+++ b/lldb/unittests/DAP/CMakeLists.txt
@@ -1,4 +1,5 @@
add_lldb_unittest(DAPTests
+ ClientLauncherTest.cpp
DAPErrorTest.cpp
DAPTest.cpp
DAPTypesTest.cpp
diff --git a/lldb/unittests/DAP/ClientLauncherTest.cpp b/lldb/unittests/DAP/ClientLauncherTest.cpp
new file mode 100644
index 0000000000000..3099982c694fb
--- /dev/null
+++ b/lldb/unittests/DAP/ClientLauncherTest.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClientLauncher.h"
+#include "llvm/ADT/StringRef.h"
+#include "gtest/gtest.h"
+#include <optional>
+
+using namespace lldb_dap;
+using namespace llvm;
+
+TEST(ClientLauncherTest, GetClientFromVSCode) {
+ std::optional<ClientLauncher::Client> result =
+ ClientLauncher::GetClientFrom("vscode");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(ClientLauncher::VSCode, result.value());
+}
+
+TEST(ClientLauncherTest, GetClientFromVSCodeUpperCase) {
+ std::optional<ClientLauncher::Client> result =
+ ClientLauncher::GetClientFrom("VSCODE");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(ClientLauncher::VSCode, result.value());
+}
+
+TEST(ClientLauncherTest, GetClientFromVSCodeMixedCase) {
+ std::optional<ClientLauncher::Client> result =
+ ClientLauncher::GetClientFrom("VSCode");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(ClientLauncher::VSCode, result.value());
+}
+
+TEST(ClientLauncherTest, GetClientFromInvalidString) {
+ std::optional<ClientLauncher::Client> result =
+ ClientLauncher::GetClientFrom("invalid");
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST(ClientLauncherTest, GetClientFromEmptyString) {
+ std::optional<ClientLauncher::Client> result =
+ ClientLauncher::GetClientFrom("");
+ EXPECT_FALSE(result.has_value());
+}
>From e595bff4506d61d8d3b8bf43eb3f4e04ab16be09 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Sun, 2 Nov 2025 11:45:16 -0800
Subject: [PATCH 2/3] Add unittest for URLEncode
---
lldb/tools/lldb-dap/ClientLauncher.cpp | 4 ++--
lldb/tools/lldb-dap/ClientLauncher.h | 1 +
lldb/unittests/DAP/ClientLauncherTest.cpp | 23 +++++++++++++++++++++++
3 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/lldb/tools/lldb-dap/ClientLauncher.cpp b/lldb/tools/lldb-dap/ClientLauncher.cpp
index 301bbde61edb9..9c369e80052d6 100644
--- a/lldb/tools/lldb-dap/ClientLauncher.cpp
+++ b/lldb/tools/lldb-dap/ClientLauncher.cpp
@@ -29,10 +29,10 @@ ClientLauncher::GetLauncher(ClientLauncher::Client client) {
return nullptr;
}
-static std::string URLEncode(llvm::StringRef in) {
+std::string VSCodeLauncher::URLEncode(llvm::StringRef str) {
std::string out;
llvm::raw_string_ostream os(out);
- for (char c : in) {
+ for (char c : str) {
if (std::isalnum(c) || llvm::StringRef("-_.~").contains(c))
os << c;
else
diff --git a/lldb/tools/lldb-dap/ClientLauncher.h b/lldb/tools/lldb-dap/ClientLauncher.h
index c9f249e00a8ca..deba6b60fe265 100644
--- a/lldb/tools/lldb-dap/ClientLauncher.h
+++ b/lldb/tools/lldb-dap/ClientLauncher.h
@@ -33,6 +33,7 @@ class VSCodeLauncher final : public ClientLauncher {
using ClientLauncher::ClientLauncher;
llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
+ static std::string URLEncode(llvm::StringRef str);
};
} // namespace lldb_dap
diff --git a/lldb/unittests/DAP/ClientLauncherTest.cpp b/lldb/unittests/DAP/ClientLauncherTest.cpp
index 3099982c694fb..dbaf9ee786336 100644
--- a/lldb/unittests/DAP/ClientLauncherTest.cpp
+++ b/lldb/unittests/DAP/ClientLauncherTest.cpp
@@ -46,3 +46,26 @@ TEST(ClientLauncherTest, GetClientFromEmptyString) {
ClientLauncher::GetClientFrom("");
EXPECT_FALSE(result.has_value());
}
+
+TEST(ClientLauncherTest, URLEncode) {
+ EXPECT_EQ("", VSCodeLauncher::URLEncode(""));
+ EXPECT_EQ(
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~",
+ VSCodeLauncher::URLEncode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST"
+ "UVWXYZ0123456789-_.~"));
+ EXPECT_EQ("hello%20world", VSCodeLauncher::URLEncode("hello world"));
+ EXPECT_EQ("hello%21%40%23%24", VSCodeLauncher::URLEncode("hello!@#$"));
+ EXPECT_EQ("%2Fpath%2Fto%2Ffile", VSCodeLauncher::URLEncode("/path/to/file"));
+ EXPECT_EQ("key%3Dvalue%26key2%3Dvalue2",
+ VSCodeLauncher::URLEncode("key=value&key2=value2"));
+ EXPECT_EQ("100%25complete", VSCodeLauncher::URLEncode("100%complete"));
+ EXPECT_EQ("file_name%20with%20spaces%20%26%20special%21.txt",
+ VSCodeLauncher::URLEncode("file_name with spaces & special!.txt"));
+ EXPECT_EQ("%00%01%02",
+ VSCodeLauncher::URLEncode(llvm::StringRef("\x00\x01\x02", 3)));
+ EXPECT_EQ("test-file_name.txt~",
+ VSCodeLauncher::URLEncode("test-file_name.txt~"));
+
+ // UTF-8 encoded characters should be percent-encoded byte by byte.
+ EXPECT_EQ("%C3%A9", VSCodeLauncher::URLEncode("é"));
+}
>From a33b5698916b36554fe131fc376a893f1b5d2dca Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 3 Nov 2025 12:08:25 -0800
Subject: [PATCH 3/3] Add vscode-url launcher
---
lldb/test/Shell/DAP/TestClientLauncher.test | 2 ++
lldb/tools/lldb-dap/ClientLauncher.cpp | 25 +++++++++++++++++----
lldb/tools/lldb-dap/ClientLauncher.h | 11 ++++++++-
3 files changed, 33 insertions(+), 5 deletions(-)
create mode 100644 lldb/test/Shell/DAP/TestClientLauncher.test
diff --git a/lldb/test/Shell/DAP/TestClientLauncher.test b/lldb/test/Shell/DAP/TestClientLauncher.test
new file mode 100644
index 0000000000000..a79a940da5a98
--- /dev/null
+++ b/lldb/test/Shell/DAP/TestClientLauncher.test
@@ -0,0 +1,2 @@
+# RUN: lldb-dap --client vscode-url -- /path/to/foo | FileCheck %s
+# CHECK: vscode://llvm-vs-code-extensions.lldb-dap/start?program=%2Fpath%2Fto%2Ffoo
diff --git a/lldb/tools/lldb-dap/ClientLauncher.cpp b/lldb/tools/lldb-dap/ClientLauncher.cpp
index 9c369e80052d6..4cac1d6346441 100644
--- a/lldb/tools/lldb-dap/ClientLauncher.cpp
+++ b/lldb/tools/lldb-dap/ClientLauncher.cpp
@@ -17,6 +17,7 @@ std::optional<ClientLauncher::Client>
ClientLauncher::GetClientFrom(llvm::StringRef str) {
return llvm::StringSwitch<std::optional<ClientLauncher::Client>>(str.lower())
.Case("vscode", ClientLauncher::VSCode)
+ .Case("vscode-url", ClientLauncher::VSCodeURL)
.Default(std::nullopt);
}
@@ -25,6 +26,8 @@ ClientLauncher::GetLauncher(ClientLauncher::Client client) {
switch (client) {
case ClientLauncher::VSCode:
return std::make_unique<VSCodeLauncher>();
+ case ClientLauncher::VSCodeURL:
+ return std::make_unique<VSCodeURLPrinter>();
}
return nullptr;
}
@@ -41,17 +44,31 @@ std::string VSCodeLauncher::URLEncode(llvm::StringRef str) {
return os.str();
}
-llvm::Error VSCodeLauncher::Launch(const std::vector<llvm::StringRef> args) {
+std::string
+VSCodeLauncher::GetLaunchURL(const std::vector<llvm::StringRef> args) const {
+ assert(!args.empty() && "empty launch args");
+
std::vector<std::string> encoded_launch_args;
for (llvm::StringRef arg : args)
encoded_launch_args.push_back(URLEncode(arg));
- const std::string args_str = llvm::join(args, "&args=");
- const std::string launch_url = llvm::formatv(
- "vscode://llvm-vs-code-extensions.lldb-dap/start?program={0}", args_str);
+ const std::string args_str = llvm::join(encoded_launch_args, "&args=");
+ return llvm::formatv(
+ "vscode://llvm-vs-code-extensions.lldb-dap/start?program={0}",
+ args_str)
+ .str();
+}
+
+llvm::Error VSCodeLauncher::Launch(const std::vector<llvm::StringRef> args) {
+ const std::string launch_url = GetLaunchURL(args);
const std::string command =
llvm::formatv("code --open-url {0}", launch_url).str();
std::system(command.c_str());
return llvm::Error::success();
}
+
+llvm::Error VSCodeURLPrinter::Launch(const std::vector<llvm::StringRef> args) {
+ llvm::outs() << GetLaunchURL(args) << '\n';
+ return llvm::Error::success();
+}
diff --git a/lldb/tools/lldb-dap/ClientLauncher.h b/lldb/tools/lldb-dap/ClientLauncher.h
index deba6b60fe265..780b178d2d6ef 100644
--- a/lldb/tools/lldb-dap/ClientLauncher.h
+++ b/lldb/tools/lldb-dap/ClientLauncher.h
@@ -19,6 +19,7 @@ class ClientLauncher {
public:
enum Client {
VSCode,
+ VSCodeURL,
};
virtual ~ClientLauncher() = default;
@@ -28,14 +29,22 @@ class ClientLauncher {
static std::unique_ptr<ClientLauncher> GetLauncher(Client client);
};
-class VSCodeLauncher final : public ClientLauncher {
+class VSCodeLauncher : public ClientLauncher {
public:
using ClientLauncher::ClientLauncher;
llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
+
+ std::string GetLaunchURL(const std::vector<llvm::StringRef> args) const;
static std::string URLEncode(llvm::StringRef str);
};
+class VSCodeURLPrinter : public VSCodeLauncher {
+ using VSCodeLauncher::VSCodeLauncher;
+
+ llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
+};
+
} // namespace lldb_dap
#endif
More information about the lldb-commits
mailing list