[dds/dap] Forward toolEvents on to the DAP client

Progress towards https://github.com/Dart-Code/Dart-Code/issues/4193.

Also related:
- https://github.com/flutter/flutter/pull/118098
- 4981cbffe2

Change-Id: I7b1394a5d5003c24b90733f1898fe368c0485937
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278515
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Danny Tuppeny 2023-01-13 17:49:12 +00:00 committed by Commit Queue
parent ebab6fb4de
commit 3949fe9aec
7 changed files with 121 additions and 8 deletions

View file

@ -1,3 +1,7 @@
# 2.7.2
- Update DDS protocol version to 1.4.
- [DAP] Forward any events from the VM Service's `ToolEvent` stream as `dart.toolEvent` DAP events.
# 2.7.1
- Updated `vm_service` version to >=9.0.0 <11.0.0.
- Simplified the DevTools URI composed by DDS.

View file

@ -1,6 +1,6 @@
# Dart Development Service Protocol 1.3
# Dart Development Service Protocol 1.4
This document describes _version 1.3_ of the Dart Development Service Protocol.
This document describes _version 1.4_ of the Dart Development Service Protocol.
This protocol is an extension of the Dart VM Service Protocol and implements it
in it's entirety. For details on the VM Service Protocol, see the [Dart VM Service Protocol Specification][service-protocol].
@ -69,6 +69,8 @@ consist of the following:
In addition, subscribing to the `Service` stream will result in a `ServiceRegistered`
event being sent to the subscribing client for each existing service extension.
From protocol version 1.4, custom streams of any name can be listened to via DDS.
## Public RPCs
The DDS Protocol supports all [public RPCs defined in the VM Service protocol][service-protocol-public-rpcs].
@ -281,6 +283,7 @@ version | comments
1.1 | Added `getDartDevelopmentServiceVersion` RPC.
1.2 | Added `getStreamHistory` RPC.
1.3 | Added `getAvailableCachedCpuSamples` and `getCachedCpuSamples` RPCs.
1.4 | Added the ability to subscribe to custom streams (which can be specified when calling `dart:developer`'s `postEvent`).
[resume]: https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#resume
[success]: https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#success

View file

@ -160,7 +160,7 @@ abstract class DartDevelopmentService {
/// The version of the DDS protocol supported by this [DartDevelopmentService]
/// instance.
static const String protocolVersion = '1.3';
static const String protocolVersion = '1.4';
}
class DartDevelopmentServiceException implements Exception {

View file

@ -439,6 +439,16 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
/// preserve order.
Future? _lastOutputEvent;
/// Capabilities of the DDS instance available in the connected VM Service.
///
/// If the VM Service is not yet connected, does not have a DDS instance, or
/// the version has not been been fetched, all capabilities will be false.
_DdsCapabilities _ddsCapabilities = _DdsCapabilities.empty;
/// The ID of the custom VM Service stream that emits events intended for
/// tools/IDEs.
static final toolEventStreamId = 'ToolEvent';
/// Removes any breakpoints or pause behaviour and resumes any paused
/// isolates.
///
@ -621,6 +631,18 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
final vmService = await _vmServiceConnectUri(uri.toString());
logger?.call('Connected to debugger at $uri!');
// Fetch DDS capabilities.
final supportedProtocols = await vmService.getSupportedProtocols();
final ddsProtocol = supportedProtocols.protocols
?.firstWhereOrNull((protocol) => protocol.protocolName == 'DDS');
if (ddsProtocol != null) {
_ddsCapabilities = _DdsCapabilities(
major: ddsProtocol.major ?? 0,
minor: ddsProtocol.minor ?? 0,
);
}
final supportsCustomStreams = _ddsCapabilities.supportsCustomStreams;
// Send debugger URI to the client.
sendDebuggerUris(uri);
@ -637,10 +659,12 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
vmService.onLoggingEvent.listen(wrap(handleLoggingEvent)),
vmService.onExtensionEvent.listen(wrap(handleExtensionEvent)),
vmService.onServiceEvent.listen(wrap(handleServiceEvent)),
if (_subscribeToOutputStreams)
if (supportsCustomStreams)
vmService.onEvent(toolEventStreamId).listen(wrap(handleToolEvent)),
if (_subscribeToOutputStreams) ...[
vmService.onStdoutEvent.listen(wrap(_handleStdoutEvent)),
if (_subscribeToOutputStreams)
vmService.onStderrEvent.listen(wrap(_handleStderrEvent)),
],
]);
await Future.wait([
vmService.streamListen(vm.EventStreams.kIsolate),
@ -648,8 +672,11 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
vmService.streamListen(vm.EventStreams.kLogging),
vmService.streamListen(vm.EventStreams.kExtension),
vmService.streamListen(vm.EventStreams.kService),
vmService.streamListen(vm.EventStreams.kStdout),
vmService.streamListen(vm.EventStreams.kStderr),
if (supportsCustomStreams) vmService.streamListen(toolEventStreamId),
if (_subscribeToOutputStreams) ...[
vmService.streamListen(vm.EventStreams.kStdout),
vmService.streamListen(vm.EventStreams.kStderr),
],
]);
final vmInfo = await vmService.getVM();
@ -2028,6 +2055,20 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
}
}
@protected
@mustCallSuper
Future<void> handleToolEvent(vm.Event event) async {
await debuggerInitialized;
sendEvent(
RawEventBody({
'kind': event.extensionKind,
'data': event.extensionData?.data,
}),
eventType: 'dart.toolEvent',
);
}
void _handleStderrEvent(vm.Event event) {
_sendOutputStreamEvent('stderr', event);
}
@ -2339,3 +2380,28 @@ class DartLaunchRequestArguments extends DartCommonLaunchAttachRequestArguments
static DartLaunchRequestArguments fromJson(Map<String, Object?> obj) =>
DartLaunchRequestArguments.fromMap(obj);
}
/// A helper for checking whether the available DDS instance has specific
/// capabilities.
class _DdsCapabilities {
final int major;
final int minor;
static const empty = _DdsCapabilities(major: 0, minor: 0);
const _DdsCapabilities({required this.major, required this.minor});
/// Whether the DDS instance supports custom streams via `dart:developer`'s
/// `postEvent`.
bool get supportsCustomStreams => _isAtLeast(major: 1, minor: 4);
bool _isAtLeast({required major, required minor}) {
if (this.major > major) {
return true;
} else if (this.major == major && this.minor >= minor) {
return true;
} else {
return false;
}
}
}

View file

@ -420,6 +420,31 @@ main() {
await server.exitCode;
}
});
test('forwards tool events to client', () async {
final testFile = dap.createTestFile(simpleToolEventProgram);
// Capture any `dart.toolEvent` events.
final toolEventsFuture = dap.client.events('dart.toolEvent').toList();
// Run the script to completion.
await Future.wait([
dap.client.event('terminated'),
dap.client.initialize(),
dap.client.launch(testFile.path),
], eagerError: true);
// Verify we got exactly the event in the sample program.
final toolEvents = await toolEventsFuture;
expect(toolEvents, hasLength(1));
final toolEvent = toolEvents.single;
expect(toolEvent.body, {
'kind': 'navigate',
'data': {
'file': 'x.dart',
},
});
});
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);

View file

@ -199,5 +199,21 @@ const simpleThrowingProgram = r'''
}
''';
/// A simple Dart script that sends a `navigate` event to the `ToolEvent`
/// stream.
const simpleToolEventProgram = r'''
import 'dart:developer';
void main(List<String> args) async {
postEvent(
'navigate',
{
'file': 'x.dart',
},
stream: 'ToolEvent',
);
}
''';
/// A marker used in some test scripts/tests for where to expected steps.
const stepMarker = '// STEP';

View file

@ -185,4 +185,3 @@ When running the `--test` debug adapter, `package:test` JSON messages will be pa
}
}
```