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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
