diff --git a/BUILD.gn b/BUILD.gn index 2a6e0a1ff6b..c1179533453 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -61,6 +61,7 @@ group("runtime") { deps += [ "runtime/bin:dartaotruntime", "runtime/bin:dartaotruntime_product", + "utils/dart_runtime_service_vm:dart_runtime_service_vm_aot_snapshot", "utils/dartdev:dartdev_aot_snapshot", "utils/dds:dds_aot", "utils/dtd:dtd_aot", @@ -68,6 +69,7 @@ group("runtime") { ] } else { deps += [ + "utils/dart_runtime_service_vm", "utils/dds", "utils/dtd", "utils/kernel-service:frontend_server", diff --git a/pkg/dart_runtime_service/lib/dart_runtime_service.dart b/pkg/dart_runtime_service/lib/dart_runtime_service.dart index 4adf99da85a..5d7cd085781 100644 --- a/pkg/dart_runtime_service/lib/dart_runtime_service.dart +++ b/pkg/dart_runtime_service/lib/dart_runtime_service.dart @@ -6,3 +6,4 @@ export 'src/dart_runtime_service.dart'; export 'src/dart_runtime_service_backend.dart'; export 'src/dart_runtime_service_options.dart'; export 'src/exceptions.dart'; +export 'src/rpc_exceptions.dart'; diff --git a/pkg/dart_runtime_service/lib/src/clients.dart b/pkg/dart_runtime_service/lib/src/clients.dart index 38f9e1faea5..2e8060af345 100644 --- a/pkg/dart_runtime_service/lib/src/clients.dart +++ b/pkg/dart_runtime_service/lib/src/clients.dart @@ -7,6 +7,8 @@ import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:stream_channel/stream_channel.dart'; +import 'dart_runtime_service.dart'; +import 'dart_runtime_service_backend.dart'; import 'dart_runtime_service_rpcs.dart'; import 'event_streams.dart'; import 'rpc_exceptions.dart'; @@ -22,6 +24,7 @@ base class Client { required StreamChannel connection, required UnmodifiableNamedLookup clients, required EventStreamMethods eventStreamMethods, + required this.backend, }) { _clientPeer = json_rpc.Peer(connection, strictProtocolChecks: false); _internalRpcs = DartRuntimeServiceRpcs( @@ -35,6 +38,7 @@ base class Client { late json_rpc.Peer _clientPeer; late final DartRuntimeServiceRpcs _internalRpcs; + final DartRuntimeServiceBackend backend; /// The logger to be used when handling requests from this client. Logger get logger => Logger('Client ($name)'); @@ -66,6 +70,9 @@ base class Client { @mustCallSuper void registerRpcHandlers() { _internalRpcs.registerRpcsWithPeer(_clientPeer); + backend.registerRpcs(_clientPeer); + _internalRpcs.registerServiceExtensionForwarder(_clientPeer); + backend.registerFallbacks(_clientPeer); } /// Attempts to register a [service] to be provided by this client. @@ -159,10 +166,10 @@ base class Client { /// /// Call [addClient] when a client connects to your service. base class ClientManager { - ClientManager({required this.eventStreamMethods}); + ClientManager({required this.backend, required this.eventStreamMethods}); static const _kServicePrologue = 's'; - + final DartRuntimeServiceBackend backend; final EventStreamMethods eventStreamMethods; /// The set of [Client]s currently connected to the service. @@ -183,6 +190,7 @@ base class ClientManager { connection: connection, clients: UnmodifiableNamedLookup(clients), eventStreamMethods: eventStreamMethods, + backend: backend, ); final namespace = clients.add(client); client.initialize(namespace: namespace).then((_) { diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart index 3ceaff146ca..754883d9ad6 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service.dart @@ -18,6 +18,8 @@ import 'exceptions.dart'; import 'handlers.dart'; import 'utils.dart'; +typedef RpcResponse = Map; + class DartRuntimeService { DartRuntimeService._({required this.config, required this.backend}) : authCode = config.disableAuthCodes ? null : generateSecret() { @@ -26,12 +28,12 @@ class DartRuntimeService { } } - static Future start({ + static Future initialize({ required DartRuntimeServiceOptions config, required DartRuntimeServiceBackend backend, }) async { final service = DartRuntimeService._(config: config, backend: backend); - await service._startService(); + await service._initialize(); return service; } @@ -40,10 +42,31 @@ class DartRuntimeService { final DartRuntimeServiceBackend backend; /// The ws:// URI pointing to this [DartRuntimeService]'s server. - Uri get uri => _uri!; + /// + /// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not + /// active. + Uri get uri { + if (_server == null) { + throw const DartRuntimeServiceServerNotRunning(); + } + return _uri!; + } + Uri? _uri; + /// The http:// URI pointing to this [DartRuntimeService]'s server. + /// + /// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not + /// active. + Uri get httpUri => uri.replace(scheme: 'http'); + /// The sse:// URI pointing to this [DartRuntimeService]'s server. + /// + /// Throws [StateError] if [DartRuntimeServiceOptions.sseHandlerPath] is not + /// set. + /// + /// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not + /// active. Uri get sseUri { if (config.sseHandlerPath == null) { throw StateError('SSE handler path not configured.'); @@ -63,6 +86,7 @@ class DartRuntimeService { @visibleForTesting late final ClientManager clientManager = ClientManager( + backend: backend, eventStreamMethods: eventStreamManager, ); @@ -73,15 +97,42 @@ class DartRuntimeService { HttpServer? _server; + /// Initializes the service's state without starting the web server. + Future _initialize() async { + await backend.initialize(); + + if (config.autoStart) { + _logger.info('Autostart enabled. Starting server.'); + await _startServer(); + } + await backend.onServiceReady(this); + } + /// Shuts down the service and cleans up backend state. Future shutdown() async { - await _server?.close(force: true); - await clientManager.shutdown(); + await backend.clearState(); await backend.shutdown(); + await _shutdownServer(); + await clientManager.shutdown(); Logger.root.clearListeners(); } - Future _startService() async { + Future toggleServer() async { + // TODO(bkonyi): verify there's no race conditions + if (_server != null) { + await _shutdownServer(); + } else { + await _startServer(); + } + } + + Future _startServer() async { + if (_server != null) { + _logger.warning( + "Attempted to start the HTTP server, but it's already running.", + ); + throw const DartRuntimeServiceServerAlreadyRunning(); + } // TODO(bkonyi): support IPv6 final host = InternetAddress.loopbackIPv4.host; @@ -120,11 +171,28 @@ class DartRuntimeService { port: server.port, path: authCode != null ? '/$authCode' : '', ); + await backend.onServerStarted(httpUri: httpUri, wsUri: uri); _logger.info( - 'Dart Runtime Service started successfully and is listening at $uri.', + 'Dart Runtime Service HTTP server started successfully and is listening ' + 'at $uri.', ); } + Future _shutdownServer() async { + final server = _server; + if (server == null) { + _logger.warning( + "Attempting to shut down the HTTP server, but it's not " + 'running.', + ); + throw const DartRuntimeServiceServerNotRunning(); + } + _logger.info('Dart Runtime Service HTTP server is shutting down.'); + _server = null; + _uri = null; + await server.close(); + } + shelf.Handler _handlers() { _logger.info('Building Shelf handlers.'); var pipeline = const shelf.Pipeline(); diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart index e84aa1d6e95..fc814d307ba 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_backend.dart @@ -2,10 +2,50 @@ // 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 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + import 'dart_runtime_service.dart'; /// A backend implementation of a service used to inject non-common /// functionality into a [DartRuntimeService]. abstract class DartRuntimeServiceBackend { + /// Invoked by the [DartRuntimeService] when the service is initializing, + /// before the service's HTTP server is started. + /// + /// The backend should not expect for this to be invoked more than once. + Future initialize(); + + /// Invoked by the [DartRuntimeService] once it has completely finished + /// initializing. + /// + /// The backend should not expect for this to be invoked more than once. + Future onServiceReady(DartRuntimeService service); + + /// Invoked by the [DartRuntimeService] when the service is shutting down, + /// allowing for the backend to clean up its state. + /// + /// The backend should not expect to be reinitialized after shutting down. Future shutdown(); + + /// Invoked by the [DartRuntimeService] when the service is no longer + /// available, either due to the HTTP server being disabled or the service + /// shutting down. + /// + /// This is always invoked immediately before [shutdown]. + Future clearState(); + + /// Invoked by the [DartRuntimeService] when the service's HTTP server has + /// started. + Future onServerStarted({required Uri httpUri, required Uri wsUri}); + + /// Invoked by the [DartRuntimeService] to register handlers for the RPCs + /// provided by the backend. + void registerRpcs(json_rpc.Peer clientPeer); + + /// Invoked by the [DartRuntimeService] to register fallback handlers + /// provided by the backend. + /// + /// Backend fallbacks are executed after incoming RPC requests fail to match + /// any registered RPCs or service extensions provided by other clients. + void registerFallbacks(json_rpc.Peer clientPeer); } diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart index a33a9bfa3bb..63255030b32 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_options.dart @@ -11,6 +11,7 @@ class DartRuntimeServiceOptions { this.port = 0, this.disableAuthCodes = false, this.sseHandlerPath, + this.autoStart = true, }); /// If true, enables log output for the service. @@ -33,17 +34,22 @@ class DartRuntimeServiceOptions { /// Defaults to null. final String? sseHandlerPath; + /// If true, the HTTP server will be started on initialization. + final bool autoStart; + DartRuntimeServiceOptions copyWith({ bool? enableLogging, int? port, bool? disableAuthCodes, String? sseHandlerPath, + bool? autoStart, }) { return DartRuntimeServiceOptions( enableLogging: enableLogging ?? this.enableLogging, port: port ?? this.port, disableAuthCodes: disableAuthCodes ?? this.disableAuthCodes, sseHandlerPath: sseHandlerPath ?? this.sseHandlerPath, + autoStart: autoStart ?? this.autoStart, ); } } diff --git a/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart b/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart index 47e0607a1ad..ff9066cc517 100644 --- a/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart +++ b/pkg/dart_runtime_service/lib/src/dart_runtime_service_rpcs.dart @@ -15,7 +15,6 @@ import 'event_streams.dart'; import 'rpc_exceptions.dart'; import 'utils.dart'; -typedef RpcResponse = Map; typedef RpcHandlerWithNoParameters = FutureOr Function(); typedef RpcHandlerWithParameters = FutureOr Function(json_rpc.Parameters); @@ -80,7 +79,9 @@ final class DartRuntimeServiceRpcs { } }); } + } + void registerServiceExtensionForwarder(json_rpc.Peer clientPeer) { clientPeer.registerFallback(serviceExtensionForwarderFallback); } diff --git a/pkg/dart_runtime_service/lib/src/exceptions.dart b/pkg/dart_runtime_service/lib/src/exceptions.dart index 11c7d600e58..6b3cc5c3b87 100644 --- a/pkg/dart_runtime_service/lib/src/exceptions.dart +++ b/pkg/dart_runtime_service/lib/src/exceptions.dart @@ -20,3 +20,19 @@ final class DartRuntimeServiceFailedToStartException const DartRuntimeServiceFailedToStartException({required String message}) : super(message: 'Failed to start: $message'); } + +/// Thrown when the [DartRuntimeService] attempts to start the server when it's +/// already active. +final class DartRuntimeServiceServerAlreadyRunning + extends DartRuntimeServiceException { + const DartRuntimeServiceServerAlreadyRunning() + : super(message: 'The HTTP server is already running.'); +} + +/// Thrown when the [DartRuntimeService] attempts to shutdown the server when +/// it's not active. +final class DartRuntimeServiceServerNotRunning + extends DartRuntimeServiceException { + const DartRuntimeServiceServerNotRunning() + : super(message: 'The HTTP server is not running.'); +} diff --git a/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart b/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart index 09d1099b114..05252cbae07 100644 --- a/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart +++ b/pkg/dart_runtime_service/lib/src/rpc_exceptions.dart @@ -8,8 +8,9 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; enum RpcException { // These error codes must be kept in sync with those in vm/json_stream.h and // vmservice.dart. - serverError(code: SERVER_ERROR, message: 'Server error'), - methodNotFound(code: METHOD_NOT_FOUND, message: 'Method not found'), + serverError(code: SERVER_ERROR, message: 'Server error.'), + methodNotFound(code: METHOD_NOT_FOUND, message: 'Method not found.'), + internalError(code: INTERNAL_ERROR, message: 'Internal error.'), connectionDisposed(code: -32010, message: 'Service connection disposed.'), featureDisabled(code: 100, message: 'Feature is disabled.'), streamAlreadySubscribed(code: 103, message: 'Stream already subscribed.'), diff --git a/pkg/dart_runtime_service/test/utils/mocks.dart b/pkg/dart_runtime_service/test/utils/mocks.dart index b098f0e2e32..b55dc26ebc2 100644 --- a/pkg/dart_runtime_service/test/utils/mocks.dart +++ b/pkg/dart_runtime_service/test/utils/mocks.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import 'package:test/fake.dart'; /// Fake implementation of [DartRuntimeServiceBackend] that throws when @@ -12,6 +13,27 @@ import 'package:test/fake.dart'; /// a backend implementation. base class FakeDartRuntimeServiceBackend extends Fake implements DartRuntimeServiceBackend { + @override + Future initialize() async {} + + @override + Future onServiceReady(DartRuntimeService service) async {} + @override Future shutdown() async {} + + @override + Future clearState() async {} + + @override + Future onServerStarted({ + required Uri httpUri, + required Uri wsUri, + }) async {} + + @override + void registerRpcs(json_rpc.Peer clientPeer) {} + + @override + void registerFallbacks(json_rpc.Peer clientPeer) {} } diff --git a/pkg/dart_runtime_service/test/utils/utilities.dart b/pkg/dart_runtime_service/test/utils/utilities.dart index 6366f16cb30..7f25c231fc9 100644 --- a/pkg/dart_runtime_service/test/utils/utilities.dart +++ b/pkg/dart_runtime_service/test/utils/utilities.dart @@ -22,7 +22,7 @@ Future createDartRuntimeServiceForTest({ DartRuntimeService? service; addTearDown(() async => await service?.shutdown()); - service = await DartRuntimeService.start( + service = await DartRuntimeService.initialize( config: config, backend: FakeDartRuntimeServiceBackend(), ); diff --git a/pkg/dart_runtime_service_vm/OWNERS b/pkg/dart_runtime_service_vm/OWNERS new file mode 100644 index 00000000000..104bde46a19 --- /dev/null +++ b/pkg/dart_runtime_service_vm/OWNERS @@ -0,0 +1 @@ +file:/tools/OWNERS_DEV_INFRA \ No newline at end of file diff --git a/pkg/dart_runtime_service_vm/analysis_options.yaml b/pkg/dart_runtime_service_vm/analysis_options.yaml new file mode 100644 index 00000000000..d5248da039f --- /dev/null +++ b/pkg/dart_runtime_service_vm/analysis_options.yaml @@ -0,0 +1 @@ +include: ../dart_runtime_service/analysis_options.yaml diff --git a/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart b/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart new file mode 100644 index 00000000000..2afe4c906a1 --- /dev/null +++ b/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart @@ -0,0 +1,113 @@ +// Copyright (c) 2026, 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:io'; + +import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:dart_runtime_service_vm/dart_runtime_service_vm.dart'; + +// ignore: unreachable_from_main +const entrypoint = pragma( + 'vm:entry-point', + !bool.fromEnvironment('dart.vm.product'), +); + +// The TCP IP that DDS listens on. +@entrypoint +// ignore: unused_element +String _ddsIP = ''; + +// The TCP port that DDS listens on. +@entrypoint +// ignore: unused_element +int _ddsPort = 0; + +// The TCP port that the HTTP server listens on. +@entrypoint +int _port = 0; + +// The TCP IP that the HTTP server listens on. +@entrypoint +// ignore: unused_element +String _ip = ''; + +// Should the HTTP server auto start? +@entrypoint +bool _autoStart = false; + +// Should the HTTP server require an auth code? +@entrypoint +bool _authCodesDisabled = false; + +// Should the HTTP server run in devmode? +@entrypoint +// ignore: unused_element +bool _originCheckDisabled = false; + +// Location of file to output VM service connection info. +@entrypoint +// ignore: unused_element +String? _serviceInfoFilename; + +@entrypoint +// ignore: unused_element +bool _isWindows = false; + +@entrypoint +// ignore: unused_element +bool _isFuchsia = false; + +@entrypoint +Stream Function(ProcessSignal signal)? _signalWatch; + +@entrypoint +// ignore: unused_element +StreamSubscription? _signalSubscription; + +@entrypoint +// ignore: unused_element +bool _serveDevtools = true; + +@entrypoint +// ignore: unused_element +bool _enableServicePortFallback = false; + +@entrypoint +// ignore: unused_element +bool _waitForDdsToAdvertiseService = false; + +@entrypoint +// ignore: unused_element +bool _printDtd = false; + +// ignore: unused_element +File? _residentCompilerInfoFile; + +@entrypoint +// ignore: unused_element +void _populateResidentCompilerInfoFile( + /// If either `--resident-compiler-info-file` or `--resident-server-info-file` + /// was supplied on the command line, the CLI argument should be forwarded as + /// the argument to this parameter. If neither option was supplied, the + /// argument to this parameter should be null. + String? residentCompilerInfoFilePathArgumentFromCli, +) { + // TODO(bkonyi): implement +} + +Future main([List args = const []]) async { + if (args case ['--help']) { + return; + } + await DartRuntimeService.initialize( + config: DartRuntimeServiceOptions( + enableLogging: true, + port: _port, + disableAuthCodes: _authCodesDisabled, + autoStart: _autoStart, + ), + backend: DartRuntimeServiceVMBackend(signalWatch: _signalWatch!), + ); +} diff --git a/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart new file mode 100644 index 00000000000..0404404818f --- /dev/null +++ b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart @@ -0,0 +1,93 @@ +// Copyright (c) 2026, 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:io'; + +import 'package:dart_runtime_service/dart_runtime_service.dart'; + +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:logging/logging.dart'; + +import 'src/native_bindings.dart'; + +class DartRuntimeServiceVMBackend extends DartRuntimeServiceBackend { + /// The backend implementation for the Dart VM Service. + /// + /// [signalWatch] is the internal implementation of [ProcessSignal.watch], + /// which bypasses checks that prevent [ProcessSignal.sigquit] from being + /// watched. + DartRuntimeServiceVMBackend({required this.signalWatch}); + + /// The internal implementation of [ProcessSignal.watch]. + final Stream Function(ProcessSignal signal) signalWatch; + + final _nativeBindings = NativeBindings(); + final _logger = Logger('VM Backend'); + + StreamSubscription? _sigquitSubscription; + + @override + Future initialize() async { + _logger.info('Initializing...'); + _nativeBindings.onStart(); + _logger.info('Initialized!'); + } + + @override + Future onServiceReady(DartRuntimeService service) async { + // SIGQUIT isn't supported on Fuchsia or Windows. + if (Platform.isFuchsia || Platform.isWindows) { + return; + } + _sigquitSubscription = signalWatch(ProcessSignal.sigquit).listen((_) { + _logger.info('SIGQUIT received. Toggling VM Service HTTP server.'); + service.toggleServer(); + }); + } + + @override + Future onServerStarted({ + required Uri httpUri, + required Uri wsUri, + }) async { + // TODO(bkonyi): handle DDS connection case. + stdout.writeln('The Dart VM service is listening on $httpUri'); + _nativeBindings.onServerAddressChange(httpUri.toString()); + } + + @override + Future clearState() async { + // Do nothing for now. + } + + @override + Future shutdown() async { + await _sigquitSubscription?.cancel(); + _nativeBindings.onExit(); + } + + @override + void registerRpcs(json_rpc.Peer clientPeer) { + // The VM service handles its service requests in service.cc. + } + + @override + void registerFallbacks(json_rpc.Peer clientPeer) { + // If the registered Dart RPC handlers can't handle a request, forward it + // it to the native VM service implementation for processing. + clientPeer.registerFallback(sendToRuntime); + } + + /// Sends service requests to the Dart VM runtime for processing. + Future sendToRuntime(json_rpc.Parameters request) async { + final method = request.method; + final params = request.asMap.cast(); + if (params case {'isolateId': final String _}) { + // TODO(bkonyi): handle isolate requests + RpcException.serverError.throwException(); + } + return await _nativeBindings.sendToVM(method: method, params: params); + } +} diff --git a/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart b/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart new file mode 100644 index 00000000000..ee400b34102 --- /dev/null +++ b/pkg/dart_runtime_service_vm/lib/src/native_bindings.dart @@ -0,0 +1,148 @@ +// Copyright (c) 2026, 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 special situation where we're allowed to import dart:_vmservice +// from outside the core libraries to access the native entrypoints. +// +// See VmTarget in package:vm for the exception for this library to access +// dart:_vmservice. +// ignore: uri_does_not_exist +import 'dart:_vmservice' as vm_service_natives; +import 'dart:async'; +import 'dart:convert'; +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +/// Allows for sending messages to the native VM service implementation. +class NativeBindings { + static final jsonUtf8Decoder = json.fuse(utf8); + + /// Sends a general RPC to the VM for processing. + /// + /// The RPC is not executed in the scope of any particular isolate. + Future sendToVM({ + required String method, + required Map params, + }) { + final receivePort = RawReceivePort(null, 'VM Message'); + final completer = Completer(); + receivePort.handler = (Object value) { + receivePort.close(); + try { + completer.complete(_toResponse(value: value)); + } on json_rpc.RpcException catch (e) { + completer.completeError(e); + } + }; + vm_service_natives.sendRootServiceMessage( + _toRequest(responsePort: receivePort, method: method, params: params), + ); + return completer.future; + } + + /// Notifies the VM that the VM service server has finished initializing. + void onStart() => vm_service_natives.onStart(); + + /// Notifies the VM that the VM service server has finished exiting. + void onExit() => vm_service_natives.onExit(); + + /// Notifies the VM that the VM service server address has been updated. + /// + /// If [address] is null, the VM will assume the server is not running. + void onServerAddressChange(String? address) => + vm_service_natives.onServerAddressChange(address); + + RpcResponse _toResponse({required Object value}) { + const kResult = 'result'; + const kError = 'error'; + const kCode = 'code'; + const kMessage = 'message'; + const kData = 'data'; + + final Object? converted; + if (value case [final Uint8List utf8String]) { + converted = jsonUtf8Decoder.decode(utf8String); + } else { + RpcException.internalError.throwException(); + } + if (converted case {kResult: final Map result}) { + return result; + } else if (converted case { + kError: {kCode: final int code, kMessage: final String message}, + }) { + final data = converted[kData]; + throw json_rpc.RpcException(code, message, data: data); + } else { + RpcException.internalError.throwException(); + } + } + + // Calls toString on all non-String elements of [list]. We do this so all + // elements in the list are strings, making consumption by C++ simpler. + // This has a side effect that boolean literal values like true become 'true' + // and thus indistinguishable from the string literal 'true'. + static void _convertAllToStringInPlace(List list) { + for (var i = 0; i < list.length; i++) { + list[i] = list[i].toString(); + } + } + + List _toRequest({ + required RawReceivePort responsePort, + required String method, + required Map params, + }) { + final parametersAreObjects = _methodNeedsObjectParameters(method); + final keys = params.keys.toList(growable: false); + final values = params.values.cast().toList(growable: false); + if (!parametersAreObjects) { + _convertAllToStringInPlace(values); + } + + // This is the request ID that will be inserted into the JSON response in + // service.cc. package:json_rpc_2 already handles these IDs, so we just + // pass in a placeholder for now until we can update Service::InvokeMethod + // to not expect it. + // TODO(bkonyi): remove request ID from service message. + const kPlaceholderRequestId = -1; + + // Keep in sync with Service::InvokeMethod in service.cc. + return List.filled(7, null) + ..[0] = + 0 // Make room for OOB message type. + ..[1] = responsePort.sendPort + ..[2] = kPlaceholderRequestId + ..[3] = method + ..[4] = parametersAreObjects + ..[5] = keys + ..[6] = values; + } + + // We currently support two ways of passing parameters from Dart code to C + // code. The original way always converts the parameters to strings before + // passing them over. Our goal is to convert all C handlers to take the + // parameters as Dart objects but until the conversion is complete, we + // maintain the list of supported methods below. + bool _methodNeedsObjectParameters(String method) { + switch (method) { + case '_listDevFS': + case '_listDevFSFiles': + case '_createDevFS': + case '_deleteDevFS': + case '_writeDevFSFile': + case '_writeDevFSFiles': + case '_readDevFSFile': + case '_spawnUri': + case '_reloadKernel': + case '_reloadSources': + case 'reloadSources': + return true; + default: + return false; + } + } +} diff --git a/pkg/dart_runtime_service_vm/pubspec.yaml b/pkg/dart_runtime_service_vm/pubspec.yaml new file mode 100644 index 00000000000..0f7be90657d --- /dev/null +++ b/pkg/dart_runtime_service_vm/pubspec.yaml @@ -0,0 +1,14 @@ +name: dart_runtime_service_vm +# This package is not intended for consumption on pub.dev. DO NOT publish. +publish_to: none + +environment: + sdk: ^3.8.0 + +resolution: workspace + +# Use 'any' constraints here; we get our versions from the DEPS file. +dependencies: + dart_runtime_service: any + json_rpc_2: any + logging: any diff --git a/pkg/vm/lib/modular/target/vm.dart b/pkg/vm/lib/modular/target/vm.dart index 5e85637a087..3c9a4bf066b 100644 --- a/pkg/vm/lib/modular/target/vm.dart +++ b/pkg/vm/lib/modular/target/vm.dart @@ -442,7 +442,9 @@ class VmTarget extends Target { importer.path.contains('runtime/tests/vm/dart') || importer.path.contains('tests/standalone/io') || importer.path.contains('test-lib') || - importer.path.contains('tests/ffi'); + importer.path.contains('tests/ffi') || + (importer.path == 'dart_runtime_service_vm/src/native_bindings.dart' && + imported.path == '_vmservice'); @override Component configureComponent(Component component) { diff --git a/pubspec.yaml b/pubspec.yaml index cbb9143599b..43b6e922526 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ workspace: - pkg/dart_data_home - pkg/dart_internal - pkg/dart_runtime_service + - pkg/dart_runtime_service_vm - pkg/dart_service_protocol_shared - pkg/dds - pkg/dds_service_extensions diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 7f46c4b8c47..ea85616ceef 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -245,6 +245,10 @@ config("dart_config") { defines += [ "DART_DYNAMIC_MODULES" ] } + if (include_experimental_vm_service) { + defines += [ "EXPERIMENTAL_VM_SERVICE" ] + } + if (is_fuchsia) { lib_dirs = [ "${fuchsia_arch_root}/lib" ] diff --git a/runtime/bin/dartdev.cc b/runtime/bin/dartdev.cc index 42938f6ad99..98295586926 100644 --- a/runtime/bin/dartdev.cc +++ b/runtime/bin/dartdev.cc @@ -1065,24 +1065,12 @@ void main(int argc, char** argv) { Loader::InitOnce(); - // Setup script_name to point to the dartdev AOT snapshot. - auto dartdev_path = DartDev::ResolvedSnapshotPath(); - char* script_name = dartdev_path.get(); - if (script_name == nullptr || !CheckForInvalidPath(script_name)) { - Syslog::PrintErr("Unable to find AOT snapshot for dartdev\n"); + auto [app_snapshot, script_name] = + Snapshot::TryReadSDKSnapshot("dartdev_aot.dart.snapshot"); + if (app_snapshot == nullptr) { FreeConvertedArgs(argc, argv, argv_converted); Platform::Exit(kErrorExitCode); } - AppSnapshot* app_snapshot = Snapshot::TryReadAppSnapshot( - script_name, /*force_load_from_memory*/ false, /*decode_uri*/ false); - if (app_snapshot == nullptr || !app_snapshot->IsAOT()) { - Syslog::PrintErr("%s is not an AOT snapshot\n", script_name); - FreeConvertedArgs(argc, argv, argv_converted); - if (app_snapshot != nullptr) { - delete app_snapshot; - } - Platform::Exit(kErrorExitCode); - } app_snapshot->SetBuffers( &ignore_vm_snapshot_data, &ignore_vm_snapshot_instructions, &app_isolate_snapshot_data, &app_isolate_snapshot_instructions); @@ -1152,7 +1140,7 @@ void main(int argc, char** argv) { // - Exit the process due to some command parsing errors // - Run the Dart script in a JIT mode by execing the JIT runtime // - Run the Dart AOT snapshot by creating a new Isolate - DartDev::RunDartDev(script_name, &dart_vm_options, &dart_options); + DartDev::RunDartDev(script_name.get(), &dart_vm_options, &dart_options); // Terminate process exit-code handler. Process::TerminateExitCodeHandler(); diff --git a/runtime/bin/main_impl.cc b/runtime/bin/main_impl.cc index 966f5270fd1..472dc1cdd31 100644 --- a/runtime/bin/main_impl.cc +++ b/runtime/bin/main_impl.cc @@ -532,8 +532,6 @@ static Dart_Isolate CreateAndSetupKernelIsolate(const char* script_uri, #endif // !defined(EXCLUDE_CFE_AND_KERNEL_PLATFORM) // Returns newly created Service Isolate on success, nullptr on failure. -// For now we only support the service isolate coming up from sources -// which are compiled by the VM parser. static Dart_Isolate CreateAndSetupServiceIsolate(const char* script_uri, const char* packages_config, Dart_IsolateFlags* flags, @@ -547,30 +545,48 @@ static Dart_Isolate CreateAndSetupServiceIsolate(const char* script_uri, packages_config, nullptr, false); ASSERT(flags != nullptr); + const uint8_t* isolate_snapshot_data = nullptr; + const uint8_t* isolate_snapshot_instructions = nullptr; + +#if defined(EXPERIMENTAL_VM_SERVICE) + if (Options::experimental_vm_service()) { + VmService::enable_experimental_vm_service = true; + auto [app_snapshot, script_name] = Snapshot::TryReadSDKSnapshot( +#if defined(DART_PRECOMIPLED_RUNTIME) + "dart_runtime_service_vm_aot.dart.snapshot"); +#else + "dart_runtime_service_vm.dart.snapshot"); +#endif // defined(DART_PRECOMPILED_RUNTIME) + if (app_snapshot == nullptr) { + Platform::Exit(kErrorExitCode); + } + const uint8_t* ignore_vm_snapshot_data; + const uint8_t* ignore_vm_snapshot_instructions; + app_snapshot->SetBuffers( + &ignore_vm_snapshot_data, &ignore_vm_snapshot_instructions, + &isolate_snapshot_data, &isolate_snapshot_instructions); + } else { +#endif // defined(EXPERIMENTAL_VM_SERVICE) #if defined(DART_PRECOMPILED_RUNTIME) - // AOT: The service isolate is included in any AOT snapshot in non-PRODUCT - // mode - so we launch the vm-service from the main app AOT snapshot. - const uint8_t* isolate_snapshot_data = app_isolate_snapshot_data; - const uint8_t* isolate_snapshot_instructions = - app_isolate_snapshot_instructions; - isolate = Dart_CreateIsolateGroup( - script_uri, DART_VM_SERVICE_ISOLATE_NAME, isolate_snapshot_data, - isolate_snapshot_instructions, flags, isolate_group_data, - /*isolate_data=*/nullptr, error); + // AOT: The service isolate is included in any AOT snapshot in non-PRODUCT + // mode - so we launch the vm-service from the main app AOT snapshot. + isolate_snapshot_data = app_isolate_snapshot_data; + isolate_snapshot_instructions = app_isolate_snapshot_instructions; #else // JIT: Service isolate uses the core libraries snapshot. - // Set flag to load and retain the vmservice library. flags->load_vmservice_library = true; flags->null_safety = true; // Service isolate runs in sound null safe mode. - const uint8_t* isolate_snapshot_data = core_isolate_snapshot_data; - const uint8_t* isolate_snapshot_instructions = - core_isolate_snapshot_instructions; + isolate_snapshot_data = core_isolate_snapshot_data; + isolate_snapshot_instructions = core_isolate_snapshot_instructions; +#endif // defined(DART_PRECOMPILED_RUNTIME) +#if defined(EXPERIMENTAL_VM_SERVICE) + } +#endif // defined(EXPERIMENTAL_VM_SERVICE) isolate = Dart_CreateIsolateGroup( script_uri, DART_VM_SERVICE_ISOLATE_NAME, isolate_snapshot_data, isolate_snapshot_instructions, flags, isolate_group_data, /*isolate_data=*/nullptr, error); -#endif // !defined(DART_PRECOMPILED_RUNTIME) if (isolate == nullptr) { delete isolate_group_data; return nullptr; diff --git a/runtime/bin/main_options.h b/runtime/bin/main_options.h index 72105506059..0c093ee0d13 100644 --- a/runtime/bin/main_options.h +++ b/runtime/bin/main_options.h @@ -62,7 +62,8 @@ namespace bin { V(profile_microtasks, profile_microtasks) \ /* The purpose of this flag is documented in */ \ /* pkg/dartdev/lib/src/commands/run.dart. */ \ - V(resident, resident) + V(resident, resident) \ + V(experimental_vm_service, experimental_vm_service) // Boolean flags that have a short form. #define SHORT_BOOL_OPTIONS_LIST(V) \ diff --git a/runtime/bin/snapshot_utils.cc b/runtime/bin/snapshot_utils.cc index e5e2e1c9612..d48aa11b409 100644 --- a/runtime/bin/snapshot_utils.cc +++ b/runtime/bin/snapshot_utils.cc @@ -11,6 +11,7 @@ #include "bin/dfe.h" #include "bin/elf_loader.h" #include "bin/error_exit.h" +#include "bin/exe_utils.h" #include "bin/file.h" #include "bin/macho_loader.h" #include "bin/platform.h" @@ -846,6 +847,67 @@ static void WriteSnapshotFile(const char* filename, } #endif +// TODO(bkonyi): dedup +static bool CheckForInvalidPath(const char* path) { + // TODO(zichangguo): "\\?\" is a prefix for paths on Windows. + // Arguments passed are parsed as an URI. "\\?\" causes problems as a part + // of URIs. This is a temporary workaround to prevent VM from crashing. + // Issue: https://github.com/dart-lang/sdk/issues/42779 + if (strncmp(path, R"(\\?\)", 4) == 0) { + Syslog::PrintErr(R"(\\?\ prefix is not supported)"); + return false; + } + return true; +} + +std::pair Snapshot::TryReadSDKSnapshot( + const char* snapshot_name) { + auto try_resolve_path = [&](CStringUniquePtr dir_prefix) { + // |dir_prefix| includes the last path separator. + // First assume we're in dart-sdk/bin. + char* snapshot_path = + Utils::SCreate("%ssnapshots/%s", dir_prefix.get(), snapshot_name); + if (File::Exists(nullptr, snapshot_path)) { + return CStringUniquePtr(snapshot_path); + } + free(snapshot_path); + + // If we're not in dart-sdk/bin, we might be in one of the $SDK/out*/ + // directories, Try to use a snapshot from that directory. + snapshot_path = Utils::SCreate("%s%s", dir_prefix.get(), snapshot_name); + if (File::Exists(nullptr, snapshot_path)) { + return CStringUniquePtr(snapshot_path); + } + free(snapshot_path); + return CStringUniquePtr(nullptr); + }; + + auto script_path = + try_resolve_path(EXEUtils::GetDirectoryPrefixFromResolvedExeName()); + if (script_path == nullptr) { + script_path = + try_resolve_path(EXEUtils::GetDirectoryPrefixFromUnresolvedExeName()); + } + if (script_path == nullptr || !CheckForInvalidPath(script_path.get())) { + Syslog::PrintErr("Unable to find snapshot: %s\n", snapshot_name); + return std::make_pair(static_cast(nullptr), + std::move(script_path)); + } + + AppSnapshot* app_snapshot = + TryReadAppSnapshot(script_path.get(), /*force_load_from_memory*/ false, + /*decode_uri*/ false); + if (app_snapshot == nullptr) { + Syslog::PrintErr("%s is not a valid snapshot\n", script_path.get()); + if (app_snapshot != nullptr) { + delete app_snapshot; + } + return std::make_pair(static_cast(nullptr), + std::move(script_path)); + } + return std::make_pair(app_snapshot, std::move(script_path)); +} + static bool WriteInt64(File* file, int64_t size) { return file->WriteFully(&size, sizeof(size)); } diff --git a/runtime/bin/snapshot_utils.h b/runtime/bin/snapshot_utils.h index 3103d123405..a68d152eebd 100644 --- a/runtime/bin/snapshot_utils.h +++ b/runtime/bin/snapshot_utils.h @@ -5,6 +5,8 @@ #ifndef RUNTIME_BIN_SNAPSHOT_UTILS_H_ #define RUNTIME_BIN_SNAPSHOT_UTILS_H_ +#include + #include "bin/dartutils.h" #include "platform/globals.h" @@ -59,6 +61,8 @@ class Snapshot { static AppSnapshot* TryReadAppSnapshot(const char* script_uri, bool force_load_from_memory = false, bool decode_uri = true); + static std::pair TryReadSDKSnapshot( + const char* snapshot_name); static void WriteAppSnapshot(const char* filename, uint8_t* isolate_data_buffer, intptr_t isolate_data_size, diff --git a/runtime/bin/vmservice_impl.cc b/runtime/bin/vmservice_impl.cc index 42208758425..a99635ae32a 100644 --- a/runtime/bin/vmservice_impl.cc +++ b/runtime/bin/vmservice_impl.cc @@ -20,6 +20,8 @@ namespace bin { #if !defined(PRODUCT) +bool VmService::enable_experimental_vm_service = false; + #define RETURN_ERROR_HANDLE(handle) \ if (Dart_IsError(handle)) { \ return handle; \ @@ -33,9 +35,6 @@ namespace bin { return false; \ } -static constexpr const char* kVMServiceIOLibraryUri = "dart:vmservice_io"; -static constexpr const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost"; - void NotifyServerState(Dart_NativeArguments args) { Dart_EnterScope(); const char* uri_chars; @@ -66,6 +65,8 @@ struct VmServiceIONativeEntry { }; static VmServiceIONativeEntry _VmServiceIONativeEntries[] = { + // TODO(bkonyi): these aren't used by any known embedders and can be + // removed. {"VMServiceIO_NotifyServerState", 1, NotifyServerState}, {"VMServiceIO_Shutdown", 0, Shutdown}, }; @@ -105,7 +106,12 @@ const uint8_t* VmServiceIONativeSymbol(Dart_NativeFunction nf) { const char* VmService::error_msg_ = nullptr; char VmService::server_uri_[kServerUriStringBufferSize]; +static constexpr const char* kVMServiceIOLibraryUri = "dart:vmservice_io"; + void VmService::SetNativeResolver() { + if (enable_experimental_vm_service) { + return; + } Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri); Dart_Handle library = Dart_LookupLibrary(url); if (!Dart_IsError(library)) { @@ -114,6 +120,8 @@ void VmService::SetNativeResolver() { } } +static constexpr const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost"; + bool VmService::Setup(const char* server_ip, intptr_t server_port, bool dev_mode_server, @@ -140,14 +148,16 @@ bool VmService::Setup(const char* server_ip, /*flag_profile_microtasks=*/false, DartIoSettings{}); SHUTDOWN_ON_ERROR(result); - Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri); - Dart_Handle library = Dart_LookupLibrary(url); - SHUTDOWN_ON_ERROR(library); - result = Dart_SetRootLibrary(library); - SHUTDOWN_ON_ERROR(library); - result = Dart_SetNativeResolver(library, VmServiceIONativeResolver, - VmServiceIONativeSymbol); - SHUTDOWN_ON_ERROR(result); + if (!enable_experimental_vm_service) { + Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri); + Dart_Handle library = Dart_LookupLibrary(url); + SHUTDOWN_ON_ERROR(library); + result = Dart_SetRootLibrary(library); + SHUTDOWN_ON_ERROR(library); + result = Dart_SetNativeResolver(library, VmServiceIONativeResolver, + VmServiceIONativeSymbol); + SHUTDOWN_ON_ERROR(result); + } // Make runnable. Dart_ExitScope(); @@ -161,7 +171,7 @@ bool VmService::Setup(const char* server_ip, Dart_EnterIsolate(isolate); Dart_EnterScope(); - library = Dart_RootLibrary(); + Dart_Handle library = Dart_RootLibrary(); SHUTDOWN_ON_ERROR(library); // Set HTTP server state. @@ -172,6 +182,12 @@ bool VmService::Setup(const char* server_ip, // port when the HTTP server is started. server_port = 0; } +#if defined(EXPERIMENTAL_VM_SERVICE) + if (enable_experimental_vm_service) { + // TODO(bkonyi): remove once DDS support is added. + wait_for_dds_to_advertise_service = false; + } +#endif if (wait_for_dds_to_advertise_service) { result = DartUtils::SetStringField(library, "_ddsIP", server_ip); SHUTDOWN_ON_ERROR(result); diff --git a/runtime/bin/vmservice_impl.h b/runtime/bin/vmservice_impl.h index 71a3309e334..dbb24fb2235 100644 --- a/runtime/bin/vmservice_impl.h +++ b/runtime/bin/vmservice_impl.h @@ -64,6 +64,12 @@ class VmService { // argument to this parameter should be null. const char* resident_compiler_info_file_path); + // Specifies that the experimental VM service implementation should be used. + // TODO(bkonyi): remove this variable when the experimental service is + // stable. This was only added as a public static to avoid temporarily + // modifying the signature of Setup(...). + static bool enable_experimental_vm_service; + static void SetNativeResolver(); // Error message if startup failed. diff --git a/runtime/lib/vmservice.cc b/runtime/lib/vmservice.cc index cef09e90512..c6381516010 100644 --- a/runtime/lib/vmservice.cc +++ b/runtime/lib/vmservice.cc @@ -139,5 +139,4 @@ DEFINE_NATIVE_ENTRY(VMService_CancelStream, 0, 1) { #endif return Object::null(); } - } // namespace dart diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 64fe45dc422..b4305224cc1 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -54,6 +54,8 @@ declare_args() { # ........dart2bytecode.snapshot (AOT snapshot, for selected targets) # ........dart2js_aot.dart.snapshot (AOT snapshot) # ........dart2wasm_product.snapshot (AOT snapshot) +# ........dart_runtime_service_vm_aot.snapshot (AOT snapshot) +# ........dart_runtime_service_vm.snapshot (JIT snapshot) # ........dartdev_aot.dart.snapshot (AOT snapshot) # ........dartdevc_aot.dart.snapshot (AOT snapshot) # ........dds_aot.dart.snapshot (AOT snapshot) @@ -153,6 +155,13 @@ if (dart_target_arch != "ia32" && dart_target_arch != "x86") { "dart_mcp_server_aot", ], ] + if (include_experimental_vm_service) { + _platform_sdk_snapshots += [ [ + "dart_runtime_service_vm_aot", + "../utils/dart_runtime_service_vm:dart_runtime_service_vm_aot", + "dart_runtime_service_vm_aot", + ] ] + } } else { _platform_sdk_snapshots += [ [ "dds", @@ -160,12 +169,20 @@ if (dart_target_arch != "ia32" && dart_target_arch != "x86") { "dds", ] ] } + if (dart_snapshot_kind == "app-jit") { _platform_sdk_snapshots += [ [ "kernel-service", "../utils/kernel-service:kernel-service_snapshot", "kernel-service", ] ] + if (include_experimental_vm_service) { + _platform_sdk_snapshots += [ [ + "dart_runtime_service_vm", + "../utils/dart_runtime_service_vm:dart_runtime_service_vm", + "dart_runtime_service_vm", + ] ] + } } # dart2bytecode is an AOT snapshot, which is not supported on the ia32 @@ -398,9 +415,7 @@ if (!using_sanitizer && current_os == "linux" && "tsan", ] if (is_clang) { - sanitizers += [ - "msan", - ] + sanitizers += [ "msan" ] } } else if (current_cpu == "riscv64") { # Fuchsia Clang is missing the riscv64 MSAN runtime. @@ -536,7 +551,8 @@ foreach(snapshot, _full_sdk_snapshots) { # The dartdev, dds and dtd snapshots are output to root_out_dir in order to # be compatible with the way the dart sdk is distributed internally. if (snapshot[0] == "dartdev_aot" || snapshot[0] == "dds_aot_product" || - snapshot[0] == "dart_tooling_daemon_aot_product") { + snapshot[0] == "dart_tooling_daemon_aot_product" || + snapshot[0] == "dart_runtime_service_vm_aot") { root = root_out_dir } copy("copy_${snapshot[0]}_snapshot") { diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart index bbdc8636abd..2aa66d8f9b6 100644 --- a/sdk/lib/vmservice/vmservice.dart +++ b/sdk/lib/vmservice/vmservice.dart @@ -290,7 +290,7 @@ class VMService extends MessageRouter { clients.remove(client); for (final streamId in client.streams) { if (!_isAnyClientSubscribed(streamId)) { - _vmCancelStream(streamId); + vmCancelStream(streamId); } } for (final pair in client.createdServiceIdZones) { @@ -444,7 +444,7 @@ class VMService extends MessageRouter { await VMServiceEmbedderHooks.cleanup!(); await clearState(); // Notify the VM that we have exited. - _onExit(); + onExit(); } void messageHandler(message) { @@ -497,7 +497,7 @@ class VMService extends MessageRouter { if (instance == null) { instance = VMService._internal(); VMService._instance = instance; - _onStart(); + onStart(); } return instance; } @@ -533,7 +533,7 @@ class VMService extends MessageRouter { if (!_isAnyClientSubscribed(streamId)) { final includePrivates = message.params['_includePrivateMembers'] == true; if (!serviceStreams.contains(streamId) && - !_vmListenStream(streamId, includePrivates)) { + !vmListenStream(streamId, includePrivates)) { return encodeRpcError( message, kInvalidParams, @@ -569,7 +569,7 @@ class VMService extends MessageRouter { client.streams.remove(streamId); if (!serviceStreams.contains(streamId) && !_isAnyClientSubscribed(streamId)) { - _vmCancelStream(streamId); + vmCancelStream(streamId); } return encodeSuccess(message); @@ -807,24 +807,19 @@ void _registerIsolate(int port_id, SendPort sp, String name) => /// Notify the VM that the service is running. @pragma("vm:external-name", "VMService_OnStart") -external void _onStart(); +external void onStart(); /// Notify the VM that the service is no longer running. @pragma("vm:external-name", "VMService_OnExit") -external void _onExit(); - -/// Notify the VM that the server's address has changed. -void onServerAddressChange(String? address) { - _onServerAddressChange(address); -} +external void onExit(); @pragma("vm:external-name", "VMService_OnServerAddressChange") -external void _onServerAddressChange(String? address); +external void onServerAddressChange(String? address); /// Subscribe to a service stream. @pragma("vm:external-name", "VMService_ListenStream") -external bool _vmListenStream(String streamId, bool include_privates); +external bool vmListenStream(String streamId, bool include_privates); /// Cancel a subscription to a service stream. @pragma("vm:external-name", "VMService_CancelStream") -external void _vmCancelStream(String streamId); +external void vmCancelStream(String streamId); diff --git a/sdk_args.gni b/sdk_args.gni index 542d7b88521..4013efd3beb 100644 --- a/sdk_args.gni +++ b/sdk_args.gni @@ -38,6 +38,12 @@ declare_args() { # can significantly improve iteration time when iteration on changes in # core libraries. precompile_tools = false + + # When set to `true`, the Dart Runtime Service based implementation of the + # VM service will be included in the build. By default, the legacy VM + # service implementation will be launched by the VM, unless the + # --include-experimental-vm-service VM flag is provided. + include_experimental_vm_service = false } if (default_git_folder == "") { diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json index a8406fb8e98..7a7c829e037 100644 --- a/tools/bots/test_matrix.json +++ b/tools/bots/test_matrix.json @@ -1045,6 +1045,7 @@ "script": "tools/build.py", "arguments": [ "--codesigning-identity=-", + "--include-experimental-vm-service", "runtime" ] }, @@ -1075,6 +1076,7 @@ "script": "tools/build.py", "arguments": [ "--codesigning-identity=-", + "--include-experimental-vm-service", "runtime" ] }, @@ -1155,6 +1157,7 @@ "script": "tools/build.py", "arguments": [ "--codesigning-identity=-", + "--include-experimental-vm-service", "runtime", "dartaotruntime" ] @@ -3611,4 +3614,4 @@ "macos": "buildtools/mac-x64/clang/bin/llvm-symbolizer", "windows": "buildtools/win-x64/clang/bin/llvm-symbolizer.exe" } -} +} \ No newline at end of file diff --git a/tools/gn.py b/tools/gn.py index 90e400195ee..a01d121fdb8 100755 --- a/tools/gn.py +++ b/tools/gn.py @@ -280,6 +280,10 @@ def ToGnArgs(args, mode, arch, target_os, sanitizer, verify_sdk_hash, if not args.platform_sdk: gn_args['dart_platform_sdk'] = args.platform_sdk + if args.include_experimental_vm_service: + gn_args[ + 'include_experimental_vm_service'] = args.include_experimental_vm_service + # We don't support stripping on Windows if host_os != 'win': gn_args['dart_stripped_binary'] = 'exe.stripped/dart' @@ -555,6 +559,11 @@ def AddCommonGnOptionArgs(parser): help='Sign executables using the given identity.', default='', type=str) + parser.add_argument( + '--include-experimental-vm-service', + help='Use the Dart Runtime Service based VM service implementation.', + default=False, + action='store_true') def AddCommonConfigurationArgs(parser): diff --git a/utils/dart_runtime_service_vm/BUILD.gn b/utils/dart_runtime_service_vm/BUILD.gn new file mode 100644 index 00000000000..9f256fa365b --- /dev/null +++ b/utils/dart_runtime_service_vm/BUILD.gn @@ -0,0 +1,33 @@ +# Copyright (c) 2026, 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("../../runtime/runtime_args.gni") +import("../aot_snapshot.gni") +import("../application_snapshot.gni") + +group("dart_runtime_service_vm_aot") { + public_deps = [ ":dart_runtime_service_vm_aot_snapshot" ] +} + +aot_snapshot("dart_runtime_service_vm_aot_snapshot") { + main_dart = "../../pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart" + output = "$root_out_dir/dart_runtime_service_vm_aot.dart.snapshot" +} + +group("dart_runtime_service_vm") { + public_deps = [ ":copy_dart_runtime_service_vm_snapshot" ] +} + +copy("copy_dart_runtime_service_vm_snapshot") { + visibility = [ ":dart_runtime_service_vm" ] + public_deps = [ ":generate_dart_runtime_service_vm_snapshot" ] + sources = [ "$root_gen_dir/dart_runtime_service_vm.dart.snapshot" ] + outputs = [ "$root_out_dir/dart_runtime_service_vm.dart.snapshot" ] +} + +application_snapshot("generate_dart_runtime_service_vm_snapshot") { + main_dart = "../../pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart" + training_args = [ "--help" ] + output = "$root_gen_dir/dart_runtime_service_vm.dart.snapshot" +}