diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index dece5ff1b5d..a61ca00e9ef 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart @@ -219,11 +219,19 @@ known, it can be explicitly provided to attach via the command-line, e.g. MacOSDesignedForIPadDevices.allowDiscovery = true; await super.validateCommand(); - if (await findTargetDevice() == null) { + + final Device? targetDevice = await findTargetDevice(); + if (targetDevice == null) { throwToolExit(null); } + debugPort; - if (debugPort == null && debugUri == null && argResults!.wasParsed(FlutterCommand.ipv6Flag)) { + // Allow --ipv6 for iOS devices even if --debug-port and --debug-url + // are unknown + if (!_isIOSDevice(targetDevice) && + debugPort == null && + debugUri == null && + argResults!.wasParsed(FlutterCommand.ipv6Flag)) { throwToolExit( 'When the --debug-port or --debug-url is unknown, this command determines ' 'the value of --ipv6 on its own.', @@ -311,7 +319,7 @@ known, it can be explicitly provided to attach via the command-line, e.g. } rethrow; } - } else if ((device is IOSDevice) || (device is IOSSimulator) || (device is MacOSDesignedForIPadDevice)) { + } else if (_isIOSDevice(device)) { // Protocol Discovery relies on logging. On iOS earlier than 13, logging is gathered using syslog. // syslog is not available for iOS 13+. For iOS 13+, Protocol Discovery gathers logs from the VMService. // Since we don't have access to the VMService yet, Protocol Discovery cannot be used for iOS 13+. @@ -395,8 +403,6 @@ known, it can be explicitly provided to attach via the command-line, e.g. ); _logger.printStatus('Waiting for a connection from Flutter on ${device.name}...'); vmServiceUri = vmServiceDiscovery.uris; - // Determine ipv6 status from the scanned logs. - usesIpv6 = vmServiceDiscovery.ipv6; } } else { vmServiceUri = Stream @@ -544,6 +550,12 @@ known, it can be explicitly provided to attach via the command-line, e.g. } Future _validateArguments() async { } + + bool _isIOSDevice(Device device) { + return (device is IOSDevice) || + (device is IOSSimulator) || + (device is MacOSDesignedForIPadDevice); + } } class HotRunnerFactory { diff --git a/packages/flutter_tools/lib/src/protocol_discovery.dart b/packages/flutter_tools/lib/src/protocol_discovery.dart index 197346c240b..d21e602d2d6 100644 --- a/packages/flutter_tools/lib/src/protocol_discovery.dart +++ b/packages/flutter_tools/lib/src/protocol_discovery.dart @@ -20,9 +20,10 @@ class ProtocolDiscovery { required this.throttleDuration, this.hostPort, this.devicePort, - required this.ipv6, + required bool ipv6, required Logger logger, - }) : _logger = logger { + }) : _logger = logger, + _ipv6 = ipv6 { _deviceLogSubscription = logReader.logLines.listen( _handleLine, onDone: _stopScrapingLogs, @@ -56,7 +57,7 @@ class ProtocolDiscovery { final DevicePortForwarder? portForwarder; final int? hostPort; final int? devicePort; - final bool ipv6; + final bool _ipv6; final Logger _logger; /// The time to wait before forwarding a new VM Service URIs from [logReader]. @@ -144,7 +145,7 @@ class ProtocolDiscovery { hostUri = deviceUri.replace(port: actualHostPort); } - if (InternetAddress(hostUri.host).isLoopback && ipv6) { + if (InternetAddress(hostUri.host).isLoopback && _ipv6) { hostUri = hostUri.replace(host: InternetAddress.loopbackIPv6.host); } return hostUri; diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index b359e92fa33..fc54619172b 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -603,8 +603,7 @@ abstract class FlutterCommand extends Command { argParser.addFlag(ipv6Flag, negatable: false, help: 'Binds to IPv6 localhost instead of IPv4 when the flutter tool ' - 'forwards the host port to a device port. Not used when the ' - '"--debug-port" flag is not set.', + 'forwards the host port to a device port.', hide: !verboseHelp, ); _usesIpv6Flag = true; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 2c0ec1d975e..b85dd74d1f0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -591,7 +591,7 @@ void main() { DeviceManager: () => testDeviceManager, }); - testUsingContext('exits when ipv6 is specified and debug-port is not', () async { + testUsingContext('exits when ipv6 is specified and debug-port is not on non-iOS device', () async { testDeviceManager.devices = [device]; final AttachCommand command = AttachCommand( @@ -615,7 +615,63 @@ void main() { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), DeviceManager: () => testDeviceManager, - },); + }); + + testUsingContext('succeeds when ipv6 is specified and debug-port is not on iOS device', () async { + final FakeIOSDevice device = FakeIOSDevice( + logReader: fakeLogReader, + portForwarder: portForwarder, + majorSdkVersion: 12, + onGetLogReader: () { + fakeLogReader.addLine('Foo'); + fakeLogReader.addLine('The Dart VM service is listening on http://[::1]:$devicePort'); + return fakeLogReader; + }, + ); + testDeviceManager.devices = [device]; + final Completer completer = Completer(); + final StreamSubscription loggerSubscription = logger.stream.listen((String message) { + if (message == '[verbose] VM Service URL on device: http://[::1]:$devicePort') { + // The "VM Service URL on device" message is output by the ProtocolDiscovery when it found the VM Service. + completer.complete(); + } + }); + final FakeHotRunner hotRunner = FakeHotRunner(); + hotRunner.onAttach = ( + Completer? connectionInfoCompleter, + Completer? appStartedCompleter, + bool allowExistingDdsInstance, + bool enableDevTools, + ) async => 0; + hotRunner.exited = false; + hotRunner.isWaitingForVmService = false; + final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory() + ..hotRunner = hotRunner; + + await createTestCommandRunner(AttachCommand( + hotRunnerFactory: hotRunnerFactory, + artifacts: artifacts, + stdio: stdio, + logger: logger, + terminal: terminal, + signals: signals, + platform: platform, + processInfo: processInfo, + fileSystem: testFileSystem, + )).run(['attach', '--ipv6']); + await completer.future; + + expect(portForwarder.devicePort, devicePort); + expect(portForwarder.hostPort, hostPort); + + await fakeLogReader.dispose(); + await loggerSubscription.cancel(); + }, overrides: { + FileSystem: () => testFileSystem, + ProcessManager: () => FakeProcessManager.any(), + Logger: () => logger, + DeviceManager: () => testDeviceManager, + }); testUsingContext('exits when vm-service-port is specified and debug-port is not', () async { device.onGetLogReader = () {