Add ipv6 and observatory port support to the attach command. (#24537)
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index 844abb0b..312d823 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -53,6 +53,9 @@
argParser
..addOption(
'debug-port',
+ help: 'Device port where the observatory is listening.',
+ )..addOption(
+ 'observatory-port',
help: 'Local port where the observatory is listening.',
)..addOption('pid-file',
help: 'Specify a file to write the process id to. '
@@ -67,6 +70,12 @@
negatable: false,
help: 'Handle machine structured JSON command input and provide output '
'and progress in machine friendly format.',
+ )..addFlag('ipv6',
+ hide: true,
+ 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.',
);
hotRunnerFactory ??= HotRunnerFactory();
}
@@ -79,6 +88,14 @@
@override
final String description = 'Attach to a running application.';
+ // TODO(djshuckerow): this is now a confusing name. An explanation:
+ // The --observatory-port flag passed to `flutter run` is used to
+ // set up the port on the development macine that the Dart observatory
+ // listens to. This flag serves the same purpose in this command.
+ //
+ // The --debug-port flag passed only to `flutter attach` is used to
+ // set up the port on the device running a Flutter app to connect back
+ // to the host development machine.
int get observatoryPort {
if (argResults['debug-port'] == null)
return null;
@@ -96,6 +113,12 @@
if (await findTargetDevice() == null)
throwToolExit(null);
observatoryPort;
+ if (observatoryPort == null && argResults.wasParsed('ipv6')) {
+ throwToolExit(
+ 'When the --debug-port is unknown, this command determines '
+ 'the value of --ipv6 on its own.',
+ );
+ }
}
@override
@@ -163,14 +186,24 @@
);
printStatus('Waiting for a connection from Flutter on ${device.name}...');
observatoryUri = await observatoryDiscovery.uri;
+ // Determine ipv6 status from the scanned logs.
+ ipv6 = observatoryDiscovery.ipv6;
printStatus('Done.');
} finally {
await observatoryDiscovery?.cancel();
}
}
} else {
- final int localPort = await device.portForwarder.forward(devicePort);
- observatoryUri = Uri.parse('http://$ipv4Loopback:$localPort/');
+ ipv6 = argResults['ipv6'];
+ // int.tryParse will throw if it is passed null, so we need to do this.
+ final int argObservatoryPort = argResults['observatory-port'] == null
+ ? null
+ : int.tryParse(argResults['observatory-port']);
+ final int localPort = argObservatoryPort
+ ?? await device.portForwarder.forward(devicePort);
+ observatoryUri = ipv6
+ ? Uri.parse('http://[$ipv6Loopback]:$localPort/')
+ : Uri.parse('http://$ipv4Loopback:$localPort/');
}
try {
final FlutterDevice flutterDevice = FlutterDevice(
diff --git a/packages/flutter_tools/test/commands/attach_test.dart b/packages/flutter_tools/test/commands/attach_test.dart
index 9f4e67c..d2d725f 100644
--- a/packages/flutter_tools/test/commands/attach_test.dart
+++ b/packages/flutter_tools/test/commands/attach_test.dart
@@ -104,6 +104,7 @@
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
+ ipv6: false,
),
)..thenReturn(MockHotRunner());
@@ -134,6 +135,7 @@
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
+ ipv6: false,
),
)..called(1);
@@ -150,6 +152,21 @@
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
});
+
+ testUsingContext('exits when ipv6 is specified and debug-port is not', () async {
+ testDeviceManager.addDevice(device);
+
+ final AttachCommand command = AttachCommand();
+ await expectLater(
+ createTestCommandRunner(command).run(<String>['attach', '--ipv6']),
+ throwsToolExit(
+ message: 'When the --debug-port is unknown, this command determines '
+ 'the value of --ipv6 on its own.',
+ ),
+ );
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ },);
});
@@ -170,7 +187,8 @@
target: anyNamed('target'),
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
- usesTerminalUI: anyNamed('usesTerminalUI'))).thenReturn(
+ usesTerminalUI: anyNamed('usesTerminalUI'),
+ ipv6: false)).thenReturn(
MockHotRunner());
testDeviceManager.addDevice(device);
@@ -199,33 +217,92 @@
target: foo.path,
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
- usesTerminalUI: anyNamed('usesTerminalUI'))).called(1);
+ usesTerminalUI: anyNamed('usesTerminalUI'),
+ ipv6: false)).called(1);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
},);
- testUsingContext('forwards to given port', () async {
+ group('forwarding to given port', () {
const int devicePort = 499;
const int hostPort = 42;
- final MockPortForwarder portForwarder = MockPortForwarder();
- final MockAndroidDevice device = MockAndroidDevice();
+ MockPortForwarder portForwarder;
+ MockAndroidDevice device;
- when(device.portForwarder).thenReturn(portForwarder);
- when(portForwarder.forward(devicePort)).thenAnswer((_) async => hostPort);
- when(portForwarder.forwardedPorts).thenReturn(
- <ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
- when(portForwarder.unforward(any)).thenAnswer((_) async => null);
- testDeviceManager.addDevice(device);
+ setUp(() {
+ portForwarder = MockPortForwarder();
+ device = MockAndroidDevice();
- final AttachCommand command = AttachCommand();
+ when(device.portForwarder).thenReturn(portForwarder);
+ when(portForwarder.forward(devicePort)).thenAnswer((_) async => hostPort);
+ when(portForwarder.forwardedPorts).thenReturn(
+ <ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
+ when(portForwarder.unforward(any)).thenAnswer((_) async => null);
+ });
- await createTestCommandRunner(command).run(
- <String>['attach', '--debug-port', '$devicePort']);
+ testUsingContext('succeeds in ipv4 mode', () async {
+ testDeviceManager.addDevice(device);
+ final AttachCommand command = AttachCommand();
- verify(portForwarder.forward(devicePort)).called(1);
- }, overrides: <Type, Generator>{
- FileSystem: () => testFileSystem,
- },);
+ await createTestCommandRunner(command).run(
+ <String>['attach', '--debug-port', '$devicePort']);
+
+ verify(portForwarder.forward(devicePort)).called(1);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+
+ testUsingContext('succeeds in ipv6 mode', () async {
+ testDeviceManager.addDevice(device);
+ final AttachCommand command = AttachCommand();
+
+ await createTestCommandRunner(command).run(
+ <String>['attach', '--debug-port', '$devicePort', '--ipv6']);
+
+ verify(portForwarder.forward(devicePort)).called(1);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+
+ testUsingContext('skips in ipv4 mode with a provided observatory port', () async {
+ testDeviceManager.addDevice(device);
+ final AttachCommand command = AttachCommand();
+
+ await createTestCommandRunner(command).run(
+ <String>[
+ 'attach',
+ '--debug-port',
+ '$devicePort',
+ '--observatory-port',
+ '$hostPort',
+ ],
+ );
+
+ verifyNever(portForwarder.forward(devicePort));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+
+ testUsingContext('skips in ipv6 mode with a provided observatory port', () async {
+ testDeviceManager.addDevice(device);
+ final AttachCommand command = AttachCommand();
+
+ await createTestCommandRunner(command).run(
+ <String>[
+ 'attach',
+ '--debug-port',
+ '$devicePort',
+ '--observatory-port',
+ '$hostPort',
+ '--ipv6',
+ ],
+ );
+
+ verifyNever(portForwarder.forward(devicePort));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => testFileSystem,
+ });
+ });
testUsingContext('exits when no device connected', () async {
final AttachCommand command = AttachCommand();