Skip to content

Commit 9150b3f

Browse files
DaveShuckerowmehmetf
authored andcommitted
Add ipv6 and observatory port support to the attach command. (flutter#24537)
1 parent 2994d35 commit 9150b3f

File tree

2 files changed

+130
-20
lines changed

2 files changed

+130
-20
lines changed

packages/flutter_tools/lib/src/commands/attach.dart

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ class AttachCommand extends FlutterCommand {
5353
argParser
5454
..addOption(
5555
'debug-port',
56+
help: 'Device port where the observatory is listening.',
57+
)..addOption(
58+
'observatory-port',
5659
help: 'Local port where the observatory is listening.',
5760
)..addOption('pid-file',
5861
help: 'Specify a file to write the process id to. '
@@ -67,6 +70,12 @@ class AttachCommand extends FlutterCommand {
6770
negatable: false,
6871
help: 'Handle machine structured JSON command input and provide output '
6972
'and progress in machine friendly format.',
73+
)..addFlag('ipv6',
74+
hide: true,
75+
negatable: false,
76+
help: 'Binds to IPv6 localhost instead of IPv4 when the flutter tool '
77+
'forwards the host port to a device port. Not used when the '
78+
'--debug-port flag is not set.',
7079
);
7180
hotRunnerFactory ??= HotRunnerFactory();
7281
}
@@ -79,6 +88,14 @@ class AttachCommand extends FlutterCommand {
7988
@override
8089
final String description = 'Attach to a running application.';
8190

91+
// TODO(djshuckerow): this is now a confusing name. An explanation:
92+
// The --observatory-port flag passed to `flutter run` is used to
93+
// set up the port on the development macine that the Dart observatory
94+
// listens to. This flag serves the same purpose in this command.
95+
//
96+
// The --debug-port flag passed only to `flutter attach` is used to
97+
// set up the port on the device running a Flutter app to connect back
98+
// to the host development machine.
8299
int get observatoryPort {
83100
if (argResults['debug-port'] == null)
84101
return null;
@@ -96,6 +113,12 @@ class AttachCommand extends FlutterCommand {
96113
if (await findTargetDevice() == null)
97114
throwToolExit(null);
98115
observatoryPort;
116+
if (observatoryPort == null && argResults.wasParsed('ipv6')) {
117+
throwToolExit(
118+
'When the --debug-port is unknown, this command determines '
119+
'the value of --ipv6 on its own.',
120+
);
121+
}
99122
}
100123

101124
@override
@@ -163,14 +186,24 @@ class AttachCommand extends FlutterCommand {
163186
);
164187
printStatus('Waiting for a connection from Flutter on ${device.name}...');
165188
observatoryUri = await observatoryDiscovery.uri;
189+
// Determine ipv6 status from the scanned logs.
190+
ipv6 = observatoryDiscovery.ipv6;
166191
printStatus('Done.');
167192
} finally {
168193
await observatoryDiscovery?.cancel();
169194
}
170195
}
171196
} else {
172-
final int localPort = await device.portForwarder.forward(devicePort);
173-
observatoryUri = Uri.parse('http://$ipv4Loopback:$localPort/');
197+
ipv6 = argResults['ipv6'];
198+
// int.tryParse will throw if it is passed null, so we need to do this.
199+
final int argObservatoryPort = argResults['observatory-port'] == null
200+
? null
201+
: int.tryParse(argResults['observatory-port']);
202+
final int localPort = argObservatoryPort
203+
?? await device.portForwarder.forward(devicePort);
204+
observatoryUri = ipv6
205+
? Uri.parse('http://[$ipv6Loopback]:$localPort/')
206+
: Uri.parse('http://$ipv4Loopback:$localPort/');
174207
}
175208
try {
176209
final FlutterDevice flutterDevice = FlutterDevice(

packages/flutter_tools/test/commands/attach_test.dart

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ void main() {
104104
debuggingOptions: anyNamed('debuggingOptions'),
105105
packagesFilePath: anyNamed('packagesFilePath'),
106106
usesTerminalUI: anyNamed('usesTerminalUI'),
107+
ipv6: false,
107108
),
108109
)..thenReturn(MockHotRunner());
109110

@@ -134,6 +135,7 @@ void main() {
134135
debuggingOptions: anyNamed('debuggingOptions'),
135136
packagesFilePath: anyNamed('packagesFilePath'),
136137
usesTerminalUI: anyNamed('usesTerminalUI'),
138+
ipv6: false,
137139
),
138140
)..called(1);
139141

@@ -150,6 +152,21 @@ void main() {
150152
}, overrides: <Type, Generator>{
151153
FileSystem: () => testFileSystem,
152154
});
155+
156+
testUsingContext('exits when ipv6 is specified and debug-port is not', () async {
157+
testDeviceManager.addDevice(device);
158+
159+
final AttachCommand command = AttachCommand();
160+
await expectLater(
161+
createTestCommandRunner(command).run(<String>['attach', '--ipv6']),
162+
throwsToolExit(
163+
message: 'When the --debug-port is unknown, this command determines '
164+
'the value of --ipv6 on its own.',
165+
),
166+
);
167+
}, overrides: <Type, Generator>{
168+
FileSystem: () => testFileSystem,
169+
},);
153170
});
154171

155172

@@ -170,7 +187,8 @@ void main() {
170187
target: anyNamed('target'),
171188
debuggingOptions: anyNamed('debuggingOptions'),
172189
packagesFilePath: anyNamed('packagesFilePath'),
173-
usesTerminalUI: anyNamed('usesTerminalUI'))).thenReturn(
190+
usesTerminalUI: anyNamed('usesTerminalUI'),
191+
ipv6: false)).thenReturn(
174192
MockHotRunner());
175193

176194
testDeviceManager.addDevice(device);
@@ -199,33 +217,92 @@ void main() {
199217
target: foo.path,
200218
debuggingOptions: anyNamed('debuggingOptions'),
201219
packagesFilePath: anyNamed('packagesFilePath'),
202-
usesTerminalUI: anyNamed('usesTerminalUI'))).called(1);
220+
usesTerminalUI: anyNamed('usesTerminalUI'),
221+
ipv6: false)).called(1);
203222
}, overrides: <Type, Generator>{
204223
FileSystem: () => testFileSystem,
205224
},);
206225

207-
testUsingContext('forwards to given port', () async {
226+
group('forwarding to given port', () {
208227
const int devicePort = 499;
209228
const int hostPort = 42;
210-
final MockPortForwarder portForwarder = MockPortForwarder();
211-
final MockAndroidDevice device = MockAndroidDevice();
229+
MockPortForwarder portForwarder;
230+
MockAndroidDevice device;
212231

213-
when(device.portForwarder).thenReturn(portForwarder);
214-
when(portForwarder.forward(devicePort)).thenAnswer((_) async => hostPort);
215-
when(portForwarder.forwardedPorts).thenReturn(
216-
<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
217-
when(portForwarder.unforward(any)).thenAnswer((_) async => null);
218-
testDeviceManager.addDevice(device);
232+
setUp(() {
233+
portForwarder = MockPortForwarder();
234+
device = MockAndroidDevice();
219235

220-
final AttachCommand command = AttachCommand();
236+
when(device.portForwarder).thenReturn(portForwarder);
237+
when(portForwarder.forward(devicePort)).thenAnswer((_) async => hostPort);
238+
when(portForwarder.forwardedPorts).thenReturn(
239+
<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
240+
when(portForwarder.unforward(any)).thenAnswer((_) async => null);
241+
});
221242

222-
await createTestCommandRunner(command).run(
223-
<String>['attach', '--debug-port', '$devicePort']);
243+
testUsingContext('succeeds in ipv4 mode', () async {
244+
testDeviceManager.addDevice(device);
245+
final AttachCommand command = AttachCommand();
224246

225-
verify(portForwarder.forward(devicePort)).called(1);
226-
}, overrides: <Type, Generator>{
227-
FileSystem: () => testFileSystem,
228-
},);
247+
await createTestCommandRunner(command).run(
248+
<String>['attach', '--debug-port', '$devicePort']);
249+
250+
verify(portForwarder.forward(devicePort)).called(1);
251+
}, overrides: <Type, Generator>{
252+
FileSystem: () => testFileSystem,
253+
});
254+
255+
testUsingContext('succeeds in ipv6 mode', () async {
256+
testDeviceManager.addDevice(device);
257+
final AttachCommand command = AttachCommand();
258+
259+
await createTestCommandRunner(command).run(
260+
<String>['attach', '--debug-port', '$devicePort', '--ipv6']);
261+
262+
verify(portForwarder.forward(devicePort)).called(1);
263+
}, overrides: <Type, Generator>{
264+
FileSystem: () => testFileSystem,
265+
});
266+
267+
testUsingContext('skips in ipv4 mode with a provided observatory port', () async {
268+
testDeviceManager.addDevice(device);
269+
final AttachCommand command = AttachCommand();
270+
271+
await createTestCommandRunner(command).run(
272+
<String>[
273+
'attach',
274+
'--debug-port',
275+
'$devicePort',
276+
'--observatory-port',
277+
'$hostPort',
278+
],
279+
);
280+
281+
verifyNever(portForwarder.forward(devicePort));
282+
}, overrides: <Type, Generator>{
283+
FileSystem: () => testFileSystem,
284+
});
285+
286+
testUsingContext('skips in ipv6 mode with a provided observatory port', () async {
287+
testDeviceManager.addDevice(device);
288+
final AttachCommand command = AttachCommand();
289+
290+
await createTestCommandRunner(command).run(
291+
<String>[
292+
'attach',
293+
'--debug-port',
294+
'$devicePort',
295+
'--observatory-port',
296+
'$hostPort',
297+
'--ipv6',
298+
],
299+
);
300+
301+
verifyNever(portForwarder.forward(devicePort));
302+
}, overrides: <Type, Generator>{
303+
FileSystem: () => testFileSystem,
304+
});
305+
});
229306

230307
testUsingContext('exits when no device connected', () async {
231308
final AttachCommand command = AttachCommand();

0 commit comments

Comments
 (0)