Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lldb/test/Shell/DAP/TestClientLauncher.test
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions lldb/tools/lldb-dap/ClientLauncher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// 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)
Copy link
Member

@vogelsgesang vogelsgesang Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be great to also have a "url" / "terminal" / "stdout" client which simply prints the URL to stdout. That would allow me to also use it with custom scripts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although 🤔 I guess that even that would already be VSCode specific... So I guess it would have to be "vscode-url" instead of just "url"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. This also made a good candidate for a Shell test.

.Case("vscode-url", ClientLauncher::VSCodeURL)
.Default(std::nullopt);
}

std::unique_ptr<ClientLauncher>
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;
}

std::string VSCodeLauncher::URLEncode(llvm::StringRef str) {
std::string out;
llvm::raw_string_ostream os(out);
for (char c : str) {
if (std::isalnum(c) || llvm::StringRef("-_.~").contains(c))
os << c;
else
os << '%' << llvm::utohexstr(c, false, 2);
}
return os.str();
}

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(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();
}
50 changes: 50 additions & 0 deletions lldb/tools/lldb-dap/ClientLauncher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//===----------------------------------------------------------------------===//
//
// 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,
VSCodeURL,
};

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 : 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
8 changes: 8 additions & 0 deletions lldb/tools/lldb-dap/tool/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<["--"], "">;
38 changes: 38 additions & 0 deletions lldb/tools/lldb-dap/tool/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//

#include "ClientLauncher.h"
#include "DAP.h"
#include "DAPLog.h"
#include "EventHelper.h"
Expand Down Expand Up @@ -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>

)___";
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/DAP/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
add_lldb_unittest(DAPTests
ClientLauncherTest.cpp
DAPErrorTest.cpp
DAPTest.cpp
DAPTypesTest.cpp
Expand Down
71 changes: 71 additions & 0 deletions lldb/unittests/DAP/ClientLauncherTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also include a URLEncode test? Either directly or by checking with calls to VSCodeLauncher::Launch?

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());
}

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("é"));
}
Loading