// Copyright 2014 The Flutter Authors. 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:file/file.dart'; import 'package:meta/meta.dart' show required, visibleForTesting; import 'package:vm_service/vm_service.dart' as vm_service; import 'base/context.dart'; import 'base/io.dart' as io; import 'build_info.dart'; import 'convert.dart'; import 'device.dart'; import 'globals.dart' as globals; import 'version.dart'; const String kGetSkSLsMethod = '_flutter.getSkSLs'; const String kSetAssetBundlePathMethod = '_flutter.setAssetBundlePath'; const String kFlushUIThreadTasksMethod = '_flutter.flushUIThreadTasks'; const String kRunInViewMethod = '_flutter.runInView'; const String kListViewsMethod = '_flutter.listViews'; const String kScreenshotSkpMethod = '_flutter.screenshotSkp'; const String kScreenshotMethod = '_flutter.screenshot'; /// The error response code from an unrecoverable compilation failure. const int kIsolateReloadBarred = 1005; /// Override `WebSocketConnector` in [context] to use a different constructor /// for [WebSocket]s (used by tests). typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression}); typedef PrintStructuredErrorLogMethod = void Function(vm_service.Event); WebSocketConnector _openChannel = _defaultOpenChannel; /// The error codes for the JSON-RPC standard. /// /// See also: https://www.jsonrpc.org/specification#error_object abstract class RPCErrorCodes { /// The method does not exist or is not available. static const int kMethodNotFound = -32601; /// Invalid method parameter(s), such as a mismatched type. static const int kInvalidParams = -32602; /// Internal JSON-RPC error. static const int kInternalError = -32603; /// Application specific error codes. static const int kServerError = -32000; } /// 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 ReloadSources = Future<void> Function( String isolateId, { bool force, bool pause, }); typedef Restart = Future<void> Function({ bool pause }); typedef CompileExpression = Future<String> Function( String isolateId, String expression, List<String> definitions, List<String> typeDefinitions, String libraryUri, String klass, bool isStatic, ); typedef ReloadMethod = Future<void> Function({ String classId, String libraryId, }); /// A method that pulls an SkSL shader from the device and writes it to a file. /// /// The name of the file returned as a result. typedef GetSkSLMethod = Future<String> Function(); Future<io.WebSocket> _defaultOpenChannel(String url, { io.CompressionOptions compression = io.CompressionOptions.compressionDefault }) async { Duration delay = const Duration(milliseconds: 100); int attempts = 0; io.WebSocket socket; Future<void> handleError(dynamic e) async { void Function(String) printVisibleTrace = globals.printTrace; if (attempts == 10) { globals.printStatus('Connecting to the VM Service is taking longer than expected...'); } else if (attempts == 20) { globals.printStatus('Still attempting to connect to the VM Service...'); globals.printStatus( 'If you do NOT see the Flutter application running, it might have ' 'crashed. The device logs (e.g. from adb or XCode) might have more ' 'details.'); globals.printStatus( 'If you do see the Flutter application running on the device, try ' 're-running with --host-vmservice-port to use a specific port known to ' 'be available.'); } else if (attempts % 50 == 0) { printVisibleTrace = globals.printStatus; } printVisibleTrace('Exception attempting to connect to the VM Service: $e'); printVisibleTrace('This was attempt #$attempts. Will retry in $delay.'); // Delay next attempt. await Future<void>.delayed(delay); // Back off exponentially, up to 1600ms per attempt. if (delay < const Duration(seconds: 1)) { delay *= 2; } } final WebSocketConnector constructor = context.get<WebSocketConnector>() ?? io.WebSocket.connect; while (socket == null) { attempts += 1; try { socket = await constructor(url, compression: compression); } on io.WebSocketException catch (e) { await handleError(e); } on io.SocketException catch (e) { await handleError(e); } } return socket; } /// Override `VMServiceConnector` in [context] to return a different VMService /// from [VMService.connect] (used by tests). typedef VMServiceConnector = Future<vm_service.VmService> Function(Uri httpUri, { ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, ReloadMethod reloadMethod, GetSkSLMethod getSkSLMethod, PrintStructuredErrorLogMethod printStructuredErrorLogMethod, io.CompressionOptions compression, Device device, }); final Expando<Uri> _httpAddressExpando = Expando<Uri>(); final Expando<Uri> _wsAddressExpando = Expando<Uri>(); @visibleForTesting void setHttpAddress(Uri uri, vm_service.VmService vmService) { _httpAddressExpando[vmService] = uri; } @visibleForTesting void setWsAddress(Uri uri, vm_service.VmService vmService) { _wsAddressExpando[vmService] = uri; } /// A connection to the Dart VM Service. vm_service.VmService setUpVmService( ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, Device device, ReloadMethod reloadMethod, GetSkSLMethod skSLMethod, PrintStructuredErrorLogMethod printStructuredErrorLogMethod, vm_service.VmService vmService ) { if (reloadSources != null) { vmService.registerServiceCallback('reloadSources', (Map<String, dynamic> params) async { final String isolateId = _validateRpcStringParam('reloadSources', params, 'isolateId'); final bool force = _validateRpcBoolParam('reloadSources', params, 'force'); final bool pause = _validateRpcBoolParam('reloadSources', params, 'pause'); await reloadSources(isolateId, force: force, pause: pause); return <String, dynamic>{ 'result': <String, Object>{ 'type': 'Success', } }; }); vmService.registerService('reloadSources', 'Flutter Tools'); } if (reloadMethod != null) { // Register a special method for hot UI. while this is implemented // currently in the same way as hot reload, it leaves the tool free // to change to a more efficient implementation in the future. // // `library` should be the file URI of the updated code. // `class` should be the name of the Widget subclass to be marked dirty. For example, // if the build method of a StatelessWidget is updated, this is the name of class. // If the build method of a StatefulWidget is updated, then this is the name // of the Widget class that created the State object. vmService.registerServiceCallback('reloadMethod', (Map<String, dynamic> params) async { final String libraryId = _validateRpcStringParam('reloadMethod', params, 'library'); final String classId = _validateRpcStringParam('reloadMethod', params, 'class'); globals.printTrace('reloadMethod not yet supported, falling back to hot reload'); await reloadMethod(libraryId: libraryId, classId: classId); return <String, dynamic>{ 'result': <String, Object>{ 'type': 'Success', } }; }); vmService.registerService('reloadMethod', 'Flutter Tools'); } if (restart != null) { vmService.registerServiceCallback('hotRestart', (Map<String, dynamic> params) async { final bool pause = _validateRpcBoolParam('compileExpression', params, 'pause'); await restart(pause: pause); return <String, dynamic>{ 'result': <String, Object>{ 'type': 'Success', } }; }); vmService.registerService('hotRestart', 'Flutter Tools'); } vmService.registerServiceCallback('flutterVersion', (Map<String, dynamic> params) async { final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion(); final Map<String, Object> versionJson = version.toJson(); versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort; versionJson['engineRevisionShort'] = version.engineRevisionShort; return <String, dynamic>{ 'result': <String, Object>{ 'type': 'Success', ...versionJson, } }; }); vmService.registerService('flutterVersion', 'Flutter Tools'); if (compileExpression != null) { vmService.registerServiceCallback('compileExpression', (Map<String, dynamic> params) async { final String isolateId = _validateRpcStringParam('compileExpression', params, 'isolateId'); final String expression = _validateRpcStringParam('compileExpression', params, 'expression'); final List<String> definitions = List<String>.from(params['definitions'] as List<dynamic>); final List<String> typeDefinitions = List<String>.from(params['typeDefinitions'] as List<dynamic>); final String libraryUri = params['libraryUri'] as String; final String klass = params['klass'] as String; final bool isStatic = _validateRpcBoolParam('compileExpression', params, 'isStatic'); final String kernelBytesBase64 = await compileExpression(isolateId, expression, definitions, typeDefinitions, libraryUri, klass, isStatic); return <String, dynamic>{ 'type': 'Success', 'result': <String, dynamic>{'kernelBytes': kernelBytesBase64}, }; }); vmService.registerService('compileExpression', 'Flutter Tools'); } if (device != null) { vmService.registerServiceCallback('flutterMemoryInfo', (Map<String, dynamic> params) async { final MemoryInfo result = await device.queryMemoryInfo(); return <String, dynamic>{ 'result': <String, Object>{ 'type': 'Success', ...result.toJson(), } }; }); vmService.registerService('flutterMemoryInfo', 'Flutter Tools'); } if (skSLMethod != null) { vmService.registerServiceCallback('flutterGetSkSL', (Map<String, dynamic> params) async { final String filename = await skSLMethod(); return <String, dynamic>{ 'result': <String, Object>{ 'type': 'Success', 'filename': filename, } }; }); vmService.registerService('flutterGetSkSL', 'Flutter Tools'); } if (printStructuredErrorLogMethod != null) { try { vmService.streamListen(vm_service.EventStreams.kExtension); } on vm_service.RPCError { // It is safe to ignore this error because we expect an error to be // thrown if we're already subscribed. } vmService.onExtensionEvent.listen(printStructuredErrorLogMethod); } return vmService; } /// Connect to a Dart VM Service at [httpUri]. /// /// 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 Future<vm_service.VmService> connectToVmService( Uri httpUri, { ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, ReloadMethod reloadMethod, GetSkSLMethod getSkSLMethod, PrintStructuredErrorLogMethod printStructuredErrorLogMethod, io.CompressionOptions compression = io.CompressionOptions.compressionDefault, Device device, }) async { final VMServiceConnector connector = context.get<VMServiceConnector>() ?? _connect; return connector(httpUri, reloadSources: reloadSources, restart: restart, compileExpression: compileExpression, compression: compression, device: device, reloadMethod: reloadMethod, getSkSLMethod: getSkSLMethod, printStructuredErrorLogMethod: printStructuredErrorLogMethod, ); } Future<vm_service.VmService> _connect( Uri httpUri, { ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, ReloadMethod reloadMethod, GetSkSLMethod getSkSLMethod, PrintStructuredErrorLogMethod printStructuredErrorLogMethod, io.CompressionOptions compression = io.CompressionOptions.compressionDefault, Device device, }) async { final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws')); final io.WebSocket channel = await _openChannel(wsUri.toString(), compression: compression); final vm_service.VmService delegateService = vm_service.VmService( channel, channel.add, log: null, disposeHandler: () async { await channel.close(); }, ); final vm_service.VmService service = setUpVmService( reloadSources, restart, compileExpression, device, reloadMethod, getSkSLMethod, printStructuredErrorLogMethod, delegateService, ); _httpAddressExpando[service] = httpUri; _wsAddressExpando[service] = wsUri; // This call is to ensure we are able to establish a connection instead of // keeping on trucking and failing farther down the process. await delegateService.getVersion(); return service; } String _validateRpcStringParam(String methodName, Map<String, dynamic> params, String paramName) { final dynamic value = params[paramName]; if (value is! String || (value as String).isEmpty) { throw vm_service.RPCError( methodName, RPCErrorCodes.kInvalidParams, "Invalid '$paramName': $value", ); } return value as String; } bool _validateRpcBoolParam(String methodName, Map<String, dynamic> params, String paramName) { final dynamic value = params[paramName]; if (value != null && value is! bool) { throw vm_service.RPCError( methodName, RPCErrorCodes.kInvalidParams, "Invalid '$paramName': $value", ); } return (value as bool) ?? false; } /// Peered to an Android/iOS FlutterView widget on a device. class FlutterView { FlutterView({ @required this.id, @required this.uiIsolate, }); factory FlutterView.parse(Map<String, Object> json) { final Map<String, Object> rawIsolate = json['isolate'] as Map<String, Object>; vm_service.IsolateRef isolate; if (rawIsolate != null) { rawIsolate['number'] = rawIsolate['number']?.toString(); isolate = vm_service.IsolateRef.parse(rawIsolate); } return FlutterView( id: json['id'] as String, uiIsolate: isolate, ); } final vm_service.IsolateRef uiIsolate; final String id; bool get hasIsolate => uiIsolate != null; @override String toString() => id; Map<String, Object> toJson() { return <String, Object>{ 'id': id, 'isolate': uiIsolate?.toJson(), }; } } /// Flutter specific VM Service functionality. extension FlutterVmService on vm_service.VmService { Uri get wsAddress => this != null ? _wsAddressExpando[this] : null; Uri get httpAddress => this != null ? _httpAddressExpando[this] : null; /// Set the asset directory for the an attached Flutter view. Future<void> setAssetDirectory({ @required Uri assetsDirectory, @required String viewId, @required String uiIsolateId, }) async { assert(assetsDirectory != null); await callMethod(kSetAssetBundlePathMethod, isolateId: uiIsolateId, args: <String, dynamic>{ 'viewId': viewId, 'assetDirectory': assetsDirectory.toFilePath(windows: false), }); } /// Retreive the cached SkSL shaders from an attached Flutter view. /// /// This method will only return data if `--cache-sksl` was provided as a /// flutter run agument, and only then on physical devices. Future<Map<String, Object>> getSkSLs({ @required String viewId, }) async { final vm_service.Response response = await callMethod( kGetSkSLsMethod, args: <String, String>{ 'viewId': viewId, }, ); return response.json['SkSLs'] as Map<String, Object>; } /// Flush all tasks on the UI thead for an attached Flutter view. /// /// This method is currently used only for benchmarking. Future<void> flushUIThreadTasks({ @required String uiIsolateId, }) async { await callMethod( kFlushUIThreadTasksMethod, args: <String, String>{ 'isolateId': uiIsolateId, }, ); } /// Launch the Dart isolate with entrypoint [main] in the Flutter engine [viewId] /// with [assetsDirectory] as the devFS. /// /// This method is used by the tool to hot restart an already running Flutter /// engine. Future<void> runInView({ @required String viewId, @required Uri main, @required Uri assetsDirectory, }) async { try { await streamListen('Isolate'); } on vm_service.RPCError { // Do nothing, since the tool is already subscribed. } final Future<void> onRunnable = onIsolateEvent.firstWhere((vm_service.Event event) { return event.kind == vm_service.EventKind.kIsolateRunnable; }); await callMethod( kRunInViewMethod, args: <String, Object>{ 'viewId': viewId, 'mainScript': main.toString(), 'assetDirectory': assetsDirectory.toString(), }, ); await onRunnable; } Future<Map<String, dynamic>> flutterDebugDumpApp({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.debugDumpApp', isolateId: isolateId, ); } Future<Map<String, dynamic>> flutterDebugDumpRenderTree({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.debugDumpRenderTree', isolateId: isolateId, ); } Future<Map<String, dynamic>> flutterDebugDumpLayerTree({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.debugDumpLayerTree', isolateId: isolateId, ); } Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder', isolateId: isolateId, ); } Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', isolateId: isolateId, ); } Future<Map<String, dynamic>> _flutterToggle(String name, { @required String isolateId, }) async { Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw( 'ext.flutter.$name', isolateId: isolateId, ); if (state != null && state.containsKey('enabled') && state['enabled'] is String) { state = await invokeFlutterExtensionRpcRaw( 'ext.flutter.$name', isolateId: isolateId, args: <String, dynamic>{ 'enabled': state['enabled'] == 'true' ? 'false' : 'true', }, ); } return state; } Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled({ @required String isolateId, }) => _flutterToggle('debugPaint', isolateId: isolateId); Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled({ @required String isolateId, }) => _flutterToggle('debugCheckElevationsEnabled', isolateId: isolateId); Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride({ @required String isolateId, }) => _flutterToggle('showPerformanceOverlay', isolateId: isolateId); Future<Map<String, dynamic>> flutterToggleWidgetInspector({ @required String isolateId, }) => _flutterToggle('inspector.show', isolateId: isolateId); Future<Map<String,dynamic>> flutterToggleInvertOversizedImages({ @required String isolateId, }) => _flutterToggle('invertOversizedImages', isolateId: isolateId); Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds({ @required String isolateId, }) => _flutterToggle('profileWidgetBuilds', isolateId: isolateId); Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show, { @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.debugAllowBanner', isolateId: isolateId, args: <String, dynamic>{'enabled': show ? 'true' : 'false'}, ); } Future<Map<String, dynamic>> flutterReassemble({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.reassemble', isolateId: isolateId, ); } Future<Map<String, dynamic>> flutterFastReassemble({ @required String isolateId, @required String className, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.fastReassemble', isolateId: isolateId, args: <String, Object>{ 'className': className, }, ); } Future<bool> flutterAlreadyPaintedFirstUsefulFrame({ @required String isolateId, }) async { final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw( 'ext.flutter.didSendFirstFrameRasterizedEvent', isolateId: isolateId, ); // result might be null when the service extension is not initialized return result != null && result['enabled'] == 'true'; } Future<Map<String, dynamic>> uiWindowScheduleFrame({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.ui.window.scheduleFrame', isolateId: isolateId, ); } Future<Map<String, dynamic>> flutterEvictAsset(String assetPath, { @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.evict', isolateId: isolateId, args: <String, dynamic>{ 'value': assetPath, }, ); } /// Exit the application by calling [exit] from `dart:io`. /// /// This method is only supported by certain embedders. This is /// described by [Device.supportsFlutterExit]. Future<void> flutterExit({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.exit', isolateId: isolateId, ).catchError((dynamic error, StackTrace stackTrace) { globals.logger.printTrace('Failure in ext.flutter.exit: $error\n$stackTrace'); // Do nothing on sentinel or exception, the isolate already exited. }, test: (dynamic error) => error is vm_service.SentinelException || error is vm_service.RPCError); } /// Return the current platform override for the flutter view running with /// the main isolate [isolateId]. /// /// If a non-null value is provided for [platform], the platform override /// is updated with this value. Future<String> flutterPlatformOverride({ String platform, @required String isolateId, }) async { final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw( 'ext.flutter.platformOverride', isolateId: isolateId, args: platform != null ? <String, dynamic>{'value': platform} : <String, String>{}, ); if (result != null && result['value'] is String) { return result['value'] as String; } return 'unknown'; } /// Return the current brightness value for the flutter view running with /// the main isolate [isolateId]. /// /// If a non-null value is provided for [brightness], the brightness override /// is updated with this value. Future<Brightness> flutterBrightnessOverride({ Brightness brightness, @required String isolateId, }) async { final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw( 'ext.flutter.brightnessOverride', isolateId: isolateId, args: brightness != null ? <String, dynamic>{'value': brightness.toString()} : <String, String>{}, ); if (result != null && result['value'] is String) { return (result['value'] as String) == 'Brightness.light' ? Brightness.light : Brightness.dark; } return null; } /// Invoke a flutter extension method, if the flutter extension is not /// available, returns null. Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw( String method, { @required String isolateId, Map<String, dynamic> args, }) async { try { final vm_service.Response response = await callServiceExtension( method, args: <String, Object>{ 'isolateId': isolateId, ...?args, }, ); return response.json; } on vm_service.RPCError catch (err) { // If an application is not using the framework if (err.code == RPCErrorCodes.kMethodNotFound) { return null; } rethrow; } } /// List all [FlutterView]s attached to the current VM. /// /// If this returns an empty list, it will poll forever unless [returnEarly] /// is set to true. /// /// By default, the poll duration is 50 milliseconds. Future<List<FlutterView>> getFlutterViews({ bool returnEarly = false, Duration delay = const Duration(milliseconds: 50), }) async { while (true) { final vm_service.Response response = await callMethod( kListViewsMethod, ); final List<Object> rawViews = response.json['views'] as List<Object>; final List<FlutterView> views = <FlutterView>[ for (final Object rawView in rawViews) FlutterView.parse(rawView as Map<String, Object>) ]; if (views.isNotEmpty || returnEarly) { return views; } await Future<void>.delayed(delay); } } /// Attempt to retrieve the isolate with id [isolateId], or `null` if it has /// been collected. Future<vm_service.Isolate> getIsolateOrNull(String isolateId) { return getIsolate(isolateId) .catchError((dynamic error, StackTrace stackTrace) { return null; }, test: (dynamic error) => error is vm_service.SentinelException); } /// Create a new development file system on the device. Future<vm_service.Response> createDevFS(String fsName) { return callServiceExtension('_createDevFS', args: <String, dynamic>{'fsName': fsName}); } /// Delete an existing file system. Future<vm_service.Response> deleteDevFS(String fsName) { return callServiceExtension('_deleteDevFS', args: <String, dynamic>{'fsName': fsName}); } Future<vm_service.Response> screenshot() { return callServiceExtension(kScreenshotMethod); } Future<vm_service.Response> screenshotSkp() { return callServiceExtension(kScreenshotSkpMethod); } /// Set the VM timeline flags. Future<vm_service.Response> setVMTimelineFlags(List<String> recordedStreams) { assert(recordedStreams != null); return callServiceExtension( 'setVMTimelineFlags', args: <String, dynamic>{ 'recordedStreams': recordedStreams, }, ); } Future<vm_service.Response> getVMTimeline() { return callServiceExtension('getVMTimeline'); } } /// Whether the event attached to an [Isolate.pauseEvent] should be considered /// a "pause" event. bool isPauseEvent(String kind) { return kind == vm_service.EventKind.kPauseStart || kind == vm_service.EventKind.kPauseExit || kind == vm_service.EventKind.kPauseBreakpoint || kind == vm_service.EventKind.kPauseInterrupted || kind == vm_service.EventKind.kPauseException || kind == vm_service.EventKind.kPausePostRequest || kind == vm_service.EventKind.kNone; } // TODO(jonahwilliams): either refactor drive to use the resident runner // or delete it. Future<String> sharedSkSlWriter(Device device, Map<String, Object> data, { File outputFile, }) async { if (data.isEmpty) { globals.logger.printStatus( 'No data was receieved. To ensure SkSL data can be generated use a ' 'physical device then:\n' ' 1. Pass "--cache-sksl" as an argument to flutter run.\n' ' 2. Interact with the application to force shaders to be compiled.\n' ); return null; } if (outputFile == null) { outputFile = globals.fsUtils.getUniqueFile( globals.fs.currentDirectory, 'flutter', 'sksl.json', ); } else if (!outputFile.parent.existsSync()) { outputFile.parent.createSync(recursive: true); } // Convert android sub-platforms to single target platform. TargetPlatform targetPlatform = await device.targetPlatform; switch (targetPlatform) { case TargetPlatform.android_arm: case TargetPlatform.android_arm64: case TargetPlatform.android_x64: case TargetPlatform.android_x86: targetPlatform = TargetPlatform.android; break; default: break; } final Map<String, Object> manifest = <String, Object>{ 'platform': getNameForTargetPlatform(targetPlatform), 'name': device.name, 'engineRevision': globals.flutterVersion.engineRevision, 'data': data, }; outputFile.writeAsStringSync(json.encode(manifest)); globals.logger.printStatus('Wrote SkSL data to ${outputFile.path}.'); return outputFile.path; } /// A brightness enum that matches the values https://github.com/flutter/engine/blob/3a96741247528133c0201ab88500c0c3c036e64e/lib/ui/window.dart#L1328 /// Describes the contrast of a theme or color palette. enum Brightness { /// The color is dark and will require a light text color to achieve readable /// contrast. /// /// For example, the color might be dark grey, requiring white text. dark, /// The color is light and will require a dark text color to achieve readable /// contrast. /// /// For example, the color might be bright white, requiring black text. light, } /// Process a VM service log event into a string message. String processVmServiceMessage(vm_service.Event event) { final String message = utf8.decode(base64.decode(event.bytes)); // Remove extra trailing newlines appended by the vm service. if (message.endsWith('\n')) { return message.substring(0, message.length - 1); } return message; }