Unverified Commit f11c8d96 authored by Alexander Aprelev's avatar Alexander Aprelev Committed by GitHub

Add compile-expression service. (#16161)

This registers compile-expression service that is used by dart vm to evaluate expressions when debugging flutter applications.
parent da7670c2
......@@ -299,6 +299,28 @@ class ResidentCompiler {
return stdoutHandler.compilerOutput.future;
}
Future<CompilerOutput> compileExpression(String expression, List<String> definitions,
List<String> typeDefinitions, String libraryUri, String klass, bool isStatic) {
stdoutHandler.reset();
// 'compile-expression' should be invoked after compiler has been started,
// program was compiled.
if (_server == null)
return null;
final String inputKey = new Uuid().generateV4();
_server.stdin.writeln('compile-expression $inputKey');
_server.stdin.writeln(expression);
definitions?.forEach(_server.stdin.writeln);
_server.stdin.writeln(inputKey);
typeDefinitions?.forEach(_server.stdin.writeln);
_server.stdin.writeln(inputKey);
_server.stdin.writeln(libraryUri ?? '');
_server.stdin.writeln(klass ?? '');
_server.stdin.writeln(isStatic ?? false);
return stdoutHandler.compilerOutput.future;
}
/// Should be invoked when results of compilation are accepted by the client.
///
......
......@@ -64,16 +64,19 @@ class FlutterDevice {
/// 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).
/// The 'compileExpression' service can be used to compile user-provided
/// expressions requested during debugging of the application.
/// This ensures that the reload process follows the normal orchestration of
/// the Flutter Tools and not just the VM internal service.
Future<Null> _connect({ReloadSources reloadSources}) async {
Future<Null> _connect({ReloadSources reloadSources, CompileExpression compileExpression}) async {
if (vmServices != null)
return;
vmServices = new List<VMService>(observatoryUris.length);
for (int i = 0; i < observatoryUris.length; i++) {
printTrace('Connecting to service protocol: ${observatoryUris[i]}');
vmServices[i] = await VMService.connect(observatoryUris[i],
reloadSources: reloadSources);
reloadSources: reloadSources,
compileExpression: compileExpression);
printTrace('Successfully connected to service protocol: ${observatoryUris[i]}');
}
}
......@@ -622,14 +625,15 @@ abstract class ResidentRunner {
/// If the [reloadSources] parameter is not null the 'reloadSources' service
/// will be registered
Future<Null> connectToServiceProtocol({String viewFilter,
ReloadSources reloadSources}) async {
ReloadSources reloadSources, CompileExpression compileExpression}) 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;
await device._connect(reloadSources: reloadSources);
await device._connect(reloadSources: reloadSources,
compileExpression: compileExpression);
await device.getVMs();
await device.waitForViews();
if (device.views == null)
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
......@@ -13,6 +14,7 @@ import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/utils.dart';
import 'build_info.dart';
import 'compile.dart';
import 'dart/dependencies.dart';
import 'device.dart';
import 'globals.dart';
......@@ -105,6 +107,23 @@ class HotRunner extends ResidentRunner {
}
}
Future<String> _compileExpressionService(String isolateId, String expression,
List<String> definitions, List<String> typeDefinitions,
String libraryUri, String klass, bool isStatic,
) async {
for (FlutterDevice device in flutterDevices) {
if (device.generator != null) {
final CompilerOutput compilerOutput =
await device.generator.compileExpression(expression, definitions,
typeDefinitions, libraryUri, klass, isStatic);
if (compilerOutput.outputFilename != null) {
return base64.encode(fs.file(compilerOutput.outputFilename).readAsBytesSync());
}
}
}
return null;
}
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<Null> appStartedCompleter,
......@@ -112,7 +131,8 @@ class HotRunner extends ResidentRunner {
}) async {
try {
await connectToServiceProtocol(viewFilter: viewFilter,
reloadSources: _reloadSourcesService);
reloadSources: _reloadSourcesService,
compileExpression: _compileExpressionService);
} catch (error) {
printError('Error connecting to the service protocol: $error');
return 2;
......
......@@ -42,6 +42,16 @@ typedef Future<Null> ReloadSources(
bool pause,
});
typedef Future<String> CompileExpression(
String isolateId,
String expression,
List<String> definitions,
List<String> typeDefinitions,
String libraryUri,
String klass,
bool isStatic,
);
const String _kRecordingType = 'vmservice';
Future<StreamChannel<String>> _defaultOpenChannel(Uri uri) async {
......@@ -99,6 +109,7 @@ class VMService {
this.wsAddress,
this._requestTimeout,
ReloadSources reloadSources,
CompileExpression compileExpression,
) {
_vm = new VM._empty(this);
_peer.listen().catchError(_connectionError.completeError);
......@@ -138,6 +149,42 @@ class VMService {
'alias': 'Flutter Tools'
});
}
if (compileExpression != null) {
_peer.registerMethod('compileExpression', (rpc.Parameters params) async {
final String isolateId = params['isolateId'].asString;
if (isolateId is! String || isolateId.isEmpty)
throw new rpc.RpcException.invalidParams(
'Invalid \'isolateId\': $isolateId');
final String expression = params['expression'].asString;
if (expression is! String || expression.isEmpty)
throw new rpc.RpcException.invalidParams(
'Invalid \'expression\': $expression');
final List<String> definitions = params['definitions'].asList;
final List<String> typeDefinitions = params['typeDefinitions'].asList;
final String libraryUri = params['libraryUri'].asString;
final String klass = params['klass'] != null ? params['klass'].asString : null;
final bool isStatic = params['isStatic'].asBoolOr(false);
try {
final String kernelBytesBase64 = await compileExpression(isolateId,
expression, definitions, typeDefinitions, libraryUri, klass,
isStatic);
return <String, dynamic>{'type': 'Success',
'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}};
} on rpc.RpcException {
rethrow;
} catch (e, st) {
throw new rpc.RpcException(rpc_error_code.SERVER_ERROR,
'Error during expression compilation: $e\n$st');
}
});
_peer.sendNotification('_registerService', <String, String>{
'service': 'compileExpression',
'alias': 'Flutter Tools'
});
}
}
/// Enables recording of VMService JSON-rpc activity to the specified base
......@@ -180,11 +227,12 @@ class VMService {
Uri httpUri, {
Duration requestTimeout = kDefaultRequestTimeout,
ReloadSources reloadSources,
CompileExpression compileExpression,
}) async {
final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws'));
final StreamChannel<String> channel = await _openChannel(wsUri);
final rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(channel));
final VMService service = new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources);
final VMService service = new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources, compileExpression);
// This call is to ensure we are able to establish a connection instead of
// keeping on trucking and failing farther down the process.
await service._sendRequest('getVersion', const <String, dynamic>{});
......
......@@ -216,6 +216,89 @@ void main() {
ProcessManager: () => mockProcessManager,
});
});
group('compile expression', ()
{
ProcessManager mockProcessManager;
ResidentCompiler generator;
MockProcess mockFrontendServer;
MockStdIn mockFrontendServerStdIn;
MockStream mockFrontendServerStdErr;
StreamController<String> stdErrStreamController;
setUp(() {
generator = new ResidentCompiler('sdkroot');
mockProcessManager = new MockProcessManager();
mockFrontendServer = new MockProcess();
mockFrontendServerStdIn = new MockStdIn();
mockFrontendServerStdErr = new MockStream();
when(mockFrontendServer.stdin).thenReturn(mockFrontendServerStdIn);
when(mockFrontendServer.stderr)
.thenAnswer((Invocation invocation) => mockFrontendServerStdErr);
stdErrStreamController = new StreamController<String>();
when(mockFrontendServerStdErr.transform<String>(any))
.thenAnswer((Invocation invocation) => stdErrStreamController.stream);
when(mockProcessManager.canRun(any)).thenReturn(true);
when(mockProcessManager.start(any)).thenAnswer(
(Invocation invocation) =>
new Future<Process>.value(mockFrontendServer)
);
});
tearDown(() {
verifyNever(mockFrontendServer.exitCode);
});
testUsingContext('fails if not previously compiled', () async {
final CompilerOutput result = await generator.compileExpression(
'2+2', null, null, null, null, false);
expect(result, isNull);
});
testUsingContext('compile single expression', () async {
final BufferLogger logger = context[Logger];
final Completer<List<int>> compileResponseCompleter =
new Completer<List<int>>();
final Completer<List<int>> compileExpressionResponseCompleter =
new Completer<List<int>>();
when(mockFrontendServer.stdout)
.thenAnswer((Invocation invocation) =>
new Stream<List<int>>.fromFutures(
<Future<List<int>>>[
compileResponseCompleter.future,
compileExpressionResponseCompleter.future]));
compileResponseCompleter.complete(new Future<List<int>>.value(utf8.encode(
'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0\n'
)));
final CompilerOutput output = await generator.recompile(
'/path/to/main.dart', null /* invalidatedFiles */
);
expect(mockFrontendServerStdIn.getAndClear(),
'compile /path/to/main.dart\n');
verifyNoMoreInteractions(mockFrontendServerStdIn);
expect(logger.errorText,
equals('compiler message: line1\ncompiler message: line2\n'));
expect(output.outputFilename, equals('/path/to/main.dart.dill'));
compileExpressionResponseCompleter.complete(new Future<List<int>>.value(utf8.encode(
'result def\nline1\nline2\ndef /path/to/main.dart.dill.incremental 0\n'
)));
final CompilerOutput outputExpression = await generator.compileExpression(
'2+2', null, null, null, null, false);
expect(outputExpression, isNotNull);
expect(outputExpression.outputFilename, equals('/path/to/main.dart.dill.incremental'));
expect(outputExpression.errorCount, 0);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
});
}
Future<Null> _recompile(StreamController<List<int>> streamController,
......
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