diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart index bfa4c21ea21..6943aeb4295 100644 --- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart @@ -168,9 +168,11 @@ class ResidentWebRunner extends ResidentRunner { target: target, flutterProject: flutterProject, buildInfo: debuggingOptions.buildInfo, - skipDwds: device is WebDevices, + hostname: debuggingOptions.hostname, + port: debuggingOptions.port, + skipDwds: device is WebServerDevice, ); - await device.startApp(package, mainPath: target, platformArgs: { + await device.startApp(package, mainPath: target, debuggingOptions: debuggingOptions, platformArgs: { 'uri': _webFs.uri }); if (supportsServiceProtocol) { diff --git a/packages/flutter_tools/lib/src/build_runner/web_fs.dart b/packages/flutter_tools/lib/src/build_runner/web_fs.dart index 3f7b19f1b36..9e2dd700e86 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_fs.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_fs.dart @@ -75,6 +75,8 @@ typedef WebFsFactory = Future Function({ @required FlutterProject flutterProject, @required BuildInfo buildInfo, @required bool skipDwds, + @required String hostname, + @required String port, }); /// The dev filesystem responsible for building and serving web applications. @@ -104,10 +106,10 @@ class WebFs { await _connectedApps?.cancel(); } - /// Retrieve the [DebugConnection] for the current application. + /// Retrieve the [DebugConnection] for the current application Future runAndDebug() { final Completer firstConnection = Completer(); - _connectedApps = _dwds.connectedApps.listen((AppConnection appConnection) async { + _connectedApps = _dwds.connectedApps.listen((AppConnection appConnection) async { appConnection.runMain(); final DebugConnection debugConnection = await _dwds.debugConnection(appConnection); if (!firstConnection.isCompleted) { @@ -140,6 +142,8 @@ class WebFs { @required FlutterProject flutterProject, @required BuildInfo buildInfo, @required bool skipDwds, + @required String hostname, + @required String port, }) async { // workaround for https://github.com/flutter/flutter/issues/38290 if (!flutterProject.dartTool.existsSync()) { @@ -165,7 +169,7 @@ class WebFs { await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries); // Initialize the dwds server. - final int port = await os.findFreePort(); + final int hostPort = port == null ? await os.findFreePort() : int.tryParse(port); // Map the bootstrap files to the correct package directory. final String targetBaseName = fs.path .withoutExtension(target).replaceFirst('lib${fs.path.separator}', ''); @@ -203,8 +207,8 @@ class WebFs { Dwds dwds; if (!skipDwds) { dwds = await dwdsFactory( - hostname: _kHostName, - applicationPort: port, + hostname: hostname ?? _kHostName, + applicationPort: hostPort, applicationTarget: kBuildTargetName, assetServerPort: daemonAssetPort, buildResults: filteredBuildResults, @@ -224,13 +228,13 @@ class WebFs { Cascade cascade = Cascade(); cascade = cascade.add(handler); cascade = cascade.add(_assetHandler(flutterProject)); - final HttpServer server = await httpMultiServerFactory(_kHostName, port); + final HttpServer server = await httpMultiServerFactory(hostname ?? _kHostName, hostPort); shelf_io.serveRequests(server, cascade.handler); return WebFs( client, server, dwds, - 'http://$_kHostName:$port/', + 'http://$_kHostName:$hostPort/', ); } diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index bc1146abe5b..f7d4aacc249 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -44,6 +44,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ..addOption('route', help: 'Which route to load when running the app.', ); + usesWebOptions(hide: !verboseHelp); usesTargetOption(); usesPortOptions(); usesIpv6Flag(); @@ -288,6 +289,8 @@ class RunCommand extends RunCommandBase { dumpSkpOnShaderCompilation: argResults['dump-skp-on-shader-compilation'], observatoryPort: observatoryPort, verboseSystemLogs: argResults['verbose-system-logs'], + hostname: featureFlags.isWebEnabled ? argResults['hostname'] : '', + port: featureFlags.isWebEnabled ? argResults['port'] : '', ); } } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 1de18da35de..bcd710e628d 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -484,6 +484,8 @@ class DebuggingOptions { this.useTestFonts = false, this.verboseSystemLogs = false, this.observatoryPort, + this.hostname, + this.port, }) : debuggingEnabled = true; DebuggingOptions.disabled(this.buildInfo) @@ -498,6 +500,8 @@ class DebuggingOptions { traceSystrace = false, dumpSkpOnShaderCompilation = false, verboseSystemLogs = false, + hostname = null, + port = null, observatoryPort = null; final bool debuggingEnabled; @@ -514,6 +518,8 @@ class DebuggingOptions { final bool useTestFonts; final bool verboseSystemLogs; final int observatoryPort; + final String port; + final String hostname; bool get hasObservatoryPort => observatoryPort != null; } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index c82a00d3878..cf2724b1f91 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -132,6 +132,20 @@ abstract class FlutterCommand extends Command { _requiresPubspecYaml = true; } + void usesWebOptions({ bool hide = true }) { + argParser.addOption('hostname', + defaultsTo: 'localhost', + help: 'The hostname to serve web application on.', + hide: hide, + ); + argParser.addOption('port', + defaultsTo: null, + help: 'The host port to serve the web application from. If not provided, the tool ' + 'will select a random open port on the host.', + hide: hide, + ); + } + void usesTargetOption() { argParser.addOption('target', abbr: 't', diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart index 5033d9722db..efd9872c4c2 100644 --- a/packages/flutter_tools/lib/src/web/web_device.dart +++ b/packages/flutter_tools/lib/src/web/web_device.dart @@ -137,7 +137,7 @@ class ChromeDevice extends Device { @override Future stopApp(ApplicationPackage app) async { - await _chrome.close(); + await _chrome?.close(); return true; } diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart index eec762c0316..e96f3520603 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart @@ -43,6 +43,8 @@ void main() { @required FlutterProject flutterProject, @required BuildInfo buildInfo, @required bool skipDwds, + @required String hostname, + @required String port, }) async { return mockWebFs; }, diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index a357a9421af..057ed4fcc7c 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -52,6 +52,8 @@ void main() { @required FlutterProject flutterProject, @required BuildInfo buildInfo, @required bool skipDwds, + @required String hostname, + @required String port, }) async { return mockWebFs; }, diff --git a/packages/flutter_tools/test/general.shard/web/web_fs_test.dart b/packages/flutter_tools/test/general.shard/web/web_fs_test.dart index e3462062b2b..572da10352e 100644 --- a/packages/flutter_tools/test/general.shard/web/web_fs_test.dart +++ b/packages/flutter_tools/test/general.shard/web/web_fs_test.dart @@ -26,8 +26,12 @@ void main() { MockHttpMultiServer mockHttpMultiServer; MockBuildDaemonClient mockBuildDaemonClient; MockOperatingSystemUtils mockOperatingSystemUtils; + dynamic lastAddress; + int lastPort; setUp(() { + lastAddress = null; + lastPort = null; mockBuildDaemonCreator = MockBuildDaemonCreator(); mockChromeLauncher = MockChromeLauncher(); mockHttpMultiServer = MockHttpMultiServer(); @@ -56,6 +60,8 @@ void main() { BuildDaemonCreator: () => mockBuildDaemonCreator, ChromeLauncher: () => mockChromeLauncher, HttpMultiServerFactory: () => (dynamic address, int port) async { + lastAddress = address; + lastPort = port; return mockHttpMultiServer; }, DwdsFactory: () => ({ @@ -83,6 +89,8 @@ void main() { target: fs.path.join('lib', 'main.dart'), buildInfo: BuildInfo.debug, flutterProject: flutterProject, + hostname: null, + port: null, ); // The build daemon is told to build once. @@ -91,6 +99,21 @@ void main() { // .dart_tool directory is created. expect(flutterProject.dartTool.existsSync(), true); })); + + test('Uses provided port number and hostname.', () => testbed.run(() async { + final FlutterProject flutterProject = FlutterProject.current(); + await WebFs.start( + skipDwds: false, + target: fs.path.join('lib', 'main.dart'), + buildInfo: BuildInfo.debug, + flutterProject: flutterProject, + hostname: 'foo', + port: '1234', + ); + + expect(lastPort, 1234); + expect(lastAddress, contains('foo')); + })); } class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {}