Commit 16037e33 authored by Carlo Bernaschina's avatar Carlo Bernaschina Committed by GitHub

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
parent 1650420d
......@@ -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<VMService>(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<Null> connectToServiceProtocol({String viewFilter}) async {
/// If the [reloadSources] parameter is not null the 'reloadSources' service
/// will be registered
Future<Null> connectToServiceProtocol({String viewFilter,
ReloadSources reloadSources}) async {
if (!debuggingOptions.debuggingEnabled)
return new Future<Null>.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)
......
......@@ -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<Null> _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<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<Null> 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;
......
......@@ -24,6 +24,23 @@ typedef StreamChannel<String> _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<Null> ReloadSources(
String isolateId, {
bool force,
bool pause,
});
const String _kRecordingType = 'vmservice';
StreamChannel<String> _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 <String, String>{'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', <String, String>{
'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<String> 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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment