Skip to content

Commit 1621486

Browse files
authored
[lldb-dap] Add support for launching supported clients (#165941)
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. Here's an example: ``` lldb-dap --client vscode -- /path/to/exe foo bar ``` This will open the following URL with `code --open-url`: ``` vscode://llvm-vs-code-extensions.lldb-dap/start?program=/path/to/exe&args=foo&arg=bar ``` Fixes #125777
1 parent 6fe3ecc commit 1621486

File tree

8 files changed

+245
-0
lines changed

8 files changed

+245
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# RUN: lldb-dap --client vscode-url -- /path/to/foo | FileCheck %s
2+
# CHECK: vscode://llvm-vs-code-extensions.lldb-dap/start?program=%2Fpath%2Fto%2Ffoo

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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
.Case("vscode-url", ClientLauncher::VSCodeURL)
21+
.Default(std::nullopt);
22+
}
23+
24+
std::unique_ptr<ClientLauncher>
25+
ClientLauncher::GetLauncher(ClientLauncher::Client client) {
26+
switch (client) {
27+
case ClientLauncher::VSCode:
28+
return std::make_unique<VSCodeLauncher>();
29+
case ClientLauncher::VSCodeURL:
30+
return std::make_unique<VSCodeURLPrinter>();
31+
}
32+
return nullptr;
33+
}
34+
35+
std::string VSCodeLauncher::URLEncode(llvm::StringRef str) {
36+
std::string out;
37+
llvm::raw_string_ostream os(out);
38+
for (char c : str) {
39+
if (std::isalnum(c) || llvm::StringRef("-_.~").contains(c))
40+
os << c;
41+
else
42+
os << '%' << llvm::utohexstr(c, false, 2);
43+
}
44+
return os.str();
45+
}
46+
47+
std::string
48+
VSCodeLauncher::GetLaunchURL(const std::vector<llvm::StringRef> args) const {
49+
assert(!args.empty() && "empty launch args");
50+
51+
std::vector<std::string> encoded_launch_args;
52+
for (llvm::StringRef arg : args)
53+
encoded_launch_args.push_back(URLEncode(arg));
54+
55+
const std::string args_str = llvm::join(encoded_launch_args, "&args=");
56+
return llvm::formatv(
57+
"vscode://llvm-vs-code-extensions.lldb-dap/start?program={0}",
58+
args_str)
59+
.str();
60+
}
61+
62+
llvm::Error VSCodeLauncher::Launch(const std::vector<llvm::StringRef> args) {
63+
const std::string launch_url = GetLaunchURL(args);
64+
const std::string command =
65+
llvm::formatv("code --open-url {0}", launch_url).str();
66+
67+
std::system(command.c_str());
68+
return llvm::Error::success();
69+
}
70+
71+
llvm::Error VSCodeURLPrinter::Launch(const std::vector<llvm::StringRef> args) {
72+
llvm::outs() << GetLaunchURL(args) << '\n';
73+
return llvm::Error::success();
74+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
VSCodeURL,
23+
};
24+
25+
virtual ~ClientLauncher() = default;
26+
virtual llvm::Error Launch(const std::vector<llvm::StringRef> args) = 0;
27+
28+
static std::optional<Client> GetClientFrom(llvm::StringRef str);
29+
static std::unique_ptr<ClientLauncher> GetLauncher(Client client);
30+
};
31+
32+
class VSCodeLauncher : public ClientLauncher {
33+
public:
34+
using ClientLauncher::ClientLauncher;
35+
36+
llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
37+
38+
std::string GetLaunchURL(const std::vector<llvm::StringRef> args) const;
39+
static std::string URLEncode(llvm::StringRef str);
40+
};
41+
42+
class VSCodeURLPrinter : public VSCodeLauncher {
43+
using VSCodeLauncher::VSCodeLauncher;
44+
45+
llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
46+
};
47+
48+
} // namespace lldb_dap
49+
50+
#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: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
}
49+
50+
TEST(ClientLauncherTest, URLEncode) {
51+
EXPECT_EQ("", VSCodeLauncher::URLEncode(""));
52+
EXPECT_EQ(
53+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~",
54+
VSCodeLauncher::URLEncode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST"
55+
"UVWXYZ0123456789-_.~"));
56+
EXPECT_EQ("hello%20world", VSCodeLauncher::URLEncode("hello world"));
57+
EXPECT_EQ("hello%21%40%23%24", VSCodeLauncher::URLEncode("hello!@#$"));
58+
EXPECT_EQ("%2Fpath%2Fto%2Ffile", VSCodeLauncher::URLEncode("/path/to/file"));
59+
EXPECT_EQ("key%3Dvalue%26key2%3Dvalue2",
60+
VSCodeLauncher::URLEncode("key=value&key2=value2"));
61+
EXPECT_EQ("100%25complete", VSCodeLauncher::URLEncode("100%complete"));
62+
EXPECT_EQ("file_name%20with%20spaces%20%26%20special%21.txt",
63+
VSCodeLauncher::URLEncode("file_name with spaces & special!.txt"));
64+
EXPECT_EQ("%00%01%02",
65+
VSCodeLauncher::URLEncode(llvm::StringRef("\x00\x01\x02", 3)));
66+
EXPECT_EQ("test-file_name.txt~",
67+
VSCodeLauncher::URLEncode("test-file_name.txt~"));
68+
69+
// UTF-8 encoded characters should be percent-encoded byte by byte.
70+
EXPECT_EQ("%C3%A9", VSCodeLauncher::URLEncode("é"));
71+
}

0 commit comments

Comments
 (0)