Skip to content

Commit 45201f1

Browse files
committed
[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
1 parent 16f3205 commit 45201f1

File tree

7 files changed

+193
-0
lines changed

7 files changed

+193
-0
lines changed

lldb/tools/lldb-dap/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS Support)
55
add_lldb_library(lldbDAP
66
Breakpoint.cpp
77
BreakpointBase.cpp
8+
ClientLauncher.cpp
89
CommandPlugins.cpp
910
DAP.cpp
1011
DAPError.cpp
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "ClientLauncher.h"
10+
#include "llvm/ADT/StringExtras.h"
11+
#include "llvm/ADT/StringSwitch.h"
12+
#include "llvm/Support/FormatVariadic.h"
13+
14+
using namespace lldb_dap;
15+
16+
std::optional<ClientLauncher::Client>
17+
ClientLauncher::GetClientFrom(llvm::StringRef str) {
18+
return llvm::StringSwitch<std::optional<ClientLauncher::Client>>(str.lower())
19+
.Case("vscode", ClientLauncher::VSCode)
20+
.Default(std::nullopt);
21+
}
22+
23+
std::unique_ptr<ClientLauncher>
24+
ClientLauncher::GetLauncher(ClientLauncher::Client client) {
25+
switch (client) {
26+
case ClientLauncher::VSCode:
27+
return std::make_unique<VSCodeLauncher>();
28+
}
29+
return nullptr;
30+
}
31+
32+
static std::string URLEncode(llvm::StringRef in) {
33+
std::string out;
34+
llvm::raw_string_ostream os(out);
35+
for (char c : in) {
36+
if (std::isalnum(c) || llvm::StringRef("-_.~").contains(c))
37+
os << c;
38+
else
39+
os << '%' << llvm::utohexstr(c, false, 2);
40+
}
41+
return os.str();
42+
}
43+
44+
llvm::Error VSCodeLauncher::Launch(const std::vector<llvm::StringRef> args) {
45+
std::vector<std::string> encoded_launch_args;
46+
for (llvm::StringRef arg : args)
47+
encoded_launch_args.push_back(URLEncode(arg));
48+
49+
const std::string args_str = llvm::join(args, "&args=");
50+
const std::string launch_url = llvm::formatv(
51+
"vscode://llvm-vs-code-extensions.lldb-dap/start?program={0}", args_str);
52+
const std::string command =
53+
llvm::formatv("code --open-url {0}", launch_url).str();
54+
55+
std::system(command.c_str());
56+
return llvm::Error::success();
57+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H
10+
#define LLDB_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H
11+
12+
#include "llvm/ADT/StringRef.h"
13+
#include "llvm/Support/Error.h"
14+
#include <vector>
15+
16+
namespace lldb_dap {
17+
18+
class ClientLauncher {
19+
public:
20+
enum Client {
21+
VSCode,
22+
};
23+
24+
virtual ~ClientLauncher() = default;
25+
virtual llvm::Error Launch(const std::vector<llvm::StringRef> args) = 0;
26+
27+
static std::optional<Client> GetClientFrom(llvm::StringRef str);
28+
static std::unique_ptr<ClientLauncher> GetLauncher(Client client);
29+
};
30+
31+
class VSCodeLauncher final : public ClientLauncher {
32+
public:
33+
using ClientLauncher::ClientLauncher;
34+
35+
llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
36+
};
37+
38+
} // namespace lldb_dap
39+
40+
#endif

lldb/tools/lldb-dap/tool/Options.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,11 @@ def connection_timeout: S<"connection-timeout">,
8282
"timeout is reached, the server will be closed and the process will exit. "
8383
"Not specifying this argument or specifying non-positive values will "
8484
"cause the server to wait for new connections indefinitely.">;
85+
86+
def client
87+
: S<"client">,
88+
MetaVarName<"<client>">,
89+
HelpText<
90+
"Use lldb-dap as a launcher for a curated number of DAP client.">;
91+
92+
def REM : R<["--"], "">;

lldb/tools/lldb-dap/tool/lldb-dap.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "ClientLauncher.h"
910
#include "DAP.h"
1011
#include "DAPLog.h"
1112
#include "EventHelper.h"
@@ -141,6 +142,12 @@ static void PrintHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {
141142
debugger to attach to the process.
142143
143144
lldb-dap -g
145+
146+
You can also use lldb-dap to launch a supported client, for example the
147+
LLDB-DAP Visual Studio Code extension.
148+
149+
lldb-dap --client vscode -- /path/to/binary <args>
150+
144151
)___";
145152
}
146153

@@ -150,6 +157,29 @@ static void PrintVersion() {
150157
llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n';
151158
}
152159

160+
static llvm::Error LaunchClient(const llvm::opt::InputArgList &args) {
161+
auto *client_arg = args.getLastArg(OPT_client);
162+
assert(client_arg && "must have client arg");
163+
164+
std::optional<ClientLauncher::Client> client =
165+
ClientLauncher::GetClientFrom(client_arg->getValue());
166+
if (!client)
167+
return llvm::createStringError(
168+
llvm::formatv("unsupported client: {0}", client_arg->getValue()));
169+
170+
std::vector<llvm::StringRef> launch_args;
171+
if (auto *arg = args.getLastArgNoClaim(OPT_REM)) {
172+
for (auto *value : arg->getValues()) {
173+
launch_args.push_back(value);
174+
}
175+
}
176+
177+
if (launch_args.empty())
178+
return llvm::createStringError("no launch arguments provided");
179+
180+
return ClientLauncher::GetLauncher(*client)->Launch(launch_args);
181+
}
182+
153183
#if not defined(_WIN32)
154184
struct FDGroup {
155185
int GetFlags() const {
@@ -541,6 +571,14 @@ int main(int argc, char *argv[]) {
541571
return EXIT_SUCCESS;
542572
}
543573

574+
if (input_args.hasArg(OPT_client)) {
575+
if (llvm::Error error = LaunchClient(input_args)) {
576+
llvm::WithColor::error() << llvm::toString(std::move(error)) << '\n';
577+
return EXIT_FAILURE;
578+
}
579+
return EXIT_SUCCESS;
580+
}
581+
544582
ReplMode default_repl_mode = ReplMode::Auto;
545583
if (input_args.hasArg(OPT_repl_mode)) {
546584
llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode);

lldb/unittests/DAP/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
add_lldb_unittest(DAPTests
2+
ClientLauncherTest.cpp
23
DAPErrorTest.cpp
34
DAPTest.cpp
45
DAPTypesTest.cpp
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "ClientLauncher.h"
10+
#include "llvm/ADT/StringRef.h"
11+
#include "gtest/gtest.h"
12+
#include <optional>
13+
14+
using namespace lldb_dap;
15+
using namespace llvm;
16+
17+
TEST(ClientLauncherTest, GetClientFromVSCode) {
18+
std::optional<ClientLauncher::Client> result =
19+
ClientLauncher::GetClientFrom("vscode");
20+
ASSERT_TRUE(result.has_value());
21+
EXPECT_EQ(ClientLauncher::VSCode, result.value());
22+
}
23+
24+
TEST(ClientLauncherTest, GetClientFromVSCodeUpperCase) {
25+
std::optional<ClientLauncher::Client> result =
26+
ClientLauncher::GetClientFrom("VSCODE");
27+
ASSERT_TRUE(result.has_value());
28+
EXPECT_EQ(ClientLauncher::VSCode, result.value());
29+
}
30+
31+
TEST(ClientLauncherTest, GetClientFromVSCodeMixedCase) {
32+
std::optional<ClientLauncher::Client> result =
33+
ClientLauncher::GetClientFrom("VSCode");
34+
ASSERT_TRUE(result.has_value());
35+
EXPECT_EQ(ClientLauncher::VSCode, result.value());
36+
}
37+
38+
TEST(ClientLauncherTest, GetClientFromInvalidString) {
39+
std::optional<ClientLauncher::Client> result =
40+
ClientLauncher::GetClientFrom("invalid");
41+
EXPECT_FALSE(result.has_value());
42+
}
43+
44+
TEST(ClientLauncherTest, GetClientFromEmptyString) {
45+
std::optional<ClientLauncher::Client> result =
46+
ClientLauncher::GetClientFrom("");
47+
EXPECT_FALSE(result.has_value());
48+
}

0 commit comments

Comments
 (0)