Add a new Device backed by flutter_tester. (#16405)
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index dda9105..6bc63d2 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -17,6 +17,7 @@ import 'globals.dart'; import 'ios/plist_utils.dart' as plist; import 'ios/xcodeproj.dart'; +import 'tester/flutter_tester.dart'; abstract class ApplicationPackage { /// Package ID from the Android Manifest or equivalent. @@ -274,6 +275,8 @@ return applicationBinary == null ? new IOSApp.fromCurrentDirectory() : new IOSApp.fromIpa(applicationBinary); + case TargetPlatform.tester: + return new FlutterTesterApp.fromCurrentDirectory(); case TargetPlatform.darwin_x64: case TargetPlatform.linux_x64: case TargetPlatform.windows_x64: @@ -305,6 +308,7 @@ case TargetPlatform.linux_x64: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: + case TargetPlatform.tester: return null; } return null;
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index f539a5a..3757ac5 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -114,6 +114,7 @@ case TargetPlatform.linux_x64: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: + case TargetPlatform.tester: return _getHostArtifactPath(artifact, platform); } assert(false, 'Invalid platform $platform.'); @@ -205,6 +206,7 @@ case TargetPlatform.darwin_x64: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: + case TargetPlatform.tester: assert(mode == null, 'Platform $platform does not support different build modes.'); return fs.path.join(engineDir, platformName); case TargetPlatform.ios:
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index f0ecb7e..82e7dea 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -157,6 +157,7 @@ linux_x64, windows_x64, fuchsia, + tester, } String getNameForTargetPlatform(TargetPlatform platform) { @@ -179,6 +180,8 @@ return 'windows-x64'; case TargetPlatform.fuchsia: return 'fuchsia'; + case TargetPlatform.tester: + return 'flutter-tester'; } assert(false); return null;
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index 52a3fb6..6148550 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -247,6 +247,7 @@ case TargetPlatform.linux_x64: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: + case TargetPlatform.tester: assert(false); } @@ -347,6 +348,7 @@ case TargetPlatform.linux_x64: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: + case TargetPlatform.tester: assert(false); }
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 112bb1a..60a0617 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -24,6 +24,7 @@ import '../run_cold.dart'; import '../run_hot.dart'; import '../runner/flutter_command.dart'; +import '../tester/flutter_tester.dart'; import '../vmservice.dart'; const String protocolVersion = '0.2.0'; @@ -570,6 +571,7 @@ addDeviceDiscoverer(new AndroidDevices()); addDeviceDiscoverer(new IOSDevices()); addDeviceDiscoverer(new IOSSimulators()); + addDeviceDiscoverer(new FlutterTesterDevices()); } void addDeviceDiscoverer(PollingDeviceDiscovery discoverer) {
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 1c5b2a7..dd7475c 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -16,6 +16,7 @@ import 'globals.dart'; import 'ios/devices.dart'; import 'ios/simulators.dart'; +import 'tester/flutter_tester.dart'; DeviceManager get deviceManager => context[DeviceManager]; @@ -28,6 +29,7 @@ _deviceDiscoverers.add(new AndroidDevices()); _deviceDiscoverers.add(new IOSDevices()); _deviceDiscoverers.add(new IOSSimulators()); + _deviceDiscoverers.add(new FlutterTesterDevices()); } final List<DeviceDiscovery> _deviceDiscoverers = <DeviceDiscovery>[];
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index ec8f72c..9a02aaf 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -26,6 +26,7 @@ import '../dart/package_map.dart'; import '../device.dart'; import '../globals.dart'; +import '../tester/flutter_tester.dart'; import '../usage.dart'; import '../version.dart'; import '../vmservice.dart'; @@ -79,6 +80,11 @@ help: 'Captures a bug report file to submit to the Flutter team ' '(contains local paths, device\nidentifiers, and log snippets).'); + argParser.addFlag('show-test-device', + negatable: false, + hide: !verboseHelp, + help: 'List the special \'flutter-tester\' device in device listings. ' + 'This headless device is used to\ntest Flutter tooling.'); String packagesHelp; if (fs.isFileSync(kPackagesFileName)) @@ -180,6 +186,11 @@ contextOverrides[Logger] = new VerboseLogger(logger); } + if (topLevelResults['show-test-device'] || + topLevelResults['device-id'] == FlutterTesterDevices.kTesterDeviceId) { + FlutterTesterDevices.showFlutterTesterDevice = true; + } + String recordTo = topLevelResults['record-to']; String replayFrom = topLevelResults['replay-from'];
diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart new file mode 100644 index 0000000..be03f16 --- /dev/null +++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
@@ -0,0 +1,208 @@ +// Copyright 2018 The Chromium 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 'dart:async'; +import 'dart:convert'; + +import 'package:meta/meta.dart'; + +import '../application_package.dart'; +import '../artifacts.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../dart/package_map.dart'; +import '../device.dart'; +import '../globals.dart'; +import '../protocol_discovery.dart'; +import '../version.dart'; + +class FlutterTesterApp extends ApplicationPackage { + final String _directory; + + factory FlutterTesterApp.fromCurrentDirectory() { + return new FlutterTesterApp._(fs.currentDirectory.path); + } + + FlutterTesterApp._(String directory) + : _directory = directory, + super(id: directory); + + @override + String get name => fs.path.basename(_directory); + + @override + String get packagePath => fs.path.join(_directory, '.packages'); +} + +// TODO(scheglov): This device does not currently work with full restarts. +class FlutterTesterDevice extends Device { + final _FlutterTesterDeviceLogReader _logReader = + new _FlutterTesterDeviceLogReader(); + + Process _process; + + FlutterTesterDevice(String deviceId) : super(deviceId); + + @override + Future<bool> get isLocalEmulator async => false; + + @override + String get name => 'Flutter test device'; + + @override + DevicePortForwarder get portForwarder => null; + + @override + Future<String> get sdkNameAndVersion async { + final FlutterVersion flutterVersion = FlutterVersion.instance; + return 'Flutter ${flutterVersion.frameworkRevisionShort}'; + } + + @override + Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester; + + @override + void clearLogs() {} + + @override + DeviceLogReader getLogReader({ApplicationPackage app}) => _logReader; + + @override + Future<bool> installApp(ApplicationPackage app) async => true; + + @override + Future<bool> isAppInstalled(ApplicationPackage app) async => false; + + @override + Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false; + + @override + bool isSupported() => true; + + @override + Future<LaunchResult> startApp( + ApplicationPackage package, { + @required String mainPath, + String route, + @required DebuggingOptions debuggingOptions, + Map<String, dynamic> platformArgs, + bool prebuiltApplication: false, + bool applicationNeedsRebuild: false, + bool usesTerminalUi: true, + bool ipv6: false, + }) async { + if (!debuggingOptions.buildInfo.isDebug) { + printError('This device only supports debug mode.'); + return new LaunchResult.failed(); + } + + final String shellPath = artifacts.getArtifactPath(Artifact.flutterTester); + if (!fs.isFileSync(shellPath)) + throwToolExit('Cannot find Flutter shell at $shellPath'); + + final List<String> command = <String>[ + shellPath, + '--non-interactive', + '--enable-dart-profiling', + '--packages=${PackageMap.globalPackagesPath}', + ]; + if (debuggingOptions.debuggingEnabled) { + if (debuggingOptions.startPaused) { + command.add('--start-paused'); + } + if (debuggingOptions.hasObservatoryPort) + command + .add('--observatory-port=${debuggingOptions.hasObservatoryPort}'); + } + + // TODO(scheglov): Provide assets location, once it is possible. + + if (mainPath != null) { + command.add(mainPath); + } + + try { + printTrace('$shellPath ${command.join(' ')}'); + + _process = await processManager.start(command); + _process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + _logReader.addLine(line); + }); + _process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + _logReader.addLine(line); + }); + + if (!debuggingOptions.debuggingEnabled) + return new LaunchResult.succeeded(); + + final ProtocolDiscovery observatoryDiscovery = + new ProtocolDiscovery.observatory(getLogReader(), + hostPort: debuggingOptions.observatoryPort); + + final Uri observatoryUri = await observatoryDiscovery.uri; + return new LaunchResult.succeeded(observatoryUri: observatoryUri); + } catch (error) { + printError('Failed to launch $package: $error'); + return new LaunchResult.failed(); + } + } + + @override + Future<bool> stopApp(ApplicationPackage app) async { + _process?.kill(); + _process = null; + + return true; + } + + @override + Future<bool> uninstallApp(ApplicationPackage app) async => true; +} + +class FlutterTesterDevices extends PollingDeviceDiscovery { + static const String kTesterDeviceId = 'flutter-tester'; + + static bool showFlutterTesterDevice = false; + + final FlutterTesterDevice _testerDevice = + new FlutterTesterDevice(kTesterDeviceId); + + FlutterTesterDevices() : super('Flutter tester'); + + @override + bool get canListAnything => true; + + @override + bool get supportsPlatform => true; + + @override + Future<List<Device>> pollingGetDevices() async { + return showFlutterTesterDevice ? <Device>[_testerDevice] : <Device>[]; + } +} + +class _FlutterTesterDeviceLogReader extends DeviceLogReader { + final StreamController<String> _logLinesController = + new StreamController<String>.broadcast(); + + @override + int get appPid => 0; + + @override + Stream<String> get logLines => _logLinesController.stream; + + @override + String get name => 'flutter tester log reader'; + + void addLine(String line) => _logLinesController.add(line); +}