From 7cbec567de997daeb12837c362ee2a3940ef68de Mon Sep 17 00:00:00 2001 From: Helin Shiah Date: Fri, 31 Jul 2020 11:06:05 -0700 Subject: [PATCH] Add daemon handler to start devtools (#62608) --- packages/flutter_tools/doc/daemon.md | 6 ++++ .../lib/src/commands/daemon.dart | 26 ++++++++++++++ .../lib/src/resident_runner.dart | 15 ++++++-- .../commands.shard/hermetic/daemon_test.dart | 35 +++++++++++++++++++ packages/flutter_tools/test/src/mocks.dart | 3 ++ 5 files changed, 82 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/doc/daemon.md b/packages/flutter_tools/doc/daemon.md index ddfc0d35898..2e4981296e3 100644 --- a/packages/flutter_tools/doc/daemon.md +++ b/packages/flutter_tools/doc/daemon.md @@ -252,6 +252,12 @@ The returned `params` will contain: - `emulatorName` - the name of the emulator created; this will have been auto-generated if you did not supply one - `error` - when `success`=`false`, a message explaining why the creation of the emulator failed +### devtools domain + +#### devtools.serve + +The `serve()` command starts a DevTools server if one isn't already running and prints out the host and port of the server. + ## 'flutter run --machine' and 'flutter attach --machine' When running `flutter run --machine` or `flutter attach --machine` the following subset of the daemon is available: diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 16fed434406..426e0c16fef 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -82,6 +82,7 @@ class Daemon { _registerDomain(appDomain = AppDomain(this)); _registerDomain(deviceDomain = DeviceDomain(this)); _registerDomain(emulatorDomain = EmulatorDomain(this)); + _registerDomain(devToolsDomain = DevToolsDomain(this)); // Start listening. _commandSubscription = commandStream.listen( @@ -98,6 +99,7 @@ class Daemon { AppDomain appDomain; DeviceDomain deviceDomain; EmulatorDomain emulatorDomain; + DevToolsDomain devToolsDomain; StreamSubscription> _commandSubscription; int _outgoingRequestId = 1; final Map> _outgoingRequestCompleters = >{}; @@ -182,6 +184,7 @@ class Daemon { void _send(Map map) => sendCommand(map); Future shutdown({ dynamic error }) async { + await devToolsDomain?.dispose(); await _commandSubscription?.cancel(); for (final Domain domain in _domainMap.values) { await domain.dispose(); @@ -886,6 +889,29 @@ class DeviceDomain extends Domain { } } +class DevToolsDomain extends Domain { + DevToolsDomain(Daemon daemon) : super(daemon, 'devtools') { + registerHandler('serve', serve); + } + + DevtoolsLauncher _devtoolsLauncher; + + Future serve([ Map args ]) async { + _devtoolsLauncher ??= DevtoolsLauncher.instance; + final HttpServer server = await _devtoolsLauncher.serve(); + + sendEvent('devtools.serve', { + 'host': server.address.host, + 'port': server.port, + }); + } + + @override + Future dispose() async { + await _devtoolsLauncher?.close(); + } +} + Stream> get stdinCommandStream => globals.stdio.stdin .transform(utf8.decoder) .transform(const LineSplitter()) diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 945b6dfa029..279b93e3645 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -1731,9 +1731,7 @@ class DevtoolsLauncher { io.HttpServer _devtoolsServer; Future launch(Uri observatoryAddress) async { try { - _devtoolsServer ??= await devtools_server.serveDevTools( - enableStdinCommands: false, - ); + await serve(); await devtools_server.launchDevTools( { 'reuseWindows': true, @@ -1748,6 +1746,17 @@ class DevtoolsLauncher { } } + Future serve() async { + try { + _devtoolsServer ??= await devtools_server.serveDevTools( + enableStdinCommands: false, + ); + } on Exception catch (e, st) { + globals.printTrace('Failed to serve DevTools: $e\n$st'); + } + return _devtoolsServer; + } + Future close() async { await _devtoolsServer?.close(); _devtoolsServer = null; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart index 0e551f5e5e8..8198079f176 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/base/common.dart'; @@ -13,6 +14,7 @@ import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/resident_runner.dart'; +import 'package:mockito/mockito.dart'; import 'package:quiver/testing/async.dart'; import '../../src/common.dart'; @@ -23,11 +25,13 @@ void main() { Daemon daemon; NotifyingLogger notifyingLogger; BufferLogger bufferLogger; + DevtoolsLauncher mockDevToolsLauncher; group('daemon', () { setUp(() { bufferLogger = BufferLogger.test(); notifyingLogger = NotifyingLogger(verbose: false, parent: bufferLogger); + mockDevToolsLauncher = MockDevToolsLauncher(); }); tearDown(() { @@ -299,6 +303,33 @@ void main() { await output.close(); await input.close(); }); + + testUsingContext('devtools.serve command should return host and port', () async { + final StreamController> commands = StreamController>(); + final StreamController> responses = StreamController>(); + daemon = Daemon( + commands.stream, + responses.add, + notifyingLogger: notifyingLogger, + ); + final HttpServer mockDevToolsServer = MockDevToolsServer(); + final InternetAddress mockInternetAddress = MockInternetAddress(); + when(mockDevToolsServer.address).thenReturn(mockInternetAddress); + when(mockInternetAddress.host).thenReturn('127.0.0.1'); + when(mockDevToolsServer.port).thenReturn(1234); + + when(mockDevToolsLauncher.serve()).thenAnswer((_) async => mockDevToolsServer); + + commands.add({'id': 0, 'method': 'devtools.serve'}); + final Map response = await responses.stream.firstWhere(_isDevToolsEvent); + expect(response['params'], isNotEmpty); + expect(response['params']['host'], equals('127.0.0.1')); + expect(response['params']['port'], equals(1234)); + await responses.close(); + await commands.close(); + }, overrides: { + DevtoolsLauncher: () => mockDevToolsLauncher, + }); }); testUsingContext('notifyingLogger outputs trace messages in verbose mode', () async { @@ -435,6 +466,8 @@ bool _notEvent(Map map) => map['event'] == null; bool _isConnectedEvent(Map map) => map['event'] == 'daemon.connected'; +bool _isDevToolsEvent(Map map) => map['event'] == 'devtools.serve'; + class MockFuchsiaWorkflow extends FuchsiaWorkflow { MockFuchsiaWorkflow({ this.canListDevices = true }); @@ -455,3 +488,5 @@ class MockIOSWorkflow extends IOSWorkflow { @override final bool canListDevices; } + +class MockDevToolsLauncher extends Mock implements DevtoolsLauncher {} diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index e00ac548d12..42611c86c91 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -741,6 +741,9 @@ class MockStdIn extends Mock implements IOSink { class MockStream extends Mock implements Stream> {} +class MockDevToolsServer extends Mock implements HttpServer {} +class MockInternetAddress extends Mock implements InternetAddress {} + class AlwaysTrueBotDetector implements BotDetector { const AlwaysTrueBotDetector();