From 16037e33ecadfdd5edc7857f372fab4b0ba4edd9 Mon Sep 17 00:00:00 2001 From: Carlo Bernaschina Date: Mon, 17 Jul 2017 15:13:24 -0700 Subject: [PATCH] Register tools as a reloadSources service (#11258) In https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217 a new functionality of the Dart VM Service Protocol has been introduced. Clients connected to the Service Protocol are now able to expose services that other clients (e.g. Observatory) can invoke through the Service Protocol itself. With these changes Flutter Tools register them self as a `reloadSources` (a.k.a. HotReload) capable client. Observatory is already listening for the clients which expose this functionality and uses by default the service based version of `reloadSources` when available, so requesting a HotReload from Observatory will trigger the full Flutter HotReload. Related https://github.com/dart-lang/sdk/issues/30023 Related https://github.com/flutter/flutter/pull/11229 Related https://github.com/flutter/flutter/pull/11256 --- .../lib/src/resident_runner.dart | 19 +++-- packages/flutter_tools/lib/src/run_hot.dart | 17 ++++- packages/flutter_tools/lib/src/vmservice.dart | 69 ++++++++++++++++++- 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index b4c4ba49d37..91de539be17 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -44,12 +44,20 @@ class FlutterDevice { _viewsCache = null; } - void connect() { + /// If the [reloadSources] parameter is not null the 'reloadSources' service + /// will be registered. + /// The 'reloadSources' service can be used by other Service Protocol clients + /// connected to the VM (e.g. Observatory) to request a reload of the source + /// code of the running application (a.k.a. HotReload). + /// This ensures that the reload process follows the normal orchestration of + /// the Flutter Tools and not just the VM internal service. + void connect({ReloadSources reloadSources}) { if (vmServices != null) return; vmServices = new List(observatoryUris.length); for (int i = 0; i < observatoryUris.length; i++) { - vmServices[i] = VMService.connect(observatoryUris[i]); + vmServices[i] = VMService.connect(observatoryUris[i], + reloadSources: reloadSources); printTrace('Connected to service protocol: ${observatoryUris[i]}'); } } @@ -526,14 +534,17 @@ abstract class ResidentRunner { device.stopEchoingDeviceLog(); } - Future connectToServiceProtocol({String viewFilter}) async { + /// If the [reloadSources] parameter is not null the 'reloadSources' service + /// will be registered + Future connectToServiceProtocol({String viewFilter, + ReloadSources reloadSources}) async { if (!debuggingOptions.debuggingEnabled) return new Future.error('Error the service protocol is not enabled.'); bool viewFound = false; for (FlutterDevice device in flutterDevices) { device.viewFilter = viewFilter; - device.connect(); + device.connect(reloadSources: reloadSources); await device.getVMs(); await device.waitForViews(); if (device.views == null) diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index e7eeb335ecd..51300a601f6 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:json_rpc_2/error_code.dart' as rpc_error_code; +import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'base/context.dart'; import 'base/file_system.dart'; @@ -83,13 +85,26 @@ class HotRunner extends ResidentRunner { return true; } + Future _reloadSourcesService(String isolateId, + { bool force: false, bool pause: false }) async { + // TODO(cbernaschina): check that isolateId is the id of the UI isolate. + final OperationResult result = await restart(pauseAfterRestart: pause); + if (result != OperationResult.ok) { + throw new rpc.RpcException( + rpc_error_code.INTERNAL_ERROR, + 'Unable to reload sources', + ); + } + } + Future attach({ Completer connectionInfoCompleter, Completer appStartedCompleter, String viewFilter, }) async { try { - await connectToServiceProtocol(viewFilter: viewFilter); + await connectToServiceProtocol(viewFilter: viewFilter, + reloadSources: _reloadSourcesService); } catch (error) { printError('Error connecting to the service protocol: $error'); return 2; diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index fa3899bf8ac..2b5f41bb6e1 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -24,6 +24,23 @@ typedef StreamChannel _OpenChannel(Uri uri); _OpenChannel _openChannel = _defaultOpenChannel; +/// A function that reacts to the invocation of the 'reloadSources' service. +/// +/// The VM Service Protocol allows clients to register custom services that +/// can be invoked by other clients through the service protocol itself. +/// +/// Clients like Observatory use external 'reloadSources' services, +/// when available, instead of the VM internal one. This allows these clients to +/// invoke Flutter HotReload when connected to a Flutter Application started in +/// hot mode. +/// +/// See: https://github.com/dart-lang/sdk/issues/30023 +typedef Future ReloadSources( + String isolateId, { + bool force, + bool pause, +}); + const String _kRecordingType = 'vmservice'; StreamChannel _defaultOpenChannel(Uri uri) => @@ -40,13 +57,51 @@ const Duration kShortRequestTimeout = const Duration(seconds: 5); /// A connection to the Dart VM Service. class VMService { - VMService._(this._peer, this.httpAddress, this.wsAddress, this._requestTimeout) { + VMService._( + this._peer, + this.httpAddress, + this.wsAddress, + this._requestTimeout, + ReloadSources reloadSources, + ) { _vm = new VM._empty(this); _peer.listen().catchError(_connectionError.completeError); _peer.registerMethod('streamNotify', (rpc.Parameters event) { _handleStreamNotify(event.asMap); }); + + if (reloadSources != null) { + _peer.registerMethod('reloadSources', (rpc.Parameters params) async { + final String isolateId = params['isolateId'].value; + final bool force = params.asMap['force'] ?? false; + final bool pause = params.asMap['pause'] ?? false; + + if (isolateId is! String || isolateId.isEmpty) + throw new rpc.RpcException.invalidParams('Invalid \'isolateId\': $isolateId'); + if (force is! bool) + throw new rpc.RpcException.invalidParams('Invalid \'force\': $force'); + if (pause is! bool) + throw new rpc.RpcException.invalidParams('Invalid \'pause\': $pause'); + + try { + await reloadSources(isolateId, force: force, pause: pause); + return {'type': 'Success'}; + } on rpc.RpcException { + rethrow; + } catch (e, st) { + throw new rpc.RpcException(rpc_error_code.SERVER_ERROR, + 'Error during Sources Reload: $e\n$st'); + } + }); + + // If the Flutter Engine doesn't support service registration this will + // have no effect + _peer.sendNotification('_registerService', { + 'service': 'reloadSources', + 'alias': 'Flutter Tools' + }); + } } /// Enables recording of VMService JSON-rpc activity to the specified base @@ -76,16 +131,24 @@ class VMService { /// Connect to a Dart VM Service at [httpUri]. /// - /// Requests made via the returns [VMService] time out after [requestTimeout] + /// Requests made via the returned [VMService] time out after [requestTimeout] /// amount of time, which is [kDefaultRequestTimeout] by default. + /// + /// If the [reloadSources] parameter is not null, the 'reloadSources' service + /// will be registered. The VM Service Protocol allows clients to register + /// custom services that can be invoked by other clients through the service + /// protocol itself. + /// + /// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217 static VMService connect( Uri httpUri, { Duration requestTimeout: kDefaultRequestTimeout, + ReloadSources reloadSources, }) { final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws')); final StreamChannel channel = _openChannel(wsUri); final rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(channel)); - return new VMService._(peer, httpUri, wsUri, requestTimeout); + return new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources); } final Uri httpAddress;