[lldb-dap] Creating a 'capabilities' event helper. (#142831)
This adds a new 'CapabilitiesEventBody' type for having a well structured type for the event and updates the 'restart' request to dynamically set their capabilities.
This commit is contained in:
@@ -135,6 +135,10 @@ class Source(object):
|
||||
return source_dict
|
||||
|
||||
|
||||
class NotSupportedError(KeyError):
|
||||
"""Raised if a feature is not supported due to its capabilities."""
|
||||
|
||||
|
||||
class DebugCommunication(object):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -153,7 +157,7 @@ class DebugCommunication(object):
|
||||
self.recv_thread = threading.Thread(target=self._read_packet_thread)
|
||||
self.process_event_body = None
|
||||
self.exit_status: Optional[int] = None
|
||||
self.initialize_body = None
|
||||
self.capabilities: dict[str, Any] = {}
|
||||
self.progress_events: list[Event] = []
|
||||
self.reverse_requests = []
|
||||
self.sequence = 1
|
||||
@@ -300,6 +304,9 @@ class DebugCommunication(object):
|
||||
elif event == "breakpoint":
|
||||
# Breakpoint events are sent when a breakpoint is resolved
|
||||
self._update_verified_breakpoints([body["breakpoint"]])
|
||||
elif event == "capabilities":
|
||||
# Update the capabilities with new ones from the event.
|
||||
self.capabilities.update(body["capabilities"])
|
||||
|
||||
elif packet_type == "response":
|
||||
if packet["command"] == "disconnect":
|
||||
@@ -487,13 +494,13 @@ class DebugCommunication(object):
|
||||
raise ValueError("didn't get terminated event")
|
||||
return event_dict
|
||||
|
||||
def get_initialize_value(self, key):
|
||||
def get_capability(self, key):
|
||||
"""Get a value for the given key if it there is a key/value pair in
|
||||
the "initialize" request response body.
|
||||
the capabilities reported by the adapter.
|
||||
"""
|
||||
if self.initialize_body and key in self.initialize_body:
|
||||
return self.initialize_body[key]
|
||||
return None
|
||||
if key in self.capabilities:
|
||||
return self.capabilities[key]
|
||||
raise NotSupportedError(key)
|
||||
|
||||
def get_threads(self):
|
||||
if self.threads is None:
|
||||
@@ -759,6 +766,9 @@ class DebugCommunication(object):
|
||||
return response
|
||||
|
||||
def request_restart(self, restartArguments=None):
|
||||
if self.exit_status is not None:
|
||||
raise ValueError("request_restart called after process exited")
|
||||
self.get_capability("supportsRestartRequest")
|
||||
command_dict = {
|
||||
"command": "restart",
|
||||
"type": "request",
|
||||
@@ -866,7 +876,7 @@ class DebugCommunication(object):
|
||||
response = self.send_recv(command_dict)
|
||||
if response:
|
||||
if "body" in response:
|
||||
self.initialize_body = response["body"]
|
||||
self.capabilities = response["body"]
|
||||
return response
|
||||
|
||||
def request_launch(
|
||||
@@ -971,6 +981,7 @@ class DebugCommunication(object):
|
||||
def request_stepInTargets(self, frameId):
|
||||
if self.exit_status is not None:
|
||||
raise ValueError("request_stepInTargets called after process exited")
|
||||
self.get_capability("supportsStepInTargetsRequest")
|
||||
args_dict = {"frameId": frameId}
|
||||
command_dict = {
|
||||
"command": "stepInTargets",
|
||||
|
||||
@@ -128,6 +128,16 @@ class DAPTestCaseBase(TestBase):
|
||||
time.sleep(0.5)
|
||||
return False
|
||||
|
||||
def assertCapabilityIsSet(self, key: str, msg: Optional[str] = None) -> None:
|
||||
"""Assert that given capability is set in the client."""
|
||||
self.assertIn(key, self.dap_server.capabilities, msg)
|
||||
self.assertEqual(self.dap_server.capabilities[key], True, msg)
|
||||
|
||||
def assertCapabilityIsNotSet(self, key: str, msg: Optional[str] = None) -> None:
|
||||
"""Assert that given capability is not set in the client."""
|
||||
if key in self.dap_server.capabilities:
|
||||
self.assertEqual(self.dap_server.capabilities[key], False, msg)
|
||||
|
||||
def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
|
||||
"""Wait for the process we are debugging to stop, and verify we hit
|
||||
any breakpoint location in the "breakpoint_ids" array.
|
||||
|
||||
@@ -566,7 +566,7 @@ class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
|
||||
)
|
||||
version_eval_output = version_eval_response["body"]["result"]
|
||||
|
||||
version_string = self.dap_server.get_initialize_value("$__lldb_version")
|
||||
version_string = self.dap_server.get_capability("$__lldb_version")
|
||||
self.assertEqual(
|
||||
version_eval_output.splitlines(),
|
||||
version_string.splitlines(),
|
||||
|
||||
@@ -67,6 +67,7 @@ add_lldb_library(lldbDAP
|
||||
Handler/VariablesRequestHandler.cpp
|
||||
|
||||
Protocol/ProtocolBase.cpp
|
||||
Protocol/ProtocolEvents.cpp
|
||||
Protocol/ProtocolTypes.cpp
|
||||
Protocol/ProtocolRequests.cpp
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "DAPError.h"
|
||||
#include "JSONUtils.h"
|
||||
#include "LLDBUtils.h"
|
||||
#include "Protocol/ProtocolEvents.h"
|
||||
#include "Protocol/ProtocolTypes.h"
|
||||
#include "lldb/API/SBFileSpec.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
|
||||
@@ -36,6 +38,22 @@ static void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) {
|
||||
dap.SendJSON(llvm::json::Value(std::move(event)));
|
||||
}
|
||||
|
||||
void SendTargetBasedCapabilities(DAP &dap) {
|
||||
if (!dap.target.IsValid())
|
||||
return;
|
||||
|
||||
protocol::CapabilitiesEventBody body;
|
||||
|
||||
// We only support restarting launch requests not attach requests.
|
||||
if (dap.last_launch_request)
|
||||
body.capabilities.supportedFeatures.insert(
|
||||
protocol::eAdapterFeatureRestartRequest);
|
||||
|
||||
// Only notify the client if supportedFeatures changed.
|
||||
if (!body.capabilities.supportedFeatures.empty())
|
||||
dap.Send(protocol::Event{"capabilities", body});
|
||||
}
|
||||
|
||||
// "ProcessEvent": {
|
||||
// "allOf": [
|
||||
// { "$ref": "#/definitions/Event" },
|
||||
|
||||
@@ -17,6 +17,9 @@ struct DAP;
|
||||
|
||||
enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
|
||||
|
||||
/// Update capabilities based on the configured target.
|
||||
void SendTargetBasedCapabilities(DAP &dap);
|
||||
|
||||
void SendProcessEvent(DAP &dap, LaunchMethod launch_method);
|
||||
|
||||
llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry = false);
|
||||
|
||||
@@ -41,6 +41,11 @@ ConfigurationDoneRequestHandler::Run(const ConfigurationDoneArguments &) const {
|
||||
"any debugger command scripts are not resuming the process during the "
|
||||
"launch sequence.");
|
||||
|
||||
// Waiting until 'configurationDone' to send target based capabilities in case
|
||||
// the launch or attach scripts adjust the target. The initial dummy target
|
||||
// may have different capabilities than the final target.
|
||||
SendTargetBasedCapabilities(dap);
|
||||
|
||||
// Clients can request a baseline of currently existing threads after
|
||||
// we acknowledge the configurationDone request.
|
||||
// Client requests the baseline of currently existing threads after
|
||||
|
||||
@@ -334,9 +334,6 @@ class RestartRequestHandler : public LegacyRequestHandler {
|
||||
public:
|
||||
using LegacyRequestHandler::LegacyRequestHandler;
|
||||
static llvm::StringLiteral GetCommand() { return "restart"; }
|
||||
FeatureSet GetSupportedFeatures() const override {
|
||||
return {protocol::eAdapterFeatureRestartRequest};
|
||||
}
|
||||
void operator()(const llvm::json::Object &request) const override;
|
||||
};
|
||||
|
||||
|
||||
@@ -69,23 +69,6 @@ void RestartRequestHandler::operator()(
|
||||
dap.SendJSON(llvm::json::Value(std::move(response)));
|
||||
return;
|
||||
}
|
||||
// Check if we were in a "launch" session or an "attach" session.
|
||||
//
|
||||
// Restarting is not well defined when we started the session by attaching to
|
||||
// an existing process, because we don't know how the process was started, so
|
||||
// we don't support it.
|
||||
//
|
||||
// Note that when using runInTerminal we're technically attached, but it's an
|
||||
// implementation detail. The adapter *did* launch the process in response to
|
||||
// a "launch" command, so we can still stop it and re-run it. This is why we
|
||||
// don't just check `dap.is_attach`.
|
||||
if (!dap.last_launch_request) {
|
||||
response["success"] = llvm::json::Value(false);
|
||||
EmplaceSafeString(response, "message",
|
||||
"Restarting an \"attach\" session is not supported.");
|
||||
dap.SendJSON(llvm::json::Value(std::move(response)));
|
||||
return;
|
||||
}
|
||||
|
||||
const llvm::json::Object *arguments = request.getObject("arguments");
|
||||
if (arguments) {
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
|
||||
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
|
||||
#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_BASE_H
|
||||
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_BASE_H
|
||||
|
||||
#include "llvm/Support/JSON.h"
|
||||
#include <cstdint>
|
||||
|
||||
20
lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
Normal file
20
lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
//===-- ProtocolEvents.cpp ------------------------------------------------===//
|
||||
//
|
||||
// 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 "Protocol/ProtocolEvents.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
namespace lldb_dap::protocol {
|
||||
|
||||
json::Value toJSON(const CapabilitiesEventBody &CEB) {
|
||||
return json::Object{{"capabilities", CEB.capabilities}};
|
||||
}
|
||||
|
||||
} // namespace lldb_dap::protocol
|
||||
46
lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
Normal file
46
lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
Normal file
@@ -0,0 +1,46 @@
|
||||
//===-- ProtocolEvents.h --------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file contains POD structs based on the DAP specification at
|
||||
// https://microsoft.github.io/debug-adapter-protocol/specification
|
||||
//
|
||||
// This is not meant to be a complete implementation, new interfaces are added
|
||||
// when they're needed.
|
||||
//
|
||||
// Each struct has a toJSON and fromJSON function, that converts between
|
||||
// the struct and a JSON representation. (See JSON.h)
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H
|
||||
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H
|
||||
|
||||
#include "Protocol/ProtocolTypes.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
|
||||
namespace lldb_dap::protocol {
|
||||
|
||||
/// The event indicates that one or more capabilities have changed.
|
||||
///
|
||||
/// Since the capabilities are dependent on the client and its UI, it might not
|
||||
/// be possible to change that at random times (or too late).
|
||||
///
|
||||
/// Consequently this event has a hint characteristic: a client can only be
|
||||
/// expected to make a 'best effort' in honoring individual capabilities but
|
||||
/// there are no guarantees.
|
||||
///
|
||||
/// Only changed capabilities need to be included, all other capabilities keep
|
||||
/// their values.
|
||||
struct CapabilitiesEventBody {
|
||||
Capabilities capabilities;
|
||||
};
|
||||
llvm::json::Value toJSON(const CapabilitiesEventBody &);
|
||||
|
||||
} // end namespace lldb_dap::protocol
|
||||
|
||||
#endif
|
||||
@@ -7,12 +7,14 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "Protocol/ProtocolTypes.h"
|
||||
#include "Protocol/ProtocolEvents.h"
|
||||
#include "Protocol/ProtocolRequests.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
#include "llvm/Testing/Support/Error.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <optional>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace lldb;
|
||||
@@ -666,3 +668,21 @@ TEST(ProtocolTypesTest, ThreadResponseBody) {
|
||||
// Validate toJSON
|
||||
EXPECT_EQ(json, pp(body));
|
||||
}
|
||||
|
||||
TEST(ProtocolTypesTest, CapabilitiesEventBody) {
|
||||
Capabilities capabilities;
|
||||
capabilities.supportedFeatures = {
|
||||
eAdapterFeatureANSIStyling,
|
||||
eAdapterFeatureBreakpointLocationsRequest,
|
||||
};
|
||||
CapabilitiesEventBody body;
|
||||
body.capabilities = capabilities;
|
||||
StringRef json = R"({
|
||||
"capabilities": {
|
||||
"supportsANSIStyling": true,
|
||||
"supportsBreakpointLocationsRequest": true
|
||||
}
|
||||
})";
|
||||
// Validate toJSON
|
||||
EXPECT_EQ(json, pp(body));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user