[fuchsia_reload] Reload each instance of a module. (#9379)
diff --git a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart index e399fc6..37ca15d 100644 --- a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart +++ b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart
@@ -40,15 +40,24 @@ help: 'Path to Fuchsia source tree.'); argParser.addOption('gn-target', abbr: 'g', - help: 'GN target of the application, e.g //path/to/app:app'); + help: 'GN target of the application, e.g //path/to/app:app.'); + argParser.addFlag('list', + abbr: 'l', + defaultsTo: false, + help: 'Lists the running modules. ' + 'Requires the flags --address(-a) and --fuchsia-root(-f).'); argParser.addOption('name-override', abbr: 'n', help: 'On-device name of the application binary.'); + argParser.addOption('isolate-number', + abbr: 'i', + help: 'To reload only one instance, speficy the isolate number, e.g. ' + 'the number in foo\$main-###### given by --list.'); argParser.addOption('target', abbr: 't', defaultsTo: flx.defaultMainPath, help: 'Target app path / main entry-point file. ' - 'Relative to --gn-target path, e.g. lib/main.dart'); + 'Relative to --gn-target path, e.g. lib/main.dart.'); } @override @@ -61,11 +70,14 @@ String _projectRoot; String _projectName; String _binaryName; + String _isolateNumber; String _fuchsiaProjectPath; String _target; String _address; String _dotPackagesPath; + bool _list; + @override Future<Null> runCommand() async { Cache.releaseLockEarly(); @@ -79,9 +91,14 @@ for (int port in servicePorts) printTrace('Fuchsia service port: $port'); + if (_list) { + await _listViews(servicePorts); + return; + } + // Check that there are running VM services on the returned // ports, and find the Isolates that are running the target app. - final String isolateName = '$_binaryName\$main'; + final String isolateName = '$_binaryName\$main$_isolateNumber'; final List<int> targetPorts = await _filterPorts(servicePorts, isolateName); if (targetPorts.isEmpty) throwToolExit('No VMs found running $_binaryName.'); @@ -90,43 +107,55 @@ // Set up a device and hot runner and attach the hot runner to the first // vm service we found. - final int firstPort = targetPorts[0]; - final String fullAddress = '$_address:$firstPort'; - final FuchsiaDevice device = new FuchsiaDevice(fullAddress); + final List<String> fullAddresses = targetPorts.map( + (int p) => '$_address:$p' + ).toList(); + final FuchsiaDevice device = new FuchsiaDevice(fullAddresses[0]); final HotRunner hotRunner = new HotRunner( device, debuggingOptions: new DebuggingOptions.enabled(getBuildMode()), target: _target, projectRootPath: _fuchsiaProjectPath, - packagesFilePath: _dotPackagesPath, + packagesFilePath: _dotPackagesPath ); - final Uri observatoryUri = Uri.parse('http://$fullAddress'); - printStatus('Connecting to $_binaryName at $observatoryUri'); - await hotRunner.attach(observatoryUri, isolateFilter: isolateName); + final List<Uri> observatoryUris = + fullAddresses.map((String a) => Uri.parse('http://$a')).toList(); + printStatus('Connecting to $_binaryName'); + await hotRunner.attach(observatoryUris, isolateFilter: isolateName); } - // Find ports where there is a view isolate with the given name - Future<List<int>> _filterPorts(List<int> ports, String isolateFilter) async { - final List<int> result = <int>[]; + Future<List<FlutterView>> _getViews(List<int> ports) async { + final List<FlutterView> views = <FlutterView>[]; for (int port in ports) { final String addr = 'http://$_address:$port'; final Uri uri = Uri.parse(addr); final VMService vmService = VMService.connect(uri); await vmService.getVM(); await vmService.waitForViews(); - if (vmService.vm.firstView == null) { - printTrace('Found no views at $addr'); - continue; - } - for (FlutterView v in vmService.vm.views) { - printTrace('At $addr, found view: ${v.uiIsolate.name}'); - if (v.uiIsolate.name.indexOf(isolateFilter) == 0) - result.add(port); - } + views.addAll(vmService.vm.views); + } + return views; + } + + // Find ports where there is a view isolate with the given name + Future<List<int>> _filterPorts(List<int> ports, String isolateFilter) async { + final List<int> result = <int>[]; + for (FlutterView v in await _getViews(ports)) { + final Uri addr = v.owner.vmService.httpAddress; + printTrace('At $addr, found view: ${v.uiIsolate.name}'); + if (v.uiIsolate.name.indexOf(isolateFilter) == 0) + result.add(addr.port); } return result; } + Future<Null> _listViews(List<int> ports) async { + for (FlutterView v in await _getViews(ports)) { + final Uri addr = v.owner.vmService.httpAddress; + printStatus('At $addr, found view: ${v.uiIsolate.name}'); + } + } + void _validateArguments() { _fuchsiaRoot = argResults['fuchsia-root']; if (_fuchsiaRoot == null) @@ -138,6 +167,12 @@ if (_address == null) throwToolExit('Give the address of the device running Fuchsia with --address.'); + _list = argResults['list']; + if (_list) { + // For --list, we only need the device address and the Fuchsia tree root. + return; + } + final List<String> gnTarget = _extractPathAndName(argResults['gn-target']); _projectRoot = gnTarget[0]; _projectName = gnTarget[1]; @@ -166,6 +201,13 @@ } else { _binaryName = nameOverride; } + + final String isolateNumber = argResults['isolate-number']; + if (isolateNumber == null) { + _isolateNumber = ''; + } else { + _isolateNumber = '-$isolateNumber'; + } } List<String> _extractPathAndName(String gnTarget) {
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index d843fee..2cef785 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -64,8 +64,7 @@ bool get isRunningRelease => debuggingOptions.buildMode == BuildMode.release; bool get supportsServiceProtocol => isRunningDebug || isRunningProfile; - VMService vmService; - FlutterView currentView; + List<VMService> vmServices; StreamSubscription<String> _loggingSubscription; /// Start the app and keep the process running during its lifetime. @@ -78,6 +77,25 @@ bool get supportsRestart => false; + String isolateFilter; + + List<FlutterView> _currentViewsCache; + List<FlutterView> get currentViews { + if (_currentViewsCache == null) { + if ((vmServices == null) || vmServices.isEmpty) + return null; + if (isolateFilter == null) + return vmServices[0].vm.views.toList(); + final List<FlutterView> result = <FlutterView>[]; + for (VMService service in vmServices) + result.addAll(service.vm.allViewsWithName(isolateFilter)); + _currentViewsCache = result; + } + return _currentViewsCache; + } + + FlutterView get currentView => (currentViews != null) ? currentViews[0] : null; + Future<OperationResult> restart({ bool fullRestart: false, bool pauseAfterRestart: false }) { throw 'unsupported'; } @@ -94,21 +112,26 @@ appFinished(); } + Future<Null> refreshViews() async { + if ((vmServices == null) || vmServices.isEmpty) + return; + for (VMService service in vmServices) + await service.vm.refreshViews(); + _currentViewsCache = null; + } + Future<Null> _debugDumpApp() async { - if (vmService != null) - await vmService.vm.refreshViews(); + await refreshViews(); await currentView.uiIsolate.flutterDebugDumpApp(); } Future<Null> _debugDumpRenderTree() async { - if (vmService != null) - await vmService.vm.refreshViews(); + await refreshViews(); await currentView.uiIsolate.flutterDebugDumpRenderTree(); } Future<Null> _debugToggleDebugPaintSizeEnabled() async { - if (vmService != null) - await vmService.vm.refreshViews(); + await refreshViews(); await currentView.uiIsolate.flutterToggleDebugPaintSizeEnabled(); } @@ -117,8 +140,7 @@ final File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png'); try { if (supportsServiceProtocol && isRunningDebug) { - if (vmService != null) - await vmService.vm.refreshViews(); + await refreshViews(); try { await currentView.uiIsolate.flutterDebugAllowBanner(false); } catch (error) { @@ -148,8 +170,7 @@ } Future<String> _debugRotatePlatform() async { - if (vmService != null) - await vmService.vm.refreshViews(); + await refreshViews(); switch (await currentView.uiIsolate.flutterPlatformOverride()) { case 'iOS': return await currentView.uiIsolate.flutterPlatformOverride('android'); @@ -209,25 +230,37 @@ _loggingSubscription = null; } - Future<Null> connectToServiceProtocol(Uri uri, {String isolateFilter}) async { - if (!debuggingOptions.debuggingEnabled) + Future<Null> connectToServiceProtocol( + List<Uri> uris, { + String isolateFilter + }) async { + if (!debuggingOptions.debuggingEnabled) { return new Future<Null>.error('Error the service protocol is not enabled.'); - vmService = VMService.connect(uri); - printTrace('Connected to service protocol: $uri'); - await vmService.getVM(); + } + final List<VMService> services = new List<VMService>(uris.length); + for (int i = 0; i < uris.length; i++) { + services[i] = VMService.connect(uris[i]); + printTrace('Connected to service protocol: ${uris[i]}'); + } + vmServices = services; + for (VMService service in services) + await service.getVM(); + + this.isolateFilter = isolateFilter; // Refresh the view list, and wait a bit for the list to populate. - await vmService.waitForViews(); - currentView = (isolateFilter == null) - ? vmService.vm.firstView - : vmService.vm.firstViewWithName(isolateFilter); + for (VMService service in services) + await service.waitForViews(); if (currentView == null) throwToolExit('No Flutter view is available'); // Listen for service protocol connection to close. - vmService.done.then<Null>( + for (VMService service in services) { + service.done.then<Null>( _serviceProtocolDone, - onError: _serviceProtocolError).whenComplete(appFinished); + onError: _serviceProtocolError + ).whenComplete(appFinished); + } } Future<Null> _serviceProtocolDone(dynamic object) { @@ -357,7 +390,10 @@ Future<Null> preStop() async { } Future<Null> stopApp() async { - if (vmService != null && !vmService.isClosed) { + if (vmServices != null && + vmServices.isNotEmpty && + !vmServices[0].isClosed) { + // TODO(zra): iterate over all the views. if ((currentView != null) && (currentView.uiIsolate != null)) { // TODO(johnmccutchan): Wait for the exit command to complete. currentView.uiIsolate.flutterExit();
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index a31a326..4db138c 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -101,27 +101,27 @@ // Connect to observatory. if (debuggingOptions.debuggingEnabled) - await connectToServiceProtocol(_result.observatoryUri); + await connectToServiceProtocol(<Uri>[_result.observatoryUri]); if (_result.hasObservatory) { connectionInfoCompleter?.complete(new DebugConnectionInfo( httpUri: _result.observatoryUri, - wsUri: vmService.wsAddress, + wsUri: vmServices[0].wsAddress, )); } printTrace('Application running.'); - if (vmService != null) { - device.getLogReader(app: package).appPid = vmService.vm.pid; - await vmService.vm.refreshViews(); - printTrace('Connected to ${vmService.vm.firstView}\.'); + if (vmServices != null && vmServices.isNotEmpty) { + device.getLogReader(app: package).appPid = vmServices[0].vm.pid; + await refreshViews(); + printTrace('Connected to $currentView.'); } - if (vmService != null && traceStartup) { + if (vmServices != null && vmServices.isNotEmpty && traceStartup) { printStatus('Downloading startup trace info...'); try { - await downloadStartupTrace(vmService); + await downloadStartupTrace(vmServices[0]); } catch(error) { printError(error); return 2; @@ -174,7 +174,7 @@ @override Future<Null> preStop() async { // If we're running in release mode, stop the app using the device logic. - if (vmService == null) + if (vmServices == null || vmServices.isEmpty) await device.stopApp(package); } }
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 61d1bce..77b1045 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -87,21 +87,21 @@ return true; } - Future<int> attach(Uri observatoryUri, { + Future<int> attach(List<Uri> observatoryUris, { Completer<DebugConnectionInfo> connectionInfoCompleter, Completer<Null> appStartedCompleter, String isolateFilter, }) async { - _observatoryUri = observatoryUri; + _observatoryUri = observatoryUris[0]; try { await connectToServiceProtocol( - _observatoryUri, isolateFilter: isolateFilter); + observatoryUris, isolateFilter: isolateFilter); } catch (error) { printError('Error connecting to the service protocol: $error'); return 2; } - device.getLogReader(app: package).appPid = vmService.vm.pid; + device.getLogReader(app: package).appPid = vmServices[0].vm.pid; try { final Uri baseUri = await _initDevFS(); @@ -109,7 +109,7 @@ connectionInfoCompleter.complete( new DebugConnectionInfo( httpUri: _observatoryUri, - wsUri: vmService.wsAddress, + wsUri: vmServices[0].wsAddress, baseUri: baseUri.toString() ) ); @@ -124,8 +124,9 @@ return 3; } - await vmService.vm.refreshViews(); - printTrace('Connected to $currentView.'); + await refreshViews(); + for (FlutterView view in currentViews) + printTrace('Connected to $view.'); if (stayResident) { setupTerminal(); @@ -140,7 +141,7 @@ // Measure time to perform a hot restart. printStatus('Benchmarking hot restart'); await restart(fullRestart: true); - await vmService.vm.refreshViews(); + await refreshViews(); // TODO(johnmccutchan): Modify script entry point. printStatus('Benchmarking hot reload'); // Measure time to perform a hot reload. @@ -219,7 +220,7 @@ return 2; } - return attach(result.observatoryUri, + return attach(<Uri>[result.observatoryUri], connectionInfoCompleter: connectionInfoCompleter, appStartedCompleter: appStartedCompleter); } @@ -241,7 +242,7 @@ Future<Uri> _initDevFS() { final String fsName = fs.path.basename(projectRootPath); - _devFS = new DevFS(vmService, + _devFS = new DevFS(vmServices[0], fsName, fs.directory(projectRootPath), packagesFilePath: packagesFilePath); @@ -425,11 +426,18 @@ final Uri devicePackagesUri = _devFS.baseUri.resolve('.packages'); if (benchmarkMode) vmReloadTimer.start(); - final Map<String, dynamic> reloadReport = - await currentView.uiIsolate.reloadSources( - pause: pause, - rootLibUri: deviceEntryUri, - packagesUri: devicePackagesUri); + + Map<String, dynamic> reloadReport; + for (FlutterView view in currentViews) { + final Map<String, dynamic> report = await view.uiIsolate.reloadSources( + pause: pause, + rootLibUri: deviceEntryUri, + packagesUri: devicePackagesUri + ); + // Just take the first one until we think of something smart to do. + if (reloadReport == null) + reloadReport = report; + } if (!validateReloadReport(reloadReport)) { // Reload failed. flutterUsage.sendEvent('hot', 'reload-reject'); @@ -464,29 +472,43 @@ if (benchmarkMode) reassembleTimer.start(); // Reload the isolate. - await currentView.uiIsolate.reload(); + for (FlutterView view in currentViews) + await view.uiIsolate.reload(); // We are now running from source. _runningFromSnapshot = false; // Check if the isolate is paused. - final ServiceEvent pauseEvent = currentView.uiIsolate.pauseEvent; - if ((pauseEvent != null) && (pauseEvent.isPauseEvent)) { - // Isolate is paused. Stop here. - printTrace('Skipping reassemble because isolate is paused.'); + + final List<FlutterView> reassembleViews = <FlutterView>[]; + for (FlutterView view in currentViews) { + final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent; + if ((pauseEvent != null) && (pauseEvent.isPauseEvent)) { + // Isolate is paused. Don't reassemble. + continue; + } + reassembleViews.add(view); + } + if (reassembleViews.isEmpty) { + printTrace('Skipping reassemble because all isolates are paused.'); return new OperationResult(OperationResult.ok.code, reloadMessage); } await _evictDirtyAssets(); printTrace('Reassembling application'); - try { - await currentView.uiIsolate.flutterReassemble(); - } catch (_) { - printError('Reassembling application failed.'); - return new OperationResult(1, 'error reassembling application'); - } - try { - /* ensure that a frame is scheduled */ - await currentView.uiIsolate.uiWindowScheduleFrame(); - } catch (_) { - /* ignore any errors */ + bool reassembleAndScheduleErrors = false; + for (FlutterView view in reassembleViews) { + try { + await view.uiIsolate.flutterReassemble(); + } catch (error) { + reassembleAndScheduleErrors = true; + printError('Reassembling ${view.uiIsolate.name} failed: $error'); + continue; + } + try { + /* ensure that a frame is scheduled */ + await view.uiIsolate.uiWindowScheduleFrame(); + } catch (error) { + reassembleAndScheduleErrors = true; + printError('Scheduling a frame for ${view.uiIsolate.name} failed: $error'); + } } reloadTimer.stop(); printTrace('Hot reload performed in ' @@ -503,7 +525,10 @@ } if (shouldReportReloadTime) flutterUsage.sendTiming('hot', 'reload', reloadTimer.elapsed); - return new OperationResult(OperationResult.ok.code, reloadMessage); + return new OperationResult( + reassembleAndScheduleErrors ? 1 : OperationResult.ok.code, + reloadMessage + ); } @override
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index ae7748b..9387d91 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -764,13 +764,12 @@ return _viewCache.values.isEmpty ? null : _viewCache.values.first; } - FlutterView firstViewWithName(String isolateFilter) { - if (_viewCache.values.isEmpty) { + List<FlutterView> allViewsWithName(String isolateFilter) { + if (_viewCache.values.isEmpty) return null; - } - return _viewCache.values.firstWhere( - (FlutterView v) => v.uiIsolate.name.contains(isolateFilter), - orElse: () => null); + return _viewCache.values.where( + (FlutterView v) => v.uiIsolate.name.contains(isolateFilter) + ).toList(); } }