mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 00:45:16 +00:00
Split package:vm_service into package:vm_service and package:vm_service_interface
To reduce the headache associated with adding parameters to VM service APIs, VmServiceInterface has been removed from package:vm_service and pulled into its own dedicated package:vm_service_interface. This will help reduce the need for major version bumps of package:vm_service, which requires manual version bumps through >8 packages in order to make the latest version available to flutter_tools and DevTools. This separation of the VmService client from the interface will reduce the frequency of major version bumps to `package:vm_service` as adding optional parameters to existing APIs would cause implementers of the interface to break. package:vm_service continues to expose a copy of the contents of package:vm_service_interface to avoid breaking google3 rolls until package:dwds can migrate to package:vm_service_interface. package:vm_service will not be published until this copy is removed. This change also includes: - some code cleanup and modernization to both the code generator and generated code - >=3.0.0 SDK version requirement to allow for new language features Change-Id: Ib1859c1b4e153fef7ee1f91e67e881bbf42652c2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/330500 Reviewed-by: Derek Xu <derekx@google.com> Reviewed-by: Devon Carew <devoncarew@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
parent
d37620ed03
commit
08b4f49249
|
@ -1,3 +1,7 @@
|
|||
# 3.0.0
|
||||
- **Breaking change:** update `vm_service` version to ^13.0.0.
|
||||
- **Breaking chnage:** change type of `DartDebugAdapter.vmService` from `VmServiceInterface` to `VmService`.
|
||||
|
||||
# 2.11.1
|
||||
- [DAP] `restartFrameRequest` is now supported for frames up until the first async boundary (that are not also the top frame).
|
||||
- Update `vm_service` version to >=11.0.0 <13.0.0.
|
||||
|
|
|
@ -349,7 +349,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
|||
///
|
||||
/// `null` if the session is running in noDebug mode of the connection has not
|
||||
/// yet been made.
|
||||
vm.VmServiceInterface? vmService;
|
||||
vm.VmService? vmService;
|
||||
|
||||
/// The root of the Dart SDK containing the VM running the debug adapter.
|
||||
late final String dartSdkRoot;
|
||||
|
|
|
@ -316,7 +316,7 @@ class ProtocolConverter {
|
|||
|
||||
/// Creates a Variable for a getter after eagerly fetching its value.
|
||||
Future<Variable> createVariableForGetter(
|
||||
vm.VmServiceInterface service,
|
||||
vm.VmService service,
|
||||
ThreadInfo thread,
|
||||
vm.Instance instance, {
|
||||
String? variableName,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: dds
|
||||
version: 2.11.1
|
||||
version: 3.0.0
|
||||
description: >-
|
||||
A library used to spawn the Dart Developer Service, used to communicate with
|
||||
a Dart VM Service instance.
|
||||
|
@ -28,7 +28,7 @@ dependencies:
|
|||
sse: ^4.0.0
|
||||
stack_trace: ^1.10.0
|
||||
stream_channel: ^2.0.0
|
||||
vm_service: '>=11.7.2 <13.0.0'
|
||||
vm_service: ^13.0.0
|
||||
web_socket_channel: ^2.0.0
|
||||
|
||||
# We use 'any' version constraints here as we get our package versions from
|
||||
|
|
|
@ -148,7 +148,7 @@ class MockRequest extends dap.Request {
|
|||
});
|
||||
}
|
||||
|
||||
class MockVmService implements VmServiceInterface {
|
||||
class MockVmService implements VmService {
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# 1.6.2
|
||||
- Updated `vm_service` version to `^13.0.0`.
|
||||
|
||||
# 1.6.1
|
||||
- Updated `vm_service` version to `^12.0.0`
|
||||
- Updated `vm_service` version to `^12.0.0`.
|
||||
|
||||
## 1.6.0
|
||||
- Made DAP extensions methods accessible in lib.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: dds_service_extensions
|
||||
version: 1.6.1
|
||||
version: 1.6.2
|
||||
description: >-
|
||||
Extension methods for `package:vm_service`, used to make requests a
|
||||
Dart Development Service (DDS) instance.
|
||||
|
@ -11,7 +11,7 @@ environment:
|
|||
dependencies:
|
||||
async: ^2.4.1
|
||||
dap: ^1.0.0
|
||||
vm_service: ^12.0.0
|
||||
vm_service: ^13.0.0
|
||||
|
||||
# We use 'any' version constraints here as we get our package versions from
|
||||
# the dart-lang/sdk repo's DEPS file. Note that this is a special case; the
|
||||
|
|
34
pkg/vm_service/CONTRIBUTING.md
Normal file
34
pkg/vm_service/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Contributing to `package:vm_service` and `package:vm_service_interface`
|
||||
|
||||
## Updating the VM service version
|
||||
|
||||
To update `package:vm_service` and `package:vm_service_interface` to support the latest version of the [VM service protocol](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md), run the following script to regenerate the client and interface:
|
||||
|
||||
`dart tool/generate.dart`
|
||||
|
||||
## Updating the code generator
|
||||
|
||||
Both `package:vm_service` and `package:vm_service_interface` make use of code generation to generate much of their implementations. As a result, manual changes made to some files (e.g., `package:vm_service/src/vm_service.dart` and `package:vm_service_interface/src/vm_service_interface.dart`) will be overwritten by the code generator.
|
||||
|
||||
To make changes to the generated files, make changes in one or more of the following files:
|
||||
|
||||
- `tool/dart/generate_dart_client.dart` for code specific to `package:vm_service`
|
||||
- `tool/dart/generate_dart_interface.dart` for code specific to `package:vm_service_interface`
|
||||
- `tool/dart/generate_dart_common.dart` for code common to `package:vm_service` and `package:vm_service_interface`
|
||||
|
||||
## Running tests locally
|
||||
|
||||
### 1. Build the SDK
|
||||
|
||||
From the root of the Dart SDK, run the following commands:
|
||||
|
||||
gclient sync -D && \
|
||||
python3 tools/build.py -ax64 create_sdk
|
||||
|
||||
Note: for a release build, add the `-mrelease` flag: `./tools/build.py -mrelease -ax64 create_sdk`
|
||||
|
||||
### 2. Run the tests
|
||||
|
||||
To run all the tests: `python3 tools/test.py [ -mdebug | -mrelease ] -ax64 -j4 pkg/vm_service`
|
||||
|
||||
To run a single test: `dart pkg/vm_service/test/<test_name>.dart`
|
|
@ -9,7 +9,7 @@ See the
|
|||
[example](https://github.com/dart-lang/sdk/blob/main/pkg/vm_service/example/vm_service_tester.dart)
|
||||
for a simple use of the library's API.
|
||||
|
||||
The VM Service Protocol spec can be found at
|
||||
The VM Service Protocol specification can be found at
|
||||
[github.com/dart-lang/sdk/runtime/vm/service/service.md](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md).
|
||||
|
||||
## Features and bugs
|
||||
|
@ -17,18 +17,3 @@ The VM Service Protocol spec can be found at
|
|||
Please file feature requests and bugs at the [issue tracker][tracker].
|
||||
|
||||
[tracker]: https://github.com/dart-lang/sdk/issues
|
||||
|
||||
## Running tests locally
|
||||
|
||||
1. Build the SDK
|
||||
```
|
||||
gclient sync -D && \
|
||||
./tools/build.py -ax64 create_sdk
|
||||
```
|
||||
Note: for a release build, add the `-mrelease` flag: `./tools/build.py -mrelease -ax64 create_sdk`
|
||||
|
||||
2. Run the tests
|
||||
|
||||
- To run all the tests: `python3 tools/test.py [ -mdebug | -mrelease ] -ax64 -j4 pkg/vm_service`
|
||||
|
||||
- To run a single test: `dart pkg/vm_service/test/<test_name>.dart`
|
||||
|
|
|
@ -7,7 +7,7 @@ analyzer:
|
|||
|
||||
linter:
|
||||
rules:
|
||||
# still 6 errors in lib/src/vm_service.dart
|
||||
# still 5 errors in lib/src/vm_service.dart
|
||||
#- comment_references
|
||||
- directives_ordering
|
||||
- prefer_single_quotes
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import '../vm_service.dart' show VmServerConnection, RPCError, Event, EventKind;
|
||||
import 'package:vm_service/vm_service.dart' show RPCError, Event, EventKind;
|
||||
|
||||
import 'stream_helpers.dart';
|
||||
import 'vm_service_interface.dart' show VmServerConnection;
|
||||
|
||||
/// A registry of custom service extensions to [VmServerConnection]s in which
|
||||
/// they were registered.
|
||||
|
|
File diff suppressed because it is too large
Load diff
1694
pkg/vm_service/lib/src/vm_service_interface.dart
Normal file
1694
pkg/vm_service/lib/src/vm_service_interface.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -5,4 +5,7 @@
|
|||
library vm_service;
|
||||
|
||||
export 'src/dart_io_extensions.dart';
|
||||
export 'src/snapshot_graph.dart';
|
||||
export 'src/vm_service.dart' hide addTypeFactory, extensionCallHelper;
|
||||
// TODO(bkonyi): remove before publishing 13.0.0
|
||||
export 'src/vm_service_interface.dart' hide vmServiceVersion;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
name: vm_service
|
||||
version: 13.0.0
|
||||
# TODO(bkonyi): resolve outstanding TODOs required for 13.0.0 release before
|
||||
# removing.
|
||||
publish_to: none
|
||||
description: >-
|
||||
A library to communicate with a service implementing the Dart VM
|
||||
service protocol.
|
||||
|
@ -19,20 +22,18 @@ dev_dependencies:
|
|||
expect: any
|
||||
lints: any
|
||||
markdown: any
|
||||
mockito: any
|
||||
path: any
|
||||
pub_semver: any
|
||||
test_package: any
|
||||
test: any
|
||||
vm_service_protos: any
|
||||
|
||||
# TODO(bkonyi): remove expect and smith overrides before
|
||||
# publishing 13.0.0.
|
||||
dependency_overrides:
|
||||
coverage: 1.6.4
|
||||
expect:
|
||||
path: '../expect'
|
||||
smith:
|
||||
path: '../smith'
|
||||
test: 1.24.7
|
||||
test_core: 0.5.7
|
||||
test_package:
|
||||
path: 'test/test_package'
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'common/test_helper.dart';
|
|||
// Chop off the file name.
|
||||
String baseDirectory = path.dirname(Platform.script.path) + '/';
|
||||
Uri baseUri = Platform.script.replace(path: baseDirectory);
|
||||
Uri breakpointFile = baseUri.resolve('test_package/the_part.dart');
|
||||
Uri breakpointFile = baseUri.resolve('test_package/lib/the_part.dart');
|
||||
const String shortFile = "the_part.dart";
|
||||
|
||||
const int LINE = 87;
|
||||
|
|
|
@ -46,7 +46,10 @@ Future<void> executeWithRandomDelay(Function f) =>
|
|||
.then((_) async {
|
||||
try {
|
||||
await f();
|
||||
} on HttpException catch (_) {} on SocketException catch (_) {} on StateError catch (_) {} on OSError catch (_) {}
|
||||
} on HttpException catch (_) {
|
||||
} on SocketException catch (_) {
|
||||
} on StateError catch (_) {
|
||||
} on OSError catch (_) {}
|
||||
});
|
||||
|
||||
Uri randomlyAddRequestParams(Uri uri) {
|
||||
|
|
|
@ -7,13 +7,11 @@
|
|||
// ignore_for_file: experiment_not_enabled
|
||||
|
||||
import 'dart:developer';
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
|
||||
abstract class A {
|
||||
int get x;
|
||||
int get y;
|
||||
|
@ -44,7 +42,7 @@ var tests = <IsolateTest>[
|
|||
(VmService service, IsolateRef isolateRef) async {
|
||||
Stack stack = await service.getStack(isolateRef.id!);
|
||||
final Set<String> vars = stack.frames![0].vars!.map((v) => v.name!).toSet();
|
||||
Expect.setEquals(<String>{'obj', 'x1', 'y1'}, vars);
|
||||
expect(vars, <String>{'obj', 'x1', 'y1'});
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -46,7 +46,10 @@ Future<void> executeWithRandomDelay(Function f) =>
|
|||
.then((_) async {
|
||||
try {
|
||||
await f();
|
||||
} on HttpException catch (_) {} on SocketException catch (_) {} on StateError catch (_) {} on OSError catch (_) {}
|
||||
} on HttpException catch (_) {
|
||||
} on SocketException catch (_) {
|
||||
} on StateError catch (_) {
|
||||
} on OSError catch (_) {}
|
||||
});
|
||||
|
||||
Uri randomlyAddRequestParams(Uri uri) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:pub_semver/pub_semver.dart';
|
|||
import 'src_gen_common.dart';
|
||||
|
||||
/// [ApiParseUtil] contains top level parsing utilities.
|
||||
mixin class ApiParseUtil {
|
||||
mixin ApiParseUtil {
|
||||
/// Extract the current VM Service version number as a String.
|
||||
static String parseVersionString(List<Node> nodes) =>
|
||||
parseVersionSemVer(nodes).toString();
|
||||
|
|
669
pkg/vm_service/tool/dart/generate_dart_client.dart
Normal file
669
pkg/vm_service/tool/dart/generate_dart_client.dart
Normal file
|
@ -0,0 +1,669 @@
|
|||
// 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 'generate_dart_common.dart';
|
||||
|
||||
class VmServiceApi extends Api {
|
||||
static const _clientHeaderCode = r'''
|
||||
// Copyright (c) 2015, 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.
|
||||
|
||||
// This is a generated file. To regenerate, run `dart tool/generate.dart`.
|
||||
|
||||
/// A library to access the VM Service API.
|
||||
///
|
||||
/// The main entry-point for this library is the [VmService] class.
|
||||
library;
|
||||
|
||||
// ignore_for_file: overridden_fields
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert' show base64, jsonDecode, jsonEncode, utf8;
|
||||
import 'dart:typed_data';
|
||||
|
||||
export 'snapshot_graph.dart' show HeapSnapshotClass,
|
||||
HeapSnapshotExternalProperty,
|
||||
HeapSnapshotField,
|
||||
HeapSnapshotGraph,
|
||||
HeapSnapshotObject,
|
||||
HeapSnapshotObjectLengthData,
|
||||
HeapSnapshotObjectNoData,
|
||||
HeapSnapshotObjectNullData;
|
||||
''';
|
||||
|
||||
static const _implCode = r'''
|
||||
|
||||
/// Call an arbitrary service protocol method. This allows clients to call
|
||||
/// methods not explicitly exposed by this library.
|
||||
Future<Response> callMethod(String method, {
|
||||
String? isolateId,
|
||||
Map<String, dynamic>? args
|
||||
}) {
|
||||
return callServiceExtension(method, isolateId: isolateId, args: args);
|
||||
}
|
||||
|
||||
/// Invoke a specific service protocol extension method.
|
||||
///
|
||||
/// See https://api.dart.dev/stable/dart-developer/dart-developer-library.html.
|
||||
Future<Response> callServiceExtension(String method, {
|
||||
String? isolateId,
|
||||
Map<String, dynamic>? args
|
||||
}) {
|
||||
if (args == null && isolateId == null) {
|
||||
return _call(method);
|
||||
} else if (args == null) {
|
||||
return _call(method, {'isolateId': isolateId!});
|
||||
} else {
|
||||
args = Map.from(args);
|
||||
if (isolateId != null) {
|
||||
args['isolateId'] = isolateId;
|
||||
}
|
||||
return _call(method, args);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _streamSub.cancel();
|
||||
_outstandingRequests.forEach((id, request) {
|
||||
request._completer.completeError(RPCError(
|
||||
request.method,
|
||||
RPCErrorKind.kServerError.code,
|
||||
'Service connection disposed',
|
||||
));
|
||||
});
|
||||
_outstandingRequests.clear();
|
||||
if (_disposeHandler != null) {
|
||||
await _disposeHandler!();
|
||||
}
|
||||
if (!_onDoneCompleter.isCompleted) {
|
||||
_onDoneCompleter.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// When overridden, this method wraps [future] with logic.
|
||||
///
|
||||
/// [wrapFuture] is called by [_call], which is the method that each VM
|
||||
/// service endpoint eventually goes through.
|
||||
///
|
||||
/// This method should be overridden if subclasses of [VmService] need to do
|
||||
/// anything special upon calling the VM service, like tracking futures or
|
||||
/// logging requests.
|
||||
Future<T> wrapFuture<T>(String name, Future<T> future) {
|
||||
return future;
|
||||
}
|
||||
|
||||
Future<T> _call<T>(String method, [Map args = const {}]) {
|
||||
return wrapFuture<T>(
|
||||
method,
|
||||
() {
|
||||
final request = _OutstandingRequest<T>(method);
|
||||
_outstandingRequests[request.id] = request;
|
||||
Map m = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': request.id,
|
||||
'method': method,
|
||||
'params': args,
|
||||
};
|
||||
String message = jsonEncode(m);
|
||||
_onSend.add(message);
|
||||
_writeMessage(message);
|
||||
return request.future;
|
||||
}(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Register a service for invocation.
|
||||
void registerServiceCallback(String service, ServiceCallback cb) {
|
||||
if (_services.containsKey(service)) {
|
||||
throw Exception('Service \'$service\' already registered');
|
||||
}
|
||||
_services[service] = cb;
|
||||
}
|
||||
|
||||
void _processMessage(dynamic message) {
|
||||
// Expect a String, an int[], or a ByteData.
|
||||
if (message is String) {
|
||||
_processMessageStr(message);
|
||||
} else if (message is List<int>) {
|
||||
final list = Uint8List.fromList(message);
|
||||
_processMessageByteData(ByteData.view(list.buffer));
|
||||
} else if (message is ByteData) {
|
||||
_processMessageByteData(message);
|
||||
} else {
|
||||
_log.warning('unknown message type: ${message.runtimeType}');
|
||||
}
|
||||
}
|
||||
|
||||
void _processMessageByteData(ByteData bytes) {
|
||||
final int metaOffset = 4;
|
||||
final int dataOffset = bytes.getUint32(0, Endian.little);
|
||||
final metaLength = dataOffset - metaOffset;
|
||||
final dataLength = bytes.lengthInBytes - dataOffset;
|
||||
final meta = utf8.decode(Uint8List.view(
|
||||
bytes.buffer, bytes.offsetInBytes + metaOffset, metaLength));
|
||||
final data = ByteData.view(
|
||||
bytes.buffer, bytes.offsetInBytes + dataOffset, dataLength);
|
||||
final map = jsonDecode(meta)!;
|
||||
if (map['method'] == 'streamNotify') {
|
||||
final streamId = map['params']['streamId'];
|
||||
final event = map['params']['event'];
|
||||
event['data'] = data;
|
||||
_getEventController(streamId)
|
||||
.add(createServiceObject(event, const ['Event'])! as Event);
|
||||
}
|
||||
}
|
||||
|
||||
void _processMessageStr(String message) {
|
||||
try {
|
||||
_onReceive.add(message);
|
||||
final json = jsonDecode(message)!;
|
||||
if (json.containsKey('method')) {
|
||||
if (json.containsKey('id')) {
|
||||
_processRequest(json);
|
||||
} else {
|
||||
_processNotification(json);
|
||||
}
|
||||
} else if (json.containsKey('id') &&
|
||||
(json.containsKey('result') || json.containsKey('error'))) {
|
||||
_processResponse(json);
|
||||
}
|
||||
else {
|
||||
_log.severe('unknown message type: $message');
|
||||
}
|
||||
} catch (e, s) {
|
||||
_log.severe('unable to decode message: $message, $e\n$s');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _processResponse(Map<String, dynamic> json) {
|
||||
final request = _outstandingRequests.remove(json['id']);
|
||||
if (request == null) {
|
||||
_log.severe('unmatched request response: ${jsonEncode(json)}');
|
||||
} else if (json['error'] != null) {
|
||||
request.completeError(RPCError.parse(request.method, json['error']));
|
||||
} else {
|
||||
final result = json['result'] as Map<String, dynamic>;
|
||||
final type = result['type'];
|
||||
if (type == 'Sentinel') {
|
||||
request.completeError(SentinelException.parse(request.method, result));
|
||||
} else if (_typeFactories[type] == null) {
|
||||
request.complete(Response.parse(result));
|
||||
} else {
|
||||
final returnTypes = _methodReturnTypes[request.method] ?? <String>[];
|
||||
request.complete(createServiceObject(result, returnTypes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future _processRequest(Map<String, dynamic> json) async {
|
||||
final result = await _routeRequest(json['method'], json['params'] ?? <String, dynamic>{});
|
||||
result['id'] = json['id'];
|
||||
result['jsonrpc'] = '2.0';
|
||||
String message = jsonEncode(result);
|
||||
_onSend.add(message);
|
||||
_writeMessage(message);
|
||||
}
|
||||
|
||||
Future _processNotification(Map<String, dynamic> json) async {
|
||||
final method = json['method'];
|
||||
final params = json['params'] ?? <String, dynamic>{};
|
||||
if (method == 'streamNotify') {
|
||||
final streamId = params['streamId'];
|
||||
_getEventController(streamId).add(createServiceObject(params['event'], const ['Event'])! as Event);
|
||||
} else {
|
||||
await _routeRequest(method, params);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map> _routeRequest(String method, Map<String, dynamic> params) async {
|
||||
final service = _services[method];
|
||||
if (service == null) {
|
||||
final error = RPCError(method, RPCErrorKind.kMethodNotFound.code,
|
||||
'method not found \'$method\'');
|
||||
return {'error': error.toMap()};
|
||||
}
|
||||
|
||||
try {
|
||||
return await service(params);
|
||||
} catch (e, st) {
|
||||
RPCError error = RPCError.withDetails(
|
||||
method,
|
||||
RPCErrorKind.kServerError.code,
|
||||
'$e',
|
||||
details: '$st',
|
||||
);
|
||||
return {'error': error.toMap()};
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
static const _rpcError = r'''
|
||||
|
||||
typedef DisposeHandler = Future Function();
|
||||
|
||||
// These error codes must be kept in sync with those in vm/json_stream.h and
|
||||
// vmservice.dart.
|
||||
enum RPCErrorKind {
|
||||
/// Application specific error code.
|
||||
kServerError(code: -32000, message: 'Application error'),
|
||||
|
||||
/// The JSON sent is not a valid Request object.
|
||||
kInvalidRequest(code: -32600, message: 'Invalid request object'),
|
||||
|
||||
/// The method does not exist or is not available.
|
||||
kMethodNotFound(code: -32601, message: 'Method not found'),
|
||||
|
||||
/// Invalid method parameter(s), such as a mismatched type.
|
||||
kInvalidParams(code: -32602, message: 'Invalid method parameters'),
|
||||
|
||||
/// Internal JSON-RPC error.
|
||||
kInternalError(code: -32603, message: 'Internal JSON-RPC error'),
|
||||
|
||||
/// The requested feature is disabled.
|
||||
kFeatureDisabled(code: 100, message: 'Feature is disabled'),
|
||||
|
||||
/// The stream has already been subscribed to.
|
||||
kStreamAlreadySubscribed(code: 103, message: 'Stream already subscribed'),
|
||||
|
||||
/// The stream has not been subscribed to.
|
||||
kStreamNotSubscribed(code: 104, message: 'Stream not subscribed'),
|
||||
|
||||
/// Isolate must first be paused.
|
||||
kIsolateMustBePaused(code: 106, message: 'Isolate must be paused'),
|
||||
|
||||
/// The service has already been registered.
|
||||
kServiceAlreadyRegistered(code: 111, message: 'Service already registered'),
|
||||
|
||||
/// The service no longer exists.
|
||||
kServiceDisappeared(code: 112, message: 'Service has disappeared'),
|
||||
|
||||
/// There was an error in the expression compiler.
|
||||
kExpressionCompilationError(
|
||||
code: 113, message: 'Expression compilation error'),
|
||||
|
||||
/// The custom stream does not exist.
|
||||
kCustomStreamDoesNotExist(code: 130, message: 'Custom stream does not exist'),
|
||||
|
||||
/// The core stream is not allowed.
|
||||
kCoreStreamNotAllowed(code: 131, message: 'Core streams are not allowed');
|
||||
|
||||
const RPCErrorKind({required this.code, required this.message});
|
||||
|
||||
final int code;
|
||||
|
||||
final String message;
|
||||
|
||||
static final _codeToErrorMap =
|
||||
RPCErrorKind.values.fold(<int, RPCErrorKind>{}, (map, error) {
|
||||
map[error.code] = error;
|
||||
return map;
|
||||
});
|
||||
|
||||
static RPCErrorKind? fromCode(int code) {
|
||||
return _codeToErrorMap[code];
|
||||
}
|
||||
}
|
||||
|
||||
class RPCError implements Exception {
|
||||
@Deprecated('Use RPCErrorKind.kServerError.code instead.')
|
||||
static int get kServerError => RPCErrorKind.kServerError.code;
|
||||
|
||||
@Deprecated('Use RPCErrorKind.kInvalidRequest.code instead.')
|
||||
static int get kInvalidRequest => RPCErrorKind.kInvalidRequest.code;
|
||||
|
||||
@Deprecated('Use RPCErrorKind.kMethodNotFound.code instead.')
|
||||
static int get kMethodNotFound => RPCErrorKind.kMethodNotFound.code;
|
||||
|
||||
@Deprecated('Use RPCErrorKind.kInvalidParams.code instead.')
|
||||
static int get kInvalidParams => RPCErrorKind.kInvalidParams.code;
|
||||
|
||||
@Deprecated('Use RPCErrorKind.kInternalError.code instead.')
|
||||
static int get kInternalError => RPCErrorKind.kInternalError.code;
|
||||
|
||||
static RPCError parse(String callingMethod, dynamic json) {
|
||||
return RPCError(callingMethod, json['code'], json['message'], json['data']);
|
||||
}
|
||||
|
||||
final String? callingMethod;
|
||||
final int code;
|
||||
final String message;
|
||||
final Map? data;
|
||||
|
||||
RPCError(this.callingMethod, this.code, [message, this.data])
|
||||
: message =
|
||||
message ?? RPCErrorKind.fromCode(code)?.message ?? 'Unknown error';
|
||||
|
||||
RPCError.withDetails(this.callingMethod, this.code, this.message,
|
||||
{Object? details})
|
||||
: data = details == null ? null : <String, dynamic>{} {
|
||||
if (details != null) {
|
||||
data!['details'] = details;
|
||||
}
|
||||
}
|
||||
|
||||
String? get details => data == null ? null : data!['details'];
|
||||
|
||||
/// Return a map representation of this error suitable for conversion to
|
||||
/// json.
|
||||
Map<String, dynamic> toMap() {
|
||||
final map = <String, dynamic>{
|
||||
'code': code,
|
||||
'message': message,
|
||||
};
|
||||
if (data != null) {
|
||||
map['data'] = data;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (details == null) {
|
||||
return '$callingMethod: ($code) $message';
|
||||
} else {
|
||||
return '$callingMethod: ($code) $message\n$details';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Thrown when an RPC response is a [Sentinel].
|
||||
class SentinelException implements Exception {
|
||||
final String callingMethod;
|
||||
final Sentinel sentinel;
|
||||
|
||||
SentinelException.parse(this.callingMethod, Map<String, dynamic> data) :
|
||||
sentinel = Sentinel.parse(data)!;
|
||||
|
||||
@override
|
||||
String toString() => '$sentinel from $callingMethod()';
|
||||
}
|
||||
|
||||
/// An `ExtensionData` is an arbitrary map that can have any contents.
|
||||
class ExtensionData {
|
||||
static ExtensionData? parse(Map<String, dynamic>? json) =>
|
||||
json == null ? null : ExtensionData._fromJson(json);
|
||||
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
ExtensionData() : data = <String, dynamic>{};
|
||||
|
||||
ExtensionData._fromJson(this.data);
|
||||
|
||||
@override
|
||||
String toString() => '[ExtensionData $data]';
|
||||
}
|
||||
|
||||
/// A logging handler you can pass to a [VmService] instance in order to get
|
||||
/// notifications of non-fatal service protocol warnings and errors.
|
||||
abstract class Log {
|
||||
/// Log a warning level message.
|
||||
void warning(String message);
|
||||
|
||||
/// Log an error level message.
|
||||
void severe(String message);
|
||||
}
|
||||
|
||||
class _NullLog implements Log {
|
||||
@override
|
||||
void warning(String message) {}
|
||||
@override
|
||||
void severe(String message) {}
|
||||
}
|
||||
''';
|
||||
|
||||
@override
|
||||
void generate(DartGenerator gen) {
|
||||
gen.out(_clientHeaderCode);
|
||||
gen.writeln("const String vmServiceVersion = '$serviceVersion';");
|
||||
gen.writeln();
|
||||
gen.writeln('''
|
||||
/// @optional
|
||||
const String optional = 'optional';
|
||||
|
||||
/// Decode a string in Base64 encoding into the equivalent non-encoded string.
|
||||
/// This is useful for handling the results of the Stdout or Stderr events.
|
||||
String decodeBase64(String str) => utf8.decode(base64.decode(str));
|
||||
|
||||
// Returns true if a response is the Dart `null` instance.
|
||||
bool _isNullInstance(Map json) => ((json['type'] == '@Instance') &&
|
||||
(json['kind'] == 'Null'));
|
||||
|
||||
Object? createServiceObject(dynamic json, List<String> expectedTypes) {
|
||||
if (json == null) return null;
|
||||
|
||||
if (json is List) {
|
||||
return json.map((e) => createServiceObject(e, expectedTypes)).toList();
|
||||
} else if (json is Map<String, dynamic>) {
|
||||
String? type = json['type'];
|
||||
|
||||
// Not a Response type.
|
||||
if (type == null) {
|
||||
// If there's only one expected type, we'll just use that type.
|
||||
if (expectedTypes.length == 1) {
|
||||
type = expectedTypes.first;
|
||||
} else {
|
||||
return Response.parse(json);
|
||||
}
|
||||
} else if (_isNullInstance(json) && (!expectedTypes.contains('InstanceRef'))) {
|
||||
// Replace null instances with null when we don't expect an instance to
|
||||
// be returned.
|
||||
return null;
|
||||
}
|
||||
final typeFactory = _typeFactories[type];
|
||||
if (typeFactory == null) {
|
||||
return null;
|
||||
} else {
|
||||
return typeFactory(json);
|
||||
}
|
||||
} else {
|
||||
// Handle simple types.
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
dynamic _createSpecificObject(
|
||||
dynamic json, dynamic Function(Map<String, dynamic> map) creator) {
|
||||
if (json == null) return null;
|
||||
|
||||
if (json is List) {
|
||||
return json.map((e) => creator(e)).toList();
|
||||
} else if (json is Map) {
|
||||
return creator({
|
||||
for (String key in json.keys)
|
||||
key: json[key],
|
||||
});
|
||||
} else {
|
||||
// Handle simple types.
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
void _setIfNotNull(Map<String, dynamic> json, String key, Object? value) {
|
||||
if (value == null) return;
|
||||
json[key] = value;
|
||||
}
|
||||
|
||||
Future<T> extensionCallHelper<T>(VmService service, String method, Map<String, dynamic> args) {
|
||||
return service._call(method, args);
|
||||
}
|
||||
|
||||
typedef ServiceCallback = Future<Map<String, dynamic>> Function(
|
||||
Map<String, dynamic> params);
|
||||
|
||||
void addTypeFactory(String name, Function factory) {
|
||||
if (_typeFactories.containsKey(name)) {
|
||||
throw StateError('Factory already registered for \$name');
|
||||
}
|
||||
_typeFactories[name] = factory;
|
||||
}
|
||||
|
||||
''');
|
||||
gen.writeln();
|
||||
gen.writeln('final _typeFactories = <String, Function>{');
|
||||
for (var type in types) {
|
||||
gen.writeln("'${type!.rawName}': ${type.name}.parse,");
|
||||
}
|
||||
gen.writeln('};');
|
||||
gen.writeln();
|
||||
|
||||
gen.writeln('final _methodReturnTypes = <String, List<String>>{');
|
||||
for (var method in methods) {
|
||||
String returnTypes = typeRefListToString(method.returnType.types);
|
||||
gen.writeln("'${method.name}' : $returnTypes,");
|
||||
}
|
||||
gen.writeln('};');
|
||||
gen.writeln();
|
||||
gen.write('''
|
||||
class _OutstandingRequest<T> {
|
||||
_OutstandingRequest(this.method);
|
||||
static int _idCounter = 0;
|
||||
final id = '\${_idCounter++}';
|
||||
final String method;
|
||||
final _stackTrace = StackTrace.current;
|
||||
final _completer = Completer<T>();
|
||||
|
||||
Future<T> get future => _completer.future;
|
||||
|
||||
void complete(T value) => _completer.complete(value);
|
||||
void completeError(Object error) =>
|
||||
_completer.completeError(error, _stackTrace);
|
||||
}
|
||||
''');
|
||||
gen.writeln();
|
||||
gen.writeln('''
|
||||
typedef VmServiceFactory<T extends VmService> = T Function({
|
||||
required Stream<dynamic> /*String|List<int>*/ inStream,
|
||||
required void Function(String message) writeMessage,
|
||||
Log? log,
|
||||
DisposeHandler? disposeHandler,
|
||||
Future? streamClosed,
|
||||
String? wsUri,
|
||||
});
|
||||
''');
|
||||
|
||||
// The client side service implementation.
|
||||
gen.writeStatement('class VmService {');
|
||||
gen.writeStatement('late final StreamSubscription _streamSub;');
|
||||
gen.writeStatement('late final Function _writeMessage;');
|
||||
gen.writeStatement(
|
||||
'final _outstandingRequests = <String, _OutstandingRequest>{};');
|
||||
gen.writeStatement('final _services = <String, ServiceCallback>{};');
|
||||
gen.writeStatement('late final Log _log;');
|
||||
gen.write('''
|
||||
|
||||
/// The web socket URI pointing to the target VM service instance.
|
||||
final String? wsUri;
|
||||
|
||||
Stream<String> get onSend => _onSend.stream;
|
||||
final _onSend = StreamController<String>.broadcast(sync: true);
|
||||
|
||||
Stream<String> get onReceive => _onReceive.stream;
|
||||
final _onReceive = StreamController<String>.broadcast(sync: true);
|
||||
|
||||
Future<void> get onDone => _onDoneCompleter.future;
|
||||
final _onDoneCompleter = Completer<void>();
|
||||
|
||||
final _eventControllers = <String, StreamController<Event>>{};
|
||||
|
||||
StreamController<Event> _getEventController(String eventName) {
|
||||
StreamController<Event>? controller = _eventControllers[eventName];
|
||||
if (controller == null) {
|
||||
controller = StreamController.broadcast();
|
||||
_eventControllers[eventName] = controller;
|
||||
}
|
||||
return controller;
|
||||
}
|
||||
|
||||
late final DisposeHandler? _disposeHandler;
|
||||
|
||||
VmService(
|
||||
Stream<dynamic> /*String|List<int>*/ inStream,
|
||||
void Function(String message) writeMessage, {
|
||||
Log? log,
|
||||
DisposeHandler? disposeHandler,
|
||||
Future? streamClosed,
|
||||
this.wsUri,
|
||||
}) {
|
||||
_streamSub = inStream.listen(_processMessage,
|
||||
onDone: () => _onDoneCompleter.complete());
|
||||
_writeMessage = writeMessage;
|
||||
_log = log ?? _NullLog();
|
||||
_disposeHandler = disposeHandler;
|
||||
streamClosed?.then((_) {
|
||||
if (!_onDoneCompleter.isCompleted) {
|
||||
_onDoneCompleter.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static VmService defaultFactory({
|
||||
required Stream<dynamic> /*String|List<int>*/ inStream,
|
||||
required void Function(String message) writeMessage,
|
||||
Log? log,
|
||||
DisposeHandler? disposeHandler,
|
||||
Future? streamClosed,
|
||||
String? wsUri,
|
||||
}) {
|
||||
return VmService(
|
||||
inStream,
|
||||
writeMessage,
|
||||
log: log,
|
||||
disposeHandler: disposeHandler,
|
||||
streamClosed: streamClosed,
|
||||
wsUri: wsUri,
|
||||
);
|
||||
}
|
||||
|
||||
Stream<Event> onEvent(String streamId) => _getEventController(streamId).stream;
|
||||
''');
|
||||
|
||||
// streamCategories
|
||||
for (var s in streamCategories) {
|
||||
s.generate(gen);
|
||||
}
|
||||
|
||||
gen.writeln();
|
||||
for (var m in methods) {
|
||||
m.generate(gen);
|
||||
}
|
||||
gen.out(_implCode);
|
||||
gen.writeStatement('}');
|
||||
gen.writeln();
|
||||
gen.out(_rpcError);
|
||||
gen.writeln();
|
||||
gen.writeln('// enums');
|
||||
for (var e in enums) {
|
||||
if (e.name == 'EventKind') {
|
||||
_generateEventStream(gen);
|
||||
}
|
||||
e.generate(gen);
|
||||
}
|
||||
gen.writeln();
|
||||
gen.writeln('// types');
|
||||
types.where((t) => !t!.skip).forEach((t) => t!.generate(gen));
|
||||
}
|
||||
|
||||
void setDefaultValue(String typeName, String fieldName, String defaultValue) {
|
||||
types
|
||||
.firstWhere((t) => t!.name == typeName)!
|
||||
.fields
|
||||
.firstWhere((f) => f.name == fieldName)
|
||||
.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
void _generateEventStream(DartGenerator gen) {
|
||||
gen.writeln();
|
||||
gen.writeDocs('An enum of available event streams.');
|
||||
gen.writeln('abstract class EventStreams {');
|
||||
gen.writeln();
|
||||
|
||||
for (var c in streamCategories) {
|
||||
gen.writeln("static const String k${c.name} = '${c.name}';");
|
||||
}
|
||||
|
||||
gen.writeln('}');
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
267
pkg/vm_service/tool/dart/generate_dart_interface.dart
Normal file
267
pkg/vm_service/tool/dart/generate_dart_interface.dart
Normal file
|
@ -0,0 +1,267 @@
|
|||
// 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 'generate_dart_common.dart';
|
||||
|
||||
class VmServiceInterfaceApi extends Api {
|
||||
static const _interfaceHeaderCode = r'''
|
||||
// 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.
|
||||
|
||||
// This is a generated file. To regenerate, run `dart tool/generate.dart`.
|
||||
|
||||
/// A library providing an interface to implement the VM Service Protocol.
|
||||
library;
|
||||
|
||||
// ignore_for_file: overridden_fields
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:vm_service/vm_service.dart' hide ServiceExtensionRegistry, VmServerConnection, VmServiceInterface;
|
||||
|
||||
import 'service_extension_registry.dart';
|
||||
|
||||
export 'service_extension_registry.dart' show ServiceExtensionRegistry;
|
||||
''';
|
||||
|
||||
@override
|
||||
void generate(DartGenerator gen) {
|
||||
gen.out(_interfaceHeaderCode);
|
||||
gen.writeln("const String vmServiceVersion = '$serviceVersion';");
|
||||
gen.writeln();
|
||||
gen.writeStatement('''
|
||||
/// A class representation of the Dart VM Service Protocol.
|
||||
abstract interface class VmServiceInterface {
|
||||
/// Returns the stream for a given stream id.
|
||||
///
|
||||
/// This is not a part of the spec, but is needed for both the client and
|
||||
/// server to get access to the real event streams.
|
||||
Stream<Event> onEvent(String streamId);
|
||||
|
||||
/// Handler for calling extra service extensions.
|
||||
Future<Response> callServiceExtension(String method, {String? isolateId, Map<String, dynamic>? args});
|
||||
''');
|
||||
for (var m in methods) {
|
||||
m.generateDefinition(gen);
|
||||
gen.write(';');
|
||||
}
|
||||
gen.write('}');
|
||||
gen.writeln();
|
||||
|
||||
// The server class, takes a VmServiceInterface and delegates to it
|
||||
// automatically.
|
||||
gen.write('''
|
||||
class _PendingServiceRequest {
|
||||
Future<Map<String, Object?>> get future => _completer.future;
|
||||
final _completer = Completer<Map<String, Object?>>();
|
||||
|
||||
final dynamic originalId;
|
||||
|
||||
_PendingServiceRequest(this.originalId);
|
||||
|
||||
void complete(Map<String, Object?> response) {
|
||||
response['id'] = originalId;
|
||||
_completer.complete(response);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Dart VM Service Protocol connection that delegates requests to a
|
||||
/// [VmServiceInterface] implementation.
|
||||
///
|
||||
/// One of these should be created for each client, but they should generally
|
||||
/// share the same [VmServiceInterface] and [ServiceExtensionRegistry]
|
||||
/// instances.
|
||||
class VmServerConnection {
|
||||
final Stream<Map<String, Object>> _requestStream;
|
||||
final StreamSink<Map<String, Object?>> _responseSink;
|
||||
final ServiceExtensionRegistry _serviceExtensionRegistry;
|
||||
final VmServiceInterface _serviceImplementation;
|
||||
/// Used to create unique ids when acting as a proxy between clients.
|
||||
int _nextServiceRequestId = 0;
|
||||
|
||||
/// Manages streams for `streamListen` and `streamCancel` requests.
|
||||
final _streamSubscriptions = <String, StreamSubscription>{};
|
||||
|
||||
/// Completes when [_requestStream] is done.
|
||||
Future<void> get done => _doneCompleter.future;
|
||||
final _doneCompleter = Completer<void>();
|
||||
|
||||
/// Pending service extension requests to this client by id.
|
||||
final _pendingServiceExtensionRequests = <dynamic, _PendingServiceRequest>{};
|
||||
|
||||
VmServerConnection(this._requestStream, this._responseSink,
|
||||
this._serviceExtensionRegistry, this._serviceImplementation) {
|
||||
_requestStream.listen(_delegateRequest, onDone: _doneCompleter.complete);
|
||||
done.then((_) {
|
||||
for (var sub in _streamSubscriptions.values) {
|
||||
sub.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Invoked when the current client has registered some extension, and
|
||||
/// another client sends an RPC request for that extension.
|
||||
///
|
||||
/// We don't attempt to do any serialization or deserialization of the
|
||||
/// request or response in this case
|
||||
Future<Map<String, Object?>> _forwardServiceExtensionRequest(
|
||||
Map<String, Object?> request) {
|
||||
final originalId = request['id'];
|
||||
request = Map<String, Object?>.of(request);
|
||||
// Modify the request ID to ensure we don't have conflicts between
|
||||
// multiple clients ids.
|
||||
final newId = '\${_nextServiceRequestId++}:\$originalId';
|
||||
request['id'] = newId;
|
||||
var pendingRequest = _PendingServiceRequest(originalId);
|
||||
_pendingServiceExtensionRequests[newId] = pendingRequest;
|
||||
_responseSink.add(request);
|
||||
return pendingRequest.future;
|
||||
}
|
||||
|
||||
void _delegateRequest(Map<String, Object?> request) async {
|
||||
try {
|
||||
var id = request['id'];
|
||||
// Check if this is actually a response to a pending request.
|
||||
if (_pendingServiceExtensionRequests.containsKey(id)) {
|
||||
final pending = _pendingServiceExtensionRequests[id]!;
|
||||
pending.complete(Map<String, Object?>.of(request));
|
||||
return;
|
||||
}
|
||||
final method = request['method'] as String?;
|
||||
if (method == null) {
|
||||
throw RPCError(null, RPCErrorKind.kInvalidRequest.code,
|
||||
'Invalid Request', request);
|
||||
}
|
||||
final params = request['params'] as Map<String, dynamic>?;
|
||||
late Response response;
|
||||
|
||||
switch(method) {
|
||||
case 'registerService':
|
||||
$registerServiceImpl
|
||||
break;
|
||||
''');
|
||||
for (var m in methods) {
|
||||
if (m.name != 'registerService') {
|
||||
gen.writeln("case '${m.name}':");
|
||||
if (m.name == 'streamListen') {
|
||||
gen.writeln(streamListenCaseImpl);
|
||||
} else if (m.name == 'streamCancel') {
|
||||
gen.writeln(streamCancelCaseImpl);
|
||||
} else {
|
||||
bool firstParam = true;
|
||||
String nullCheck() {
|
||||
final result = firstParam ? '!' : '';
|
||||
if (firstParam) {
|
||||
firstParam = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (m.deprecated) {
|
||||
gen.writeln('// ignore: deprecated_member_use_from_same_package');
|
||||
}
|
||||
gen.write('response = await _serviceImplementation.${m.name}(');
|
||||
// Positional args
|
||||
m.args.where((arg) => !arg.optional).forEach((MethodArg arg) {
|
||||
if (arg.type.isArray) {
|
||||
gen.write(
|
||||
"${arg.type.listCreationRef}.from(params${nullCheck()}['${arg.name}'] ?? []), ");
|
||||
} else {
|
||||
gen.write("params${nullCheck()}['${arg.name}'], ");
|
||||
}
|
||||
});
|
||||
// Optional named args
|
||||
var namedArgs = m.args.where((arg) => arg.optional);
|
||||
if (namedArgs.isNotEmpty) {
|
||||
for (var arg in namedArgs) {
|
||||
if (arg.name == 'scope') {
|
||||
gen.writeln(
|
||||
"${arg.name}: params${nullCheck()}['${arg.name}']?.cast<String, String>(), ");
|
||||
} else {
|
||||
gen.writeln(
|
||||
"${arg.name}: params${nullCheck()}['${arg.name}'], ");
|
||||
}
|
||||
}
|
||||
}
|
||||
gen.writeln(');');
|
||||
}
|
||||
gen.writeln('break;');
|
||||
}
|
||||
}
|
||||
// Handle service extensions
|
||||
gen.writeln('default:');
|
||||
gen.writeln('''
|
||||
final registeredClient = _serviceExtensionRegistry.clientFor(method);
|
||||
if (registeredClient != null) {
|
||||
// Check for any client which has registered this extension, if we
|
||||
// have one then delegate the request to that client.
|
||||
_responseSink.add(
|
||||
await registeredClient._forwardServiceExtensionRequest(request));
|
||||
// Bail out early in this case, we are just acting as a proxy and
|
||||
// never get a `Response` instance.
|
||||
return;
|
||||
} else if (method.startsWith('ext.')) {
|
||||
// Remaining methods with `ext.` are assumed to be registered via
|
||||
// dart:developer, which the service implementation handles.
|
||||
final args = params == null ? null : Map<String, dynamic>.of(params);
|
||||
final isolateId = args?.remove('isolateId');
|
||||
response = await _serviceImplementation.callServiceExtension(method,
|
||||
isolateId: isolateId, args: args);
|
||||
} else {
|
||||
throw RPCError(method, RPCErrorKind.kMethodNotFound.code,
|
||||
'Method not found', request);
|
||||
}
|
||||
''');
|
||||
// Terminate the switch
|
||||
gen.writeln('}');
|
||||
|
||||
// Generate the json success response
|
||||
gen.write("""_responseSink.add({
|
||||
'jsonrpc': '2.0',
|
||||
'id': id,
|
||||
'result': response.toJson(),
|
||||
});
|
||||
""");
|
||||
|
||||
// Close the try block, handle errors
|
||||
gen.write(r'''
|
||||
} on SentinelException catch (e) {
|
||||
_responseSink.add({
|
||||
'jsonrpc': '2.0',
|
||||
'id': request['id'],
|
||||
'result': e.sentinel.toJson(),
|
||||
});
|
||||
} catch (e, st) {
|
||||
final error = e is RPCError
|
||||
? e.toMap()
|
||||
: {
|
||||
'code': RPCErrorKind.kInternalError.code,
|
||||
'message': '${request['method']}: $e',
|
||||
'data': {'details': '$st'},
|
||||
};
|
||||
_responseSink.add({
|
||||
'jsonrpc': '2.0',
|
||||
'id': request['id'],
|
||||
'error': error,
|
||||
});
|
||||
}
|
||||
''');
|
||||
|
||||
// terminate the _delegateRequest method
|
||||
gen.write('}');
|
||||
gen.writeln();
|
||||
|
||||
gen.write('}');
|
||||
gen.writeln();
|
||||
}
|
||||
|
||||
void setDefaultValue(String typeName, String fieldName, String defaultValue) {
|
||||
types
|
||||
.firstWhere((t) => t!.name == typeName)!
|
||||
.fields
|
||||
.firstWhere((f) => f.name == fieldName)
|
||||
.defaultValue = defaultValue;
|
||||
}
|
||||
}
|
|
@ -14,13 +14,17 @@ class DartGenerator {
|
|||
static const defaultColumnBoundary = 80;
|
||||
|
||||
final int colBoundary;
|
||||
final String interfaceName;
|
||||
|
||||
String _indent = '';
|
||||
final StringBuffer _buf = StringBuffer();
|
||||
|
||||
bool _previousWasEol = false;
|
||||
|
||||
DartGenerator({this.colBoundary = defaultColumnBoundary});
|
||||
DartGenerator({
|
||||
required this.interfaceName,
|
||||
this.colBoundary = defaultColumnBoundary,
|
||||
});
|
||||
|
||||
/// Write out the given dartdoc text, wrapping lines as necessary to flow
|
||||
/// along the column boundary.
|
||||
|
@ -28,8 +32,9 @@ class DartGenerator {
|
|||
docs = docs
|
||||
.replaceAll('[RPC error] code', 'RPC error code')
|
||||
.replaceAll('[RPC error]', '[RPCError]')
|
||||
.replaceAll('[SnapshotGraph]', '[HeapSnapshotGraph]')
|
||||
.replaceAllMapped(RegExp(r'\[([gs]et[a-zA-Z]+)\]'), (match) {
|
||||
return '[VmServiceInterface.${match[1]}]';
|
||||
return '[$interfaceName.${match[1]}]';
|
||||
});
|
||||
|
||||
docs = wrap(docs.trim(), colBoundary - _indent.length - 4);
|
||||
|
|
|
@ -9,19 +9,21 @@ import 'package:path/path.dart';
|
|||
import 'package:pub_semver/pub_semver.dart';
|
||||
|
||||
import 'common/generate_common.dart';
|
||||
import 'dart/generate_dart.dart' as dart show Api, api, DartGenerator;
|
||||
import 'dart/generate_dart_client.dart';
|
||||
import 'dart/generate_dart_common.dart';
|
||||
import 'dart/generate_dart_interface.dart';
|
||||
import 'java/generate_java.dart' as java show Api, api, JavaGenerator;
|
||||
|
||||
final bool _stampPubspecVersion = false;
|
||||
|
||||
/// Parse the 'service.md' into a model and generate both Dart and Java
|
||||
/// libraries.
|
||||
void main(List<String> args) async {
|
||||
String appDirPath = dirname(Platform.script.toFilePath());
|
||||
Future<void> main(List<String> args) async {
|
||||
final codeGeneratorDir = dirname(Platform.script.toFilePath());
|
||||
|
||||
// Parse service.md into a model.
|
||||
final file = File(
|
||||
normalize(join(appDirPath, '../../../runtime/vm/service/service.md')),
|
||||
normalize(join(codeGeneratorDir, '../../../runtime/vm/service/service.md')),
|
||||
);
|
||||
final document = Document();
|
||||
final buf = StringBuffer(file.readAsStringSync());
|
||||
|
@ -32,26 +34,81 @@ void main(List<String> args) async {
|
|||
// Generate code from the model.
|
||||
print('');
|
||||
|
||||
await _generateDart(appDirPath, nodes);
|
||||
await _generateJava(appDirPath, nodes);
|
||||
await _generateDartClient(codeGeneratorDir, nodes);
|
||||
await _generateDartInterface(codeGeneratorDir, nodes);
|
||||
await _generateJava(codeGeneratorDir, nodes);
|
||||
}
|
||||
|
||||
Future _generateDart(String appDirPath, List<Node> nodes) async {
|
||||
var outDirPath = normalize(join(appDirPath, '..', 'lib/src'));
|
||||
var outDir = Directory(outDirPath);
|
||||
if (!outDir.existsSync()) outDir.createSync(recursive: true);
|
||||
var outputFile = File(join(outDirPath, 'vm_service.dart'));
|
||||
var generator = dart.DartGenerator();
|
||||
dart.api = dart.Api();
|
||||
dart.api.parse(nodes);
|
||||
dart.api.generate(generator);
|
||||
outputFile.writeAsStringSync(generator.toString());
|
||||
ProcessResult result = Process.runSync('dart', ['format', outDirPath]);
|
||||
if (result.exitCode != 0) {
|
||||
print('dart format: ${result.stdout}\n${result.stderr}');
|
||||
throw result.exitCode;
|
||||
Future<void> _generateDartClient(
|
||||
String codeGeneratorDir, List<Node> nodes) async {
|
||||
var outputFilePath = await _generateDartCommon(
|
||||
api: VmServiceApi(),
|
||||
nodes: nodes,
|
||||
codeGeneratorDir: codeGeneratorDir,
|
||||
packageName: 'vm_service',
|
||||
interfaceName: 'VmService',
|
||||
);
|
||||
print('Wrote Dart client to $outputFilePath.');
|
||||
outputFilePath = await _generateDartCommon(
|
||||
api: VmServiceInterfaceApi(),
|
||||
nodes: nodes,
|
||||
codeGeneratorDir: codeGeneratorDir,
|
||||
packageName: 'vm_service',
|
||||
interfaceName: 'VmServiceInterface',
|
||||
fileNameOverride: 'vm_service_interface',
|
||||
);
|
||||
print('Wrote Dart temporary interface to $outputFilePath.');
|
||||
}
|
||||
|
||||
Future<void> _generateDartInterface(
|
||||
String codeGeneratorDir, List<Node> nodes) async {
|
||||
final outputFilePath = await _generateDartCommon(
|
||||
api: VmServiceInterfaceApi(),
|
||||
nodes: nodes,
|
||||
codeGeneratorDir: codeGeneratorDir,
|
||||
packageName: 'vm_service_interface',
|
||||
interfaceName: 'VmServiceInterface',
|
||||
);
|
||||
print('Wrote Dart interface to $outputFilePath.');
|
||||
}
|
||||
|
||||
Future<String> _generateDartCommon({
|
||||
required Api api,
|
||||
required List<Node> nodes,
|
||||
required String codeGeneratorDir,
|
||||
required String packageName,
|
||||
required String interfaceName,
|
||||
String? fileNameOverride,
|
||||
}) async {
|
||||
final outDirPath = normalize(
|
||||
join(
|
||||
codeGeneratorDir,
|
||||
'../..',
|
||||
packageName,
|
||||
'lib/src',
|
||||
),
|
||||
);
|
||||
final outDir = Directory(outDirPath);
|
||||
if (!outDir.existsSync()) {
|
||||
outDir.createSync(recursive: true);
|
||||
}
|
||||
|
||||
final outputFile = File(
|
||||
join(
|
||||
outDirPath,
|
||||
'${fileNameOverride ?? packageName}.dart',
|
||||
),
|
||||
);
|
||||
final generator = DartGenerator(interfaceName: interfaceName);
|
||||
|
||||
// Generate the code.
|
||||
api.parse(nodes);
|
||||
api.generate(generator);
|
||||
outputFile.writeAsStringSync(generator.toString());
|
||||
|
||||
// Clean up the code.
|
||||
await _runDartFormat(outDirPath);
|
||||
|
||||
if (_stampPubspecVersion) {
|
||||
// Update the pubspec file.
|
||||
Version version = ApiParseUtil.parseVersionSemVer(nodes);
|
||||
|
@ -60,12 +117,19 @@ Future _generateDart(String appDirPath, List<Node> nodes) async {
|
|||
// Validate that the changelog contains an entry for the current version.
|
||||
_checkUpdateChangelog(version);
|
||||
}
|
||||
|
||||
print('Wrote Dart to ${outputFile.path}.');
|
||||
return outputFile.path;
|
||||
}
|
||||
|
||||
Future _generateJava(String appDirPath, List<Node> nodes) async {
|
||||
var srcDirPath = normalize(join(appDirPath, '..', 'java', 'src'));
|
||||
Future<void> _runDartFormat(String outDirPath) async {
|
||||
ProcessResult result = Process.runSync('dart', ['format', outDirPath]);
|
||||
if (result.exitCode != 0) {
|
||||
print('dart format: ${result.stdout}\n${result.stderr}');
|
||||
throw result.exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _generateJava(String codeGeneratorDir, List<Node> nodes) async {
|
||||
var srcDirPath = normalize(join(codeGeneratorDir, '..', 'java', 'src'));
|
||||
var generator = java.JavaGenerator(srcDirPath);
|
||||
|
||||
final scriptPath = Platform.script.toFilePath();
|
||||
|
@ -83,7 +147,7 @@ Future _generateJava(String appDirPath, List<Node> nodes) async {
|
|||
.map((path) => relative(path, from: 'java'))
|
||||
.toList();
|
||||
generatedPaths.sort();
|
||||
File gitignoreFile = File(join(appDirPath, '..', 'java', '.gitignore'));
|
||||
File gitignoreFile = File(join(codeGeneratorDir, '..', 'java', '.gitignore'));
|
||||
gitignoreFile.writeAsStringSync('''
|
||||
# This is a generated file.
|
||||
|
||||
|
|
3
pkg/vm_service_interface/CHANGELOG.md
Normal file
3
pkg/vm_service_interface/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 1.0.0
|
||||
- Initial release.
|
||||
- Provides version 4.13 of the Dart VM service protocol.
|
3
pkg/vm_service_interface/CONTRIBUTING.md
Normal file
3
pkg/vm_service_interface/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Contributing to `package:vm_service` and `package:vm_service_interface`
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/dart-lang/sdk/blob/main/pkg/vm_service/CONTRIBUTING.md) in `package:vm_service` for details.
|
27
pkg/vm_service_interface/LICENSE
Normal file
27
pkg/vm_service_interface/LICENSE
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright 2023, the Dart project authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
1
pkg/vm_service_interface/OWNERS
Normal file
1
pkg/vm_service_interface/OWNERS
Normal file
|
@ -0,0 +1 @@
|
|||
file:/tools/OWNERS_VM
|
15
pkg/vm_service_interface/README.md
Normal file
15
pkg/vm_service_interface/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# `package:vm_service_interface`
|
||||
|
||||
[![pub package](https://img.shields.io/pub/v/vm_service_interface.svg)](https://pub.dev/packages/vm_service_interface)
|
||||
[![package publisher](https://img.shields.io/pub/publisher/vm_service_interface.svg)](https://pub.dev/packages/vm_service_interface/publisher)
|
||||
|
||||
A library providing an interface to implement the Dart VM service protocol.
|
||||
|
||||
The VM Service Protocol specification can be found at
|
||||
[github.com/dart-lang/sdk/runtime/vm/service/service.md](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md).
|
||||
|
||||
## Features and bugs
|
||||
|
||||
Please file feature requests and bugs at the [issue tracker][tracker].
|
||||
|
||||
[tracker]: https://github.com/dart-lang/sdk/issues
|
8
pkg/vm_service_interface/analysis_options.yaml
Normal file
8
pkg/vm_service_interface/analysis_options.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
include: package:lints/recommended.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# still 1 error in lib/src/vm_service_interface.dart
|
||||
- comment_references
|
||||
- directives_ordering
|
||||
- prefer_single_quotes
|
|
@ -0,0 +1,71 @@
|
|||
// 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:vm_service/vm_service.dart' show RPCError, Event, EventKind;
|
||||
|
||||
import 'stream_helpers.dart';
|
||||
import 'vm_service_interface.dart' show VmServerConnection;
|
||||
|
||||
/// A registry of custom service extensions to [VmServerConnection]s in which
|
||||
/// they were registered.
|
||||
class ServiceExtensionRegistry {
|
||||
/// Maps service extensions registered through the protocol to the
|
||||
/// [VmServerConnection] in which they were registered.
|
||||
///
|
||||
/// Note: this does not track services registered through `dart:developer`,
|
||||
/// only the services registered through the `_registerService` rpc method.
|
||||
final _extensionToConnection = <String, VmServerConnection>{};
|
||||
|
||||
/// Controller for tracking registration and unregistration events.
|
||||
final _eventController = StreamController<Event>.broadcast();
|
||||
|
||||
ServiceExtensionRegistry();
|
||||
|
||||
/// Registers [extension] for [client].
|
||||
///
|
||||
/// All future requests for [extension] will be routed to [client].
|
||||
void registerExtension(String extension, VmServerConnection client) {
|
||||
if (_extensionToConnection.containsKey(extension)) {
|
||||
throw RPCError('registerExtension', 111, 'Service already registered');
|
||||
}
|
||||
_eventController.sink.add(_toRegistrationEvent(extension));
|
||||
_extensionToConnection[extension] = client;
|
||||
// Remove the mapping if the client disconnects.
|
||||
client.done.whenComplete(() {
|
||||
_extensionToConnection.remove(extension);
|
||||
_eventController.sink.add(_toRegistrationEvent(extension,
|
||||
kind: EventKind.kServiceUnregistered));
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the [VmServerConnection] for a given [extension], or `null` if
|
||||
/// none is registered.
|
||||
///
|
||||
/// The result of this function should not be stored, because clients may
|
||||
/// shut down at any time.
|
||||
VmServerConnection? clientFor(String extension) =>
|
||||
_extensionToConnection[extension];
|
||||
|
||||
/// All of the currently registered extensions
|
||||
Iterable<String> get registeredExtensions => _extensionToConnection.keys;
|
||||
|
||||
/// Emits an [Event] of type `ServiceRegistered` for all current and future
|
||||
/// extensions that are registered, and `ServiceUnregistered` when those
|
||||
/// clients disconnect.
|
||||
Stream<Event> get onExtensionEvent => _eventController.stream
|
||||
.transform(startWithMany(registeredExtensions.map(_toRegistrationEvent)));
|
||||
|
||||
/// Creates a `_Service` stream event, with a default kind of
|
||||
/// [EventKind.kServiceRegistered].
|
||||
Event _toRegistrationEvent(String method,
|
||||
{String kind = EventKind.kServiceRegistered}) =>
|
||||
Event(
|
||||
kind: kind,
|
||||
service: method,
|
||||
method: method,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
}
|
100
pkg/vm_service_interface/lib/src/stream_helpers.dart
Normal file
100
pkg/vm_service_interface/lib/src/stream_helpers.dart
Normal file
|
@ -0,0 +1,100 @@
|
|||
// 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';
|
||||
|
||||
/// Copied from package:stream_transform.
|
||||
|
||||
/// Starts emitting values from [next] after the original stream is complete.
|
||||
///
|
||||
/// If the initial stream never finishes, the [next] stream will never be
|
||||
/// listened to.
|
||||
///
|
||||
/// If a single-subscription follows the a broadcast stream it may be listened
|
||||
/// to and never canceled.
|
||||
///
|
||||
/// If a broadcast stream follows any other stream it will miss any events which
|
||||
/// occur before the first stream is done. If a broadcast stream follows a
|
||||
/// single-subscription stream, pausing the stream while it is listening to the
|
||||
/// second stream will cause events to be dropped rather than buffered.
|
||||
StreamTransformer<T, T> followedBy<T>(Stream<T> next) => _FollowedBy<T>(next);
|
||||
|
||||
class _FollowedBy<T> extends StreamTransformerBase<T, T> {
|
||||
final Stream<T> _next;
|
||||
|
||||
_FollowedBy(this._next);
|
||||
|
||||
@override
|
||||
Stream<T> bind(Stream<T> first) {
|
||||
var controller = first.isBroadcast
|
||||
? StreamController<T>.broadcast(sync: true)
|
||||
: StreamController<T>(sync: true);
|
||||
|
||||
var next = first.isBroadcast && !_next.isBroadcast
|
||||
? _next.asBroadcastStream()
|
||||
: _next;
|
||||
|
||||
StreamSubscription<T>? subscription;
|
||||
var currentStream = first;
|
||||
var firstDone = false;
|
||||
var secondDone = false;
|
||||
|
||||
late Function currentDoneHandler;
|
||||
|
||||
listen() {
|
||||
subscription = currentStream.listen(controller.add,
|
||||
onError: controller.addError, onDone: () => currentDoneHandler());
|
||||
}
|
||||
|
||||
onSecondDone() {
|
||||
secondDone = true;
|
||||
controller.close();
|
||||
}
|
||||
|
||||
onFirstDone() {
|
||||
firstDone = true;
|
||||
currentStream = next;
|
||||
currentDoneHandler = onSecondDone;
|
||||
listen();
|
||||
}
|
||||
|
||||
currentDoneHandler = onFirstDone;
|
||||
|
||||
controller.onListen = () {
|
||||
assert(subscription == null);
|
||||
listen();
|
||||
final sub = subscription!;
|
||||
if (!first.isBroadcast) {
|
||||
controller
|
||||
..onPause = () {
|
||||
if (!firstDone || !next.isBroadcast) return sub.pause();
|
||||
sub.cancel();
|
||||
subscription = null;
|
||||
}
|
||||
..onResume = () {
|
||||
if (!firstDone || !next.isBroadcast) return sub.resume();
|
||||
listen();
|
||||
};
|
||||
}
|
||||
controller.onCancel = () {
|
||||
if (secondDone) return null;
|
||||
var toCancel = subscription!;
|
||||
subscription = null;
|
||||
return toCancel.cancel();
|
||||
};
|
||||
};
|
||||
return controller.stream;
|
||||
}
|
||||
}
|
||||
|
||||
StreamTransformer<T, T> startWithMany<T>(Iterable<T> initial) =>
|
||||
startWithStream<T>(Stream.fromIterable(initial));
|
||||
|
||||
StreamTransformer<T, T> startWithStream<T>(Stream<T> initial) =>
|
||||
StreamTransformer.fromBind((values) {
|
||||
if (values.isBroadcast && !initial.isBroadcast) {
|
||||
initial = initial.asBroadcastStream();
|
||||
}
|
||||
return initial.transform(followedBy(values));
|
||||
});
|
1694
pkg/vm_service_interface/lib/src/vm_service_interface.dart
Normal file
1694
pkg/vm_service_interface/lib/src/vm_service_interface.dart
Normal file
File diff suppressed because it is too large
Load diff
7
pkg/vm_service_interface/lib/vm_service_interface.dart
Normal file
7
pkg/vm_service_interface/lib/vm_service_interface.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
// 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.
|
||||
|
||||
library vm_service_interface;
|
||||
|
||||
export 'src/vm_service_interface.dart';
|
23
pkg/vm_service_interface/pubspec.yaml
Normal file
23
pkg/vm_service_interface/pubspec.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: vm_service_interface
|
||||
|
||||
version: 1.0.0
|
||||
description: >-
|
||||
A library providing an interface to implement the Dart VM service protocol.
|
||||
|
||||
repository: https://github.com/dart-lang/sdk/tree/main/pkg/vm_service/packages/vm_service_interface/
|
||||
|
||||
environment:
|
||||
sdk: ^3.0.0
|
||||
|
||||
dependencies:
|
||||
vm_service: '>=12.0.0 <14.0.0'
|
||||
|
||||
# We use 'any' version constraints here as we get our package versions from
|
||||
# the dart-lang/sdk repo's DEPS file. Note that this is a special case; the
|
||||
# best practice for packages is to specify their compatible version ranges.
|
||||
# See also https://dart.dev/tools/pub/dependencies.
|
||||
dev_dependencies:
|
||||
async: any
|
||||
lints: any
|
||||
mockito: any
|
||||
test: any
|
|
@ -9,16 +9,18 @@ import 'dart:convert';
|
|||
import 'package:async/async.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service.dart'
|
||||
hide ServiceExtensionRegistry, VmServiceInterface, VmServerConnection;
|
||||
import 'package:vm_service_interface/vm_service_interface.dart';
|
||||
|
||||
void main() {
|
||||
late MockVmService serviceMock;
|
||||
late MockVmServiceImplementation serviceMock;
|
||||
late StreamController<Map<String, Object>> requestsController;
|
||||
late StreamController<Map<String, Object?>> responsesController;
|
||||
late ServiceExtensionRegistry serviceRegistry;
|
||||
|
||||
setUp(() {
|
||||
serviceMock = MockVmService();
|
||||
serviceMock = MockVmServiceImplementation();
|
||||
requestsController = StreamController<Map<String, Object>>();
|
||||
responsesController = StreamController<Map<String, Object?>>();
|
||||
serviceRegistry = ServiceExtensionRegistry();
|
||||
|
@ -33,7 +35,7 @@ void main() {
|
|||
|
||||
group('method delegation', () {
|
||||
test('works for simple methods', () {
|
||||
var request = rpcRequest("getVersion");
|
||||
var request = rpcRequest('getVersion');
|
||||
var version = Version(major: 1, minor: 0);
|
||||
when(serviceMock.getVersion()).thenAnswer((_) => Future.value(version));
|
||||
expect(responsesController.stream, emits(rpcResponse(version)));
|
||||
|
@ -59,7 +61,7 @@ void main() {
|
|||
isSystemIsolate: false,
|
||||
);
|
||||
var request =
|
||||
rpcRequest("getIsolate", params: {'isolateId': isolate.id!});
|
||||
rpcRequest('getIsolate', params: {'isolateId': isolate.id!});
|
||||
when(serviceMock.getIsolate(isolate.id!))
|
||||
.thenAnswer((Invocation invocation) {
|
||||
expect(invocation.positionalArguments, equals([isolate.id]));
|
||||
|
@ -88,7 +90,7 @@ void main() {
|
|||
breakpoints: [],
|
||||
isSystemIsolate: false,
|
||||
);
|
||||
var request = rpcRequest("setVMTimelineFlags", params: {
|
||||
var request = rpcRequest('setVMTimelineFlags', params: {
|
||||
'isolateId': isolate.id!,
|
||||
// Note: the dynamic list below is intentional in order to exercise the
|
||||
// code under test.
|
||||
|
@ -109,7 +111,7 @@ void main() {
|
|||
test('with no params or isolateId', () {
|
||||
var extension = 'ext.cool';
|
||||
var request = rpcRequest(extension, params: null);
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
var response = Response()..json = {'hello': 'world'};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(isNull, named: 'isolateId'),
|
||||
|
@ -126,7 +128,7 @@ void main() {
|
|||
test('with isolateId and no other params', () {
|
||||
var extension = 'ext.cool';
|
||||
var request = rpcRequest(extension, params: {'isolateId': '1'});
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
var response = Response()..json = {'hello': 'world'};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(equals('1'), named: 'isolateId'),
|
||||
|
@ -144,7 +146,7 @@ void main() {
|
|||
var extension = 'ext.cool';
|
||||
var params = {'cool': 'option'};
|
||||
var request = rpcRequest(extension, params: params);
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
var response = Response()..json = {'hello': 'world'};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(isNull, named: 'isolateId'),
|
||||
|
@ -163,10 +165,10 @@ void main() {
|
|||
var params = {'cool': 'option'};
|
||||
var request =
|
||||
rpcRequest(extension, params: Map.of(params)..['isolateId'] = '1');
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
var response = Response()..json = {'hello': 'world'};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(equals("1"), named: 'isolateId'),
|
||||
isolateId: argThat(equals('1'), named: 'isolateId'),
|
||||
args: argThat(equals(params), named: 'args'),
|
||||
)).thenAnswer((Invocation invocation) {
|
||||
expect(invocation.namedArguments,
|
||||
|
@ -181,7 +183,7 @@ void main() {
|
|||
|
||||
group('error handling', () {
|
||||
test('special cases RPCError instances', () {
|
||||
var request = rpcRequest("getVersion");
|
||||
var request = rpcRequest('getVersion');
|
||||
var error =
|
||||
RPCError('getVersion', 1234, 'custom message', {'custom': 'data'});
|
||||
when(serviceMock.getVersion()).thenAnswer((_) => Future.error(error));
|
||||
|
@ -190,7 +192,7 @@ void main() {
|
|||
});
|
||||
|
||||
test('has a fallback for generic exceptions', () {
|
||||
var request = rpcRequest("getVersion");
|
||||
var request = rpcRequest('getVersion');
|
||||
var error = UnimplementedError();
|
||||
when(serviceMock.getVersion()).thenAnswer((_) => Future.error(error));
|
||||
expect(
|
||||
|
@ -346,7 +348,7 @@ void main() {
|
|||
requestsController2.stream,
|
||||
responsesController2.sink,
|
||||
serviceRegistry,
|
||||
VmService(Stream.empty(), (String _) => null),
|
||||
MockVmServiceImplementation(),
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -374,7 +376,8 @@ void main() {
|
|||
requestsController3.stream,
|
||||
responsesController3.sink,
|
||||
serviceRegistry,
|
||||
VmService(Stream.empty(), (String _) => null),
|
||||
MockVmServiceImplementation(),
|
||||
//VmService(Stream.empty(), (String _) => null),
|
||||
);
|
||||
expect(
|
||||
responsesController3.stream,
|
||||
|
@ -446,21 +449,21 @@ void main() {
|
|||
}
|
||||
|
||||
Map<String, Object> rpcRequest(String method,
|
||||
{Map<String, Object>? params = const {}, String id = "1"}) =>
|
||||
{Map<String, Object>? params = const {}, String id = '1'}) =>
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
if (params != null) "params": params,
|
||||
"id": id,
|
||||
'jsonrpc': '2.0',
|
||||
'method': method,
|
||||
if (params != null) 'params': params,
|
||||
'id': id,
|
||||
};
|
||||
|
||||
Map<String, Object> rpcResponse(Response response, {String id = "1"}) => {
|
||||
Map<String, Object> rpcResponse(Response response, {String id = '1'}) => {
|
||||
'jsonrpc': '2.0',
|
||||
'id': id,
|
||||
'result': response.toJson(),
|
||||
};
|
||||
|
||||
Map<String, Object> rpcErrorResponse(Object error, {String id = "1"}) {
|
||||
Map<String, Object> rpcErrorResponse(Object error, {String id = '1'}) {
|
||||
Map<String, Object> errorJson;
|
||||
if (error is RPCError) {
|
||||
errorJson = {
|
||||
|
@ -488,7 +491,7 @@ Map<String, Object> streamNotifyResponse(String streamId, Event event) {
|
|||
'jsonrpc': '2.0',
|
||||
'method': 'streamNotify',
|
||||
'params': {
|
||||
'streamId': '$streamId',
|
||||
'streamId': streamId,
|
||||
'event': event.toJson(),
|
||||
},
|
||||
};
|
||||
|
@ -502,7 +505,7 @@ Map<String, Object?> stripEventTimestamp(Map response) {
|
|||
return response as Map<String, Object?>;
|
||||
}
|
||||
|
||||
class MockVmService extends Mock implements VmServiceInterface {
|
||||
class MockVmServiceImplementation extends Mock implements VmServiceInterface {
|
||||
final streamControllers = <String, StreamController<Event>>{};
|
||||
|
||||
@override
|
Loading…
Reference in a new issue