mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 18:49:37 +00:00
Add debug adapter to DAP
Change-Id: I5bacd460175a5b2e86326f7501d7f250bbe3ab0c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292060 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Helin Shiah <helinx@google.com>
This commit is contained in:
parent
86d754690a
commit
5ef021b116
|
@ -11,6 +11,7 @@ import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
|
||||||
import '../dds.dart';
|
import '../dds.dart';
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
|
import 'dap/adapters/dds_hosted_adapter.dart';
|
||||||
import 'dds_impl.dart';
|
import 'dds_impl.dart';
|
||||||
import 'rpc_error_codes.dart';
|
import 'rpc_error_codes.dart';
|
||||||
import 'stream_manager.dart';
|
import 'stream_manager.dart';
|
||||||
|
@ -252,6 +253,11 @@ class DartDevelopmentServiceClient {
|
||||||
dds.packageUriConverter.convert,
|
dds.packageUriConverter.convert,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_clientPeer.registerMethod(
|
||||||
|
'handleDap',
|
||||||
|
(parameters) => dds.dapHandler.handle(adapter, parameters),
|
||||||
|
);
|
||||||
|
|
||||||
// When invoked within a fallback, the next fallback will start executing.
|
// When invoked within a fallback, the next fallback will start executing.
|
||||||
// The final fallback forwards the request to the VM service directly.
|
// The final fallback forwards the request to the VM service directly.
|
||||||
Never nextFallback() => throw json_rpc.RpcException.methodNotFound('');
|
Never nextFallback() => throw json_rpc.RpcException.methodNotFound('');
|
||||||
|
@ -334,4 +340,5 @@ class DartDevelopmentServiceClient {
|
||||||
final Set<String> profilerUserTagFilters = {};
|
final Set<String> profilerUserTagFilters = {};
|
||||||
final json_rpc.Peer _vmServicePeer;
|
final json_rpc.Peer _vmServicePeer;
|
||||||
late json_rpc.Peer _clientPeer;
|
late json_rpc.Peer _clientPeer;
|
||||||
|
final DdsHostedAdapter adapter = DdsHostedAdapter();
|
||||||
}
|
}
|
||||||
|
|
|
@ -631,7 +631,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger?.call('Connecting to debugger at $uri');
|
logger?.call('Connecting to debugger at $uri');
|
||||||
sendOutput('console', 'Connecting to VM Service at $uri\n');
|
sendConsoleOutput('Connecting to VM Service at $uri\n');
|
||||||
final vmService = await _vmServiceConnectUri(uri.toString());
|
final vmService = await _vmServiceConnectUri(uri.toString());
|
||||||
logger?.call('Connected to debugger at $uri!');
|
logger?.call('Connected to debugger at $uri!');
|
||||||
|
|
||||||
|
@ -1272,6 +1272,11 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
||||||
sendResponse(ScopesResponseBody(scopes: scopes));
|
sendResponse(ScopesResponseBody(scopes: scopes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends an OutputEvent with a newline to the console.
|
||||||
|
void sendConsoleOutput(String message) {
|
||||||
|
sendOutput('console', '\n$message');
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends an OutputEvent (without a newline, since calls to this method
|
/// Sends an OutputEvent (without a newline, since calls to this method
|
||||||
/// may be using buffered data that is not split cleanly on newlines).
|
/// may be using buffered data that is not split cleanly on newlines).
|
||||||
///
|
///
|
||||||
|
|
124
pkg/dds/lib/src/dap/adapters/dds_hosted_adapter.dart
Normal file
124
pkg/dds/lib/src/dap/adapters/dds_hosted_adapter.dart
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:async/async.dart';
|
||||||
|
import 'package:vm_service/vm_service.dart' as vm;
|
||||||
|
|
||||||
|
import '../exceptions.dart';
|
||||||
|
import '../protocol_generated.dart';
|
||||||
|
import '../protocol_stream.dart';
|
||||||
|
import 'dart.dart';
|
||||||
|
import 'mixins.dart';
|
||||||
|
|
||||||
|
/// A DAP Debug Adapter for attaching to already-running Dart and Flutter applications.
|
||||||
|
class DdsHostedAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||||
|
DartAttachRequestArguments>
|
||||||
|
with PidTracker, VmServiceInfoFileUtils, PackageConfigUtils, TestAdapter {
|
||||||
|
Uri? ddsUri;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final parseLaunchArgs = DartLaunchRequestArguments.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final parseAttachArgs = DartAttachRequestArguments.fromJson;
|
||||||
|
|
||||||
|
DdsHostedAdapter()
|
||||||
|
: super(
|
||||||
|
// TODO(helin24): Make channel optional for base adapter class.
|
||||||
|
ByteStreamServerChannel(
|
||||||
|
Stream.empty(),
|
||||||
|
NullStreamSink(),
|
||||||
|
(message) {},
|
||||||
|
),
|
||||||
|
ipv6: true,
|
||||||
|
enableDds: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Whether the VM Service closing should be used as a signal to terminate the
|
||||||
|
/// debug session.
|
||||||
|
///
|
||||||
|
/// True here because we no longer need this adapter once the VM service has closed.
|
||||||
|
@override
|
||||||
|
bool get terminateOnVmServiceClose => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> debuggerConnected(vm.VM vmInfo) async {}
|
||||||
|
|
||||||
|
/// Called by [disconnectRequest] to request that we forcefully shut down the
|
||||||
|
/// app being run (or in the case of an attach, disconnect).
|
||||||
|
@override
|
||||||
|
Future<void> disconnectImpl() async {
|
||||||
|
await handleDetach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called by [launchRequest] to request that we actually start the app to be
|
||||||
|
/// run/debugged.
|
||||||
|
///
|
||||||
|
/// For debugging, this should start paused, connect to the VM Service, set
|
||||||
|
/// breakpoints, and resume.
|
||||||
|
@override
|
||||||
|
Future<void> launchImpl() async {
|
||||||
|
sendConsoleOutput(
|
||||||
|
'Launch is not supported for the attach only adapter',
|
||||||
|
);
|
||||||
|
handleSessionTerminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called by [attachRequest] to request that we actually connect to the app
|
||||||
|
/// to be debugged.
|
||||||
|
@override
|
||||||
|
Future<void> attachImpl() async {
|
||||||
|
final args = this.args as DartAttachRequestArguments;
|
||||||
|
final vmServiceUri = args.vmServiceUri;
|
||||||
|
|
||||||
|
if (vmServiceUri == null) {
|
||||||
|
sendConsoleOutput(
|
||||||
|
'To attach, provide vmServiceUri',
|
||||||
|
);
|
||||||
|
handleSessionTerminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (vmServiceUri != ddsUri.toString()) {
|
||||||
|
sendConsoleOutput(
|
||||||
|
'To use the attach-only adapter, VM service URI must match DDS URI',
|
||||||
|
);
|
||||||
|
handleSessionTerminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(helin24): In this method, we only need to verify that the DDS URI
|
||||||
|
// matches the VM service URI. The DDS URI isn't really needed because this
|
||||||
|
// adapter is running in the same process. We need to refactor so that we
|
||||||
|
// call DDS/VM service methods directly instead of using the websocket.
|
||||||
|
unawaited(connectDebugger(ddsUri!));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called by [terminateRequest] to request that we gracefully shut down the
|
||||||
|
/// app being run (or in the case of an attach, disconnect).
|
||||||
|
@override
|
||||||
|
Future<void> terminateImpl() async {
|
||||||
|
await handleDetach();
|
||||||
|
terminatePids(ProcessSignal.sigterm);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> handleMessage(String message) async {
|
||||||
|
final potentialException =
|
||||||
|
DebugAdapterException('Message does not conform to DAP spec: $message');
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Map<String, Object?> json = jsonDecode(message);
|
||||||
|
final type = json['type'] as String;
|
||||||
|
if (type == 'request') {
|
||||||
|
return handleIncomingRequest(Request.fromJson(json));
|
||||||
|
// TODO(helin24): Handle event and response?
|
||||||
|
}
|
||||||
|
throw potentialException;
|
||||||
|
} catch (e) {
|
||||||
|
throw potentialException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,6 +107,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
||||||
Request request,
|
Request request,
|
||||||
RequestHandler<TArg, TResp> handler,
|
RequestHandler<TArg, TResp> handler,
|
||||||
TArg Function(Map<String, Object?>) fromJson,
|
TArg Function(Map<String, Object?>) fromJson,
|
||||||
|
Completer<Response> completer,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final args = request.arguments != null
|
final args = request.arguments != null
|
||||||
|
@ -130,7 +131,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
||||||
command: request.command,
|
command: request.command,
|
||||||
body: responseBody,
|
body: responseBody,
|
||||||
);
|
);
|
||||||
_channel.sendResponse(response);
|
completer.complete(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
await handler(request, args, sendResponse);
|
await handler(request, args, sendResponse);
|
||||||
|
@ -145,7 +146,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
||||||
message: e is DebugAdapterException ? e.message : '$e',
|
message: e is DebugAdapterException ? e.message : '$e',
|
||||||
body: '$s',
|
body: '$s',
|
||||||
);
|
);
|
||||||
_channel.sendResponse(response);
|
completer.complete(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +271,7 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
||||||
/// Handles incoming messages from the client editor.
|
/// Handles incoming messages from the client editor.
|
||||||
void _handleIncomingMessage(ProtocolMessage message) {
|
void _handleIncomingMessage(ProtocolMessage message) {
|
||||||
if (message is Request) {
|
if (message is Request) {
|
||||||
_handleIncomingRequest(message);
|
handleIncomingRequest(message).then(_channel.sendResponse);
|
||||||
} else if (message is Response) {
|
} else if (message is Response) {
|
||||||
_handleIncomingResponse(message);
|
_handleIncomingResponse(message);
|
||||||
} else {
|
} else {
|
||||||
|
@ -279,80 +280,152 @@ abstract class BaseDebugAdapter<TLaunchArgs extends LaunchRequestArguments,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles an incoming request, calling the appropriate method to handle it.
|
/// Handles an incoming request, calling the appropriate method to handle it.
|
||||||
void _handleIncomingRequest(Request request) {
|
Future<Response> handleIncomingRequest(Request request) {
|
||||||
|
final completer = Completer<Response>();
|
||||||
|
|
||||||
if (request.command == 'initialize') {
|
if (request.command == 'initialize') {
|
||||||
handle(request, initializeRequest, InitializeRequestArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
initializeRequest,
|
||||||
|
InitializeRequestArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'launch') {
|
} else if (request.command == 'launch') {
|
||||||
handle(request, _withVoidResponse(launchRequest), parseLaunchArgs);
|
handle(
|
||||||
|
request,
|
||||||
|
_withVoidResponse(launchRequest),
|
||||||
|
parseLaunchArgs,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'attach') {
|
} else if (request.command == 'attach') {
|
||||||
handle(request, _withVoidResponse(attachRequest), parseAttachArgs);
|
handle(
|
||||||
|
request,
|
||||||
|
_withVoidResponse(attachRequest),
|
||||||
|
parseAttachArgs,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'restart') {
|
} else if (request.command == 'restart') {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
_withVoidResponse(restartRequest),
|
_withVoidResponse(restartRequest),
|
||||||
_allowNullArg(RestartArguments.fromJson),
|
_allowNullArg(RestartArguments.fromJson),
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
} else if (request.command == 'terminate') {
|
} else if (request.command == 'terminate') {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
_withVoidResponse(terminateRequest),
|
_withVoidResponse(terminateRequest),
|
||||||
_allowNullArg(TerminateArguments.fromJson),
|
_allowNullArg(TerminateArguments.fromJson),
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
} else if (request.command == 'disconnect') {
|
} else if (request.command == 'disconnect') {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
_withVoidResponse(disconnectRequest),
|
_withVoidResponse(disconnectRequest),
|
||||||
_allowNullArg(DisconnectArguments.fromJson),
|
_allowNullArg(DisconnectArguments.fromJson),
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
} else if (request.command == 'configurationDone') {
|
} else if (request.command == 'configurationDone') {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
_withVoidResponse(configurationDoneRequest),
|
_withVoidResponse(configurationDoneRequest),
|
||||||
_allowNullArg(ConfigurationDoneArguments.fromJson),
|
_allowNullArg(ConfigurationDoneArguments.fromJson),
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
} else if (request.command == 'setBreakpoints') {
|
} else if (request.command == 'setBreakpoints') {
|
||||||
handle(request, setBreakpointsRequest, SetBreakpointsArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
setBreakpointsRequest,
|
||||||
|
SetBreakpointsArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'setExceptionBreakpoints') {
|
} else if (request.command == 'setExceptionBreakpoints') {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
setExceptionBreakpointsRequest,
|
setExceptionBreakpointsRequest,
|
||||||
SetExceptionBreakpointsArguments.fromJson,
|
SetExceptionBreakpointsArguments.fromJson,
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
} else if (request.command == 'continue') {
|
} else if (request.command == 'continue') {
|
||||||
handle(request, continueRequest, ContinueArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
continueRequest,
|
||||||
|
ContinueArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'next') {
|
} else if (request.command == 'next') {
|
||||||
handle(request, _withVoidResponse(nextRequest), NextArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
_withVoidResponse(nextRequest),
|
||||||
|
NextArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'stepIn') {
|
} else if (request.command == 'stepIn') {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
_withVoidResponse(stepInRequest),
|
_withVoidResponse(stepInRequest),
|
||||||
StepInArguments.fromJson,
|
StepInArguments.fromJson,
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
} else if (request.command == 'stepOut') {
|
} else if (request.command == 'stepOut') {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
_withVoidResponse(stepOutRequest),
|
_withVoidResponse(stepOutRequest),
|
||||||
StepOutArguments.fromJson,
|
StepOutArguments.fromJson,
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
} else if (request.command == 'threads') {
|
} else if (request.command == 'threads') {
|
||||||
handle(request, threadsRequest, _voidArgs);
|
handle(
|
||||||
|
request,
|
||||||
|
threadsRequest,
|
||||||
|
_voidArgs,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'stackTrace') {
|
} else if (request.command == 'stackTrace') {
|
||||||
handle(request, stackTraceRequest, StackTraceArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
stackTraceRequest,
|
||||||
|
StackTraceArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'source') {
|
} else if (request.command == 'source') {
|
||||||
handle(request, sourceRequest, SourceArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
sourceRequest,
|
||||||
|
SourceArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'scopes') {
|
} else if (request.command == 'scopes') {
|
||||||
handle(request, scopesRequest, ScopesArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
scopesRequest,
|
||||||
|
ScopesArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'variables') {
|
} else if (request.command == 'variables') {
|
||||||
handle(request, variablesRequest, VariablesArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
variablesRequest,
|
||||||
|
VariablesArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else if (request.command == 'evaluate') {
|
} else if (request.command == 'evaluate') {
|
||||||
handle(request, evaluateRequest, EvaluateArguments.fromJson);
|
handle(
|
||||||
|
request,
|
||||||
|
evaluateRequest,
|
||||||
|
EvaluateArguments.fromJson,
|
||||||
|
completer,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
handle(
|
handle(
|
||||||
request,
|
request,
|
||||||
customRequest,
|
customRequest,
|
||||||
_allowNullArg(RawRequestArguments.fromJson),
|
_allowNullArg(RawRequestArguments.fromJson),
|
||||||
|
completer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleIncomingResponse(Response response) {
|
void _handleIncomingResponse(Response response) {
|
||||||
|
|
9
pkg/dds/lib/src/dap/constants.dart
Normal file
9
pkg/dds/lib/src/dap/constants.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
class Command {
|
||||||
|
static const initialize = 'initialize';
|
||||||
|
static const configurationDone = 'configurationDone';
|
||||||
|
static const attach = 'attach';
|
||||||
|
}
|
|
@ -114,7 +114,7 @@ class ByteStreamServerChannel {
|
||||||
|
|
||||||
void _sendParseError(String data) {
|
void _sendParseError(String data) {
|
||||||
// TODO(dantup): Review LSP implementation of this when consolidating classes.
|
// TODO(dantup): Review LSP implementation of this when consolidating classes.
|
||||||
throw DebugAdapterException('Message does not confirm to DAP spec: $data');
|
throw DebugAdapterException('Message does not conform to DAP spec: $data');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send [bytes] to [_output].
|
/// Send [bytes] to [_output].
|
||||||
|
|
80
pkg/dds/lib/src/dap_handler.dart
Normal file
80
pkg/dds/lib/src/dap_handler.dart
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
|
||||||
|
|
||||||
|
import '../dap.dart';
|
||||||
|
import 'dap/adapters/dds_hosted_adapter.dart';
|
||||||
|
import 'dap/constants.dart';
|
||||||
|
import 'dds_impl.dart';
|
||||||
|
|
||||||
|
/// Responds to incoming DAP messages using a debug adapter connected to DDS.
|
||||||
|
class DapHandler {
|
||||||
|
DapHandler(this.dds);
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> handle(
|
||||||
|
DdsHostedAdapter adapter,
|
||||||
|
json_rpc.Parameters parameters,
|
||||||
|
) async {
|
||||||
|
if (adapter.ddsUri == null) {
|
||||||
|
_startAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(helin24): Consider a sequence offset for incoming messages to avoid
|
||||||
|
// overlapping sequence numbers with startup requests.
|
||||||
|
final message = parameters['message'].asString;
|
||||||
|
|
||||||
|
final result = await adapter.handleMessage(message);
|
||||||
|
|
||||||
|
return <String, dynamic>{
|
||||||
|
'type': 'DapResponse',
|
||||||
|
'message': result.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startAdapter(DdsHostedAdapter adapter) async {
|
||||||
|
adapter.ddsUri = dds.uri;
|
||||||
|
|
||||||
|
// TODO(helin24): Most likely we'll want the client to do these
|
||||||
|
// initialization steps so that clients can differentiate capabilities. This
|
||||||
|
// may require a custom stream for the debug adapter.
|
||||||
|
int seq = 1;
|
||||||
|
// TODO(helin24): Add waiting for `InitializedEvent`.
|
||||||
|
await adapter.initializeRequest(
|
||||||
|
Request(
|
||||||
|
command: Command.initialize,
|
||||||
|
seq: seq,
|
||||||
|
),
|
||||||
|
InitializeRequestArguments(
|
||||||
|
adapterID: 'dds-dap-handler',
|
||||||
|
),
|
||||||
|
(capabilities) {},
|
||||||
|
);
|
||||||
|
await adapter.configurationDoneRequest(
|
||||||
|
Request(
|
||||||
|
arguments: const {},
|
||||||
|
command: Command.configurationDone,
|
||||||
|
seq: seq++,
|
||||||
|
),
|
||||||
|
ConfigurationDoneArguments(),
|
||||||
|
noopCallback,
|
||||||
|
);
|
||||||
|
await adapter.attachRequest(
|
||||||
|
Request(
|
||||||
|
arguments: const {},
|
||||||
|
command: Command.attach,
|
||||||
|
seq: seq++,
|
||||||
|
),
|
||||||
|
DartAttachRequestArguments(
|
||||||
|
vmServiceUri: dds.remoteVmServiceUri.toString(),
|
||||||
|
),
|
||||||
|
noopCallback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DartDevelopmentServiceImpl dds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void noopCallback() {}
|
|
@ -23,6 +23,7 @@ import 'binary_compatible_peer.dart';
|
||||||
import 'client.dart';
|
import 'client.dart';
|
||||||
import 'client_manager.dart';
|
import 'client_manager.dart';
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
|
import 'dap_handler.dart';
|
||||||
import 'devtools/handler.dart';
|
import 'devtools/handler.dart';
|
||||||
import 'expression_evaluator.dart';
|
import 'expression_evaluator.dart';
|
||||||
import 'isolate_manager.dart';
|
import 'isolate_manager.dart';
|
||||||
|
@ -68,6 +69,7 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
||||||
_isolateManager = IsolateManager(this);
|
_isolateManager = IsolateManager(this);
|
||||||
_streamManager = StreamManager(this);
|
_streamManager = StreamManager(this);
|
||||||
_packageUriConverter = PackageUriConverter(this);
|
_packageUriConverter = PackageUriConverter(this);
|
||||||
|
_dapHandler = DapHandler(this);
|
||||||
_authCode = _authCodesEnabled ? _makeAuthToken() : '';
|
_authCode = _authCodesEnabled ? _makeAuthToken() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,6 +488,9 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
||||||
PackageUriConverter get packageUriConverter => _packageUriConverter;
|
PackageUriConverter get packageUriConverter => _packageUriConverter;
|
||||||
late PackageUriConverter _packageUriConverter;
|
late PackageUriConverter _packageUriConverter;
|
||||||
|
|
||||||
|
DapHandler get dapHandler => _dapHandler;
|
||||||
|
late DapHandler _dapHandler;
|
||||||
|
|
||||||
ClientManager get clientManager => _clientManager;
|
ClientManager get clientManager => _clientManager;
|
||||||
late ClientManager _clientManager;
|
late ClientManager _clientManager;
|
||||||
|
|
||||||
|
|
64
pkg/dds/test/dap_handler_test.dart
Normal file
64
pkg/dds/test/dap_handler_test.dart
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dds/dds.dart';
|
||||||
|
import 'package:dds/src/dap/protocol_generated.dart';
|
||||||
|
import 'package:dds_service_extensions/src/dap.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:vm_service/vm_service_io.dart';
|
||||||
|
|
||||||
|
import 'common/test_helper.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Process process;
|
||||||
|
DartDevelopmentService? dds;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
process = await spawnDartProcess(
|
||||||
|
'get_cached_cpu_samples_script.dart',
|
||||||
|
disableServiceAuthCodes: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await dds?.shutdown();
|
||||||
|
process.kill();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DDS responds to DAP message', () async {
|
||||||
|
Uri serviceUri = remoteVmServiceUri;
|
||||||
|
dds = await DartDevelopmentService.startDartDevelopmentService(
|
||||||
|
remoteVmServiceUri,
|
||||||
|
);
|
||||||
|
serviceUri = dds!.wsUri!;
|
||||||
|
expect(dds!.isRunning, true);
|
||||||
|
final service = await vmServiceConnectUri(serviceUri.toString());
|
||||||
|
|
||||||
|
final setBreakpointsRequest = Request(
|
||||||
|
command: 'setBreakpoints',
|
||||||
|
seq: 9,
|
||||||
|
arguments: SetBreakpointsArguments(
|
||||||
|
breakpoints: [
|
||||||
|
SourceBreakpoint(line: 20),
|
||||||
|
SourceBreakpoint(line: 30),
|
||||||
|
],
|
||||||
|
source: Source(
|
||||||
|
name: 'main.dart',
|
||||||
|
path: '/file/to/main.dart',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO(helinx): Check result format after using better typing from JSON.
|
||||||
|
final result = await service.handleDap(jsonEncode(setBreakpointsRequest));
|
||||||
|
expect(result.message, isNotNull);
|
||||||
|
expect(result.message['type'], 'response');
|
||||||
|
expect(result.message['success'], true);
|
||||||
|
expect(result.message['command'], 'setBreakpoints');
|
||||||
|
expect(result.message['body'], isNotNull);
|
||||||
|
});
|
||||||
|
}
|
50
pkg/dds_service_extensions/lib/src/dap.dart
Normal file
50
pkg/dds_service_extensions/lib/src/dap.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// ignore: implementation_imports
|
||||||
|
import 'package:vm_service/src/vm_service.dart';
|
||||||
|
|
||||||
|
extension DapExtension on VmService {
|
||||||
|
static bool _factoriesRegistered = false;
|
||||||
|
Future<DapResponse> handleDap(String message) async {
|
||||||
|
return _callHelper<DapResponse>(
|
||||||
|
'handleDap',
|
||||||
|
args: {'message': message},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T> _callHelper<T>(String method,
|
||||||
|
{String? isolateId, Map args = const {}}) {
|
||||||
|
if (!_factoriesRegistered) {
|
||||||
|
_registerFactories();
|
||||||
|
}
|
||||||
|
return callMethod(
|
||||||
|
method,
|
||||||
|
args: {
|
||||||
|
if (isolateId != null) 'isolateId': isolateId,
|
||||||
|
...args,
|
||||||
|
},
|
||||||
|
).then((e) => e as T);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _registerFactories() {
|
||||||
|
addTypeFactory('DapResponse', DapResponse.parse);
|
||||||
|
_factoriesRegistered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DapResponse extends Response {
|
||||||
|
static DapResponse? parse(Map<String, dynamic>? json) =>
|
||||||
|
json == null ? null : DapResponse._fromJson(json);
|
||||||
|
|
||||||
|
DapResponse({
|
||||||
|
required this.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
DapResponse._fromJson(Map<String, dynamic> json) : message = json['message'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get type => 'DapResponse';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '[DapResponse]';
|
||||||
|
|
||||||
|
final Map<String, Object?> message;
|
||||||
|
}
|
Loading…
Reference in a new issue