Flutter run (#3553) * rework flutter run * fix npe with --debug-port * connect to obs and exit when that conneciton closes * update todos
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index f15cad6..00b9d6a 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -9,7 +9,6 @@ import 'package:path/path.dart' as path; import '../application_package.dart'; -import '../base/common.dart'; import '../base/os.dart'; import '../base/process.dart'; import '../build_configuration.dart'; @@ -154,19 +153,15 @@ } @override - Future<bool> startApp( + Future<LaunchResult> startApp( ApplicationPackage app, Toolchain toolchain, { String mainPath, String route, - bool checked: true, - bool clearLogs: false, - bool startPaused: false, - int observatoryPort: observatoryDefaultPort, - int diagnosticPort: diagnosticDefaultPort, + DebuggingOptions debuggingOptions, Map<String, dynamic> platformArgs }) async { - // TODO(chinmaygarde): Use checked, mainPath, route, clearLogs. + // TODO(chinmaygarde): Use checked, mainPath, route. // TODO(devoncarew): Handle startPaused, debugPort. printTrace('Building ${app.name} for $id'); @@ -174,7 +169,7 @@ bool buildResult = await buildIOSXcodeProject(app, buildForDevice: true); if (!buildResult) { printError('Could not build the precompiled application for the device.'); - return false; + return new LaunchResult.failed(); } // Step 2: Check that the application exists at the specified path. @@ -182,7 +177,7 @@ bool bundleExists = bundle.existsSync(); if (!bundleExists) { printError('Could not find the built application bundle at ${bundle.path}.'); - return false; + return new LaunchResult.failed(); } // Step 3: Attempt to install the application on the device. @@ -197,10 +192,10 @@ if (installationResult != 0) { printError('Could not install ${bundle.path} on $id.'); - return false; + return new LaunchResult.failed(); } - return true; + return new LaunchResult.succeeded(); } @override @@ -266,90 +261,46 @@ } class _IOSDeviceLogReader extends DeviceLogReader { - _IOSDeviceLogReader(this.device); + _IOSDeviceLogReader(this.device) { + _linesController = new StreamController<String>.broadcast( + onListen: _start, + onCancel: _stop + ); + } final IOSDevice device; - final StreamController<String> _linesStreamController = - new StreamController<String>.broadcast(); - + StreamController<String> _linesController; Process _process; - StreamSubscription<String> _stdoutSubscription; - StreamSubscription<String> _stderrSubscription; @override - Stream<String> get lines => _linesStreamController.stream; + Stream<String> get logLines => _linesController.stream; @override String get name => device.name; - @override - bool get isReading => _process != null; + void _start() { + runCommand(<String>[device.loggerPath]).then((Process process) { + _process = process; + _process.stdout.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onLine); + _process.stderr.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onLine); - @override - Future<int> get finished { - return _process != null ? _process.exitCode : new Future<int>.value(0); + _process.exitCode.then((int code) { + if (_linesController.hasListener) + _linesController.close(); + }); + }); } - @override - Future<Null> start() async { - if (_process != null) { - throw new StateError( - '_IOSDeviceLogReader must be stopped before it can be started.' - ); - } - _process = await runCommand(<String>[device.loggerPath]); - _stdoutSubscription = - _process.stdout.transform(UTF8.decoder) - .transform(const LineSplitter()).listen(_onLine); - _stderrSubscription = - _process.stderr.transform(UTF8.decoder) - .transform(const LineSplitter()).listen(_onLine); - _process.exitCode.then(_onExit); - } - - @override - Future<Null> stop() async { - if (_process == null) { - throw new StateError( - '_IOSDeviceLogReader must be started before it can be stopped.' - ); - } - _stdoutSubscription?.cancel(); - _stdoutSubscription = null; - _stderrSubscription?.cancel(); - _stderrSubscription = null; - await _process.kill(); - _process = null; - } - - void _onExit(int exitCode) { - _stdoutSubscription?.cancel(); - _stdoutSubscription = null; - _stderrSubscription?.cancel(); - _stderrSubscription = null; - _process = null; - } - - RegExp _runnerRegex = new RegExp(r'Runner'); + static final RegExp _runnerRegex = new RegExp(r'FlutterRunner'); void _onLine(String line) { - if (!_runnerRegex.hasMatch(line)) - return; - - _linesStreamController.add(line); + if (_runnerRegex.hasMatch(line)) + _linesController.add(line); } - @override - int get hashCode => name.hashCode; - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other is! _IOSDeviceLogReader) - return false; - return other.name == name; + void _stop() { + _process?.kill(); } }
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 043bc59..a55a74a 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -9,7 +9,6 @@ import 'package:path/path.dart' as path; import '../application_package.dart'; -import '../base/common.dart'; import '../base/context.dart'; import '../base/process.dart'; import '../build_configuration.dart'; @@ -437,32 +436,25 @@ } @override - Future<bool> startApp( + Future<LaunchResult> startApp( ApplicationPackage app, Toolchain toolchain, { String mainPath, String route, - bool checked: true, - bool clearLogs: false, - bool startPaused: false, - int observatoryPort: observatoryDefaultPort, - int diagnosticPort: diagnosticDefaultPort, + DebuggingOptions debuggingOptions, Map<String, dynamic> platformArgs }) async { printTrace('Building ${app.name} for $id.'); - if (clearLogs) - this.clearLogs(); - if (!(await _setupUpdatedApplicationBundle(app, toolchain))) - return false; + return new LaunchResult.failed(); - ServiceProtocolDiscovery serviceProtocolDiscovery = - new ServiceProtocolDiscovery(logReader, ServiceProtocolDiscovery.kObservatoryService); + ServiceProtocolDiscovery observatoryDiscovery; - // We take this future here but do not wait for completion until *after* we - // start the application. - Future<int> scrapeServicePort = serviceProtocolDiscovery.nextPort(); + if (debuggingOptions.debuggingEnabled) { + observatoryDiscovery = new ServiceProtocolDiscovery( + logReader, ServiceProtocolDiscovery.kObservatoryService); + } // Prepare launch arguments. List<String> args = <String>[ @@ -471,39 +463,47 @@ "--packages=${path.absolute('.packages')}", ]; - if (checked) - args.add("--enable-checked-mode"); + if (debuggingOptions.debuggingEnabled) { + if (debuggingOptions.checked) + args.add("--enable-checked-mode"); + if (debuggingOptions.startPaused) + args.add("--start-paused"); - if (startPaused) - args.add("--start-paused"); - - if (observatoryPort != observatoryDefaultPort) + int observatoryPort = await debuggingOptions.findBestObservatoryPort(); args.add("--observatory-port=$observatoryPort"); + } // Launch the updated application in the simulator. try { SimControl.instance.launch(id, app.id, args); } catch (error) { printError('$error'); - return false; + return new LaunchResult.failed(); } - // Wait for the service protocol port here. This will complete once the - // device has printed "Observatory is listening on..." - printTrace('Waiting for observatory port to be available...'); + if (!debuggingOptions.debuggingEnabled) { + return new LaunchResult.succeeded(); + } else { + // Wait for the service protocol port here. This will complete once the + // device has printed "Observatory is listening on..." + printTrace('Waiting for observatory port to be available...'); - try { - int devicePort = await scrapeServicePort.timeout(new Duration(seconds: 12)); - printTrace('service protocol port = $devicePort'); - printStatus('Observatory listening on http://127.0.0.1:$devicePort'); - return true; - } catch (error) { - if (error is TimeoutException) - printError('Timed out while waiting for a debug connection.'); - else - printError('Error waiting for a debug connection: $error'); - - return false; + try { + int devicePort = await observatoryDiscovery + .nextPort() + .timeout(new Duration(seconds: 20)); + printTrace('service protocol port = $devicePort'); + printStatus('Observatory listening on http://127.0.0.1:$devicePort'); + return new LaunchResult.succeeded(observatoryPort: devicePort); + } catch (error) { + if (error is TimeoutException) + printError('Timed out while waiting for a debug connection.'); + else + printError('Error waiting for a debug connection: $error'); + return new LaunchResult.failed(); + } finally { + observatoryDiscovery.cancel(); + } } } @@ -662,118 +662,57 @@ } class _IOSSimulatorLogReader extends DeviceLogReader { - _IOSSimulatorLogReader(this.device); + _IOSSimulatorLogReader(this.device) { + _linesController = new StreamController<String>.broadcast( + onListen: () { + _start(); + }, + onCancel: _stop + ); + } final IOSSimulator device; - final StreamController<String> _linesStreamController = - new StreamController<String>.broadcast(); - + StreamController<String> _linesController; bool _lastWasFiltered = false; - // We log from two logs: the device and the system log. + // We log from two files: the device and the system log. Process _deviceProcess; - StreamSubscription<String> _deviceStdoutSubscription; - StreamSubscription<String> _deviceStderrSubscription; - Process _systemProcess; - StreamSubscription<String> _systemStdoutSubscription; - StreamSubscription<String> _systemStderrSubscription; @override - Stream<String> get lines => _linesStreamController.stream; + Stream<String> get logLines => _linesController.stream; @override String get name => device.name; - @override - bool get isReading => (_deviceProcess != null) && (_systemProcess != null); - - @override - Future<int> get finished { - return (_deviceProcess != null) ? _deviceProcess.exitCode : new Future<int>.value(0); - } - - @override - Future<Null> start() async { - if (isReading) { - throw new StateError( - '_IOSSimulatorLogReader must be stopped before it can be started.' - ); - } - - // TODO(johnmccutchan): Add a ProcessSet abstraction that handles running - // N processes and merging their output. - + Future<Null> _start() async { // Device log. device.ensureLogsExists(); - _deviceProcess = await runCommand( - <String>['tail', '-n', '+0', '-F', device.logFilePath]); - _deviceStdoutSubscription = - _deviceProcess.stdout.transform(UTF8.decoder) - .transform(const LineSplitter()).listen(_onDeviceLine); - _deviceStderrSubscription = - _deviceProcess.stderr.transform(UTF8.decoder) - .transform(const LineSplitter()).listen(_onDeviceLine); - _deviceProcess.exitCode.then(_onDeviceExit); + _deviceProcess = await runCommand(<String>['tail', '-n', '0', '-F', device.logFilePath]); + _deviceProcess.stdout.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onDeviceLine); + _deviceProcess.stderr.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onDeviceLine); // Track system.log crashes. // ReportCrash[37965]: Saved crash report for FlutterRunner[37941]... - _systemProcess = await runCommand( - <String>['tail', '-F', '/private/var/log/system.log']); - _systemStdoutSubscription = - _systemProcess.stdout.transform(UTF8.decoder) - .transform(const LineSplitter()).listen(_onSystemLine); - _systemStderrSubscription = - _systemProcess.stderr.transform(UTF8.decoder) - .transform(const LineSplitter()).listen(_onSystemLine); - _systemProcess.exitCode.then(_onSystemExit); - } + _systemProcess = await runCommand(<String>['tail', '-n', '0', '-F', '/private/var/log/system.log']); + _systemProcess.stdout.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onSystemLine); + _systemProcess.stderr.transform(UTF8.decoder).transform(const LineSplitter()).listen(_onSystemLine); - @override - Future<Null> stop() async { - if (!isReading) { - throw new StateError( - '_IOSSimulatorLogReader must be started before it can be stopped.' - ); - } - if (_deviceProcess != null) { - await _deviceProcess.kill(); - _deviceProcess = null; - } - _onDeviceExit(0); - if (_systemProcess != null) { - await _systemProcess.kill(); - _systemProcess = null; - } - _onSystemExit(0); - } - - void _onDeviceExit(int exitCode) { - _deviceStdoutSubscription?.cancel(); - _deviceStdoutSubscription = null; - _deviceStderrSubscription?.cancel(); - _deviceStderrSubscription = null; - _deviceProcess = null; - } - - void _onSystemExit(int exitCode) { - _systemStdoutSubscription?.cancel(); - _systemStdoutSubscription = null; - _systemStderrSubscription?.cancel(); - _systemStderrSubscription = null; - _systemProcess = null; + _deviceProcess.exitCode.then((int code) { + if (_linesController.hasListener) + _linesController.close(); + }); } // Match the log prefix (in order to shorten it): // 'Jan 29 01:31:44 devoncarew-macbookpro3 SpringBoard[96648]: ...' - final RegExp _mapRegex = - new RegExp(r'\S+ +\S+ +\S+ \S+ (.+)\[\d+\]\)?: (.*)$'); + static final RegExp _mapRegex = new RegExp(r'\S+ +\S+ +\S+ \S+ (.+)\[\d+\]\)?: (.*)$'); // Jan 31 19:23:28 --- last message repeated 1 time --- - final RegExp _lastMessageRegex = new RegExp(r'\S+ +\S+ +\S+ --- (.*) ---$'); + static final RegExp _lastMessageRegex = new RegExp(r'\S+ +\S+ +\S+ --- (.*) ---$'); - final RegExp _flutterRunnerRegex = new RegExp(r' FlutterRunner\[\d+\] '); + static final RegExp _flutterRunnerRegex = new RegExp(r' FlutterRunner\[\d+\] '); String _filterDeviceLine(String string) { Match match = _mapRegex.matchAsPrefix(string); @@ -808,8 +747,7 @@ String filteredLine = _filterDeviceLine(line); if (filteredLine == null) return; - - _linesStreamController.add(filteredLine); + _linesController.add(filteredLine); } String _filterSystemLog(String string) { @@ -825,19 +763,12 @@ if (filteredLine == null) return; - _linesStreamController.add(filteredLine); + _linesController.add(filteredLine); } - @override - int get hashCode => device.logFilePath.hashCode; - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other is! _IOSSimulatorLogReader) - return false; - return other.device.logFilePath == device.logFilePath; + void _stop() { + _deviceProcess?.kill(); + _systemProcess?.kill(); } }