Skip to content

Commit 1ab3efb

Browse files
chingjungilnobrega
authored andcommitted
Support mdns when attaching to proxied devices. (flutter#146021)
Also move the vm service discovery logic into platform-specific implementation of `Device`s. This is to avoid having platform-specific code in attach.dart.
1 parent 83b2ab2 commit 1ab3efb

13 files changed

Lines changed: 897 additions & 121 deletions

File tree

packages/flutter_tools/lib/src/android/android_device.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import '../build_info.dart';
1818
import '../convert.dart';
1919
import '../device.dart';
2020
import '../device_port_forwarder.dart';
21+
import '../device_vm_service_discovery_for_attach.dart';
2122
import '../project.dart';
2223
import '../protocol_discovery.dart';
2324
import 'android.dart';
@@ -800,6 +801,26 @@ class AndroidDevice extends Device {
800801
}
801802
}
802803

804+
@override
805+
VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
806+
String? appId,
807+
String? fuchsiaModule,
808+
int? filterDevicePort,
809+
int? expectedHostPort,
810+
required bool ipv6,
811+
required Logger logger,
812+
}) =>
813+
LogScanningVMServiceDiscoveryForAttach(
814+
// If it's an Android device, attaching relies on past log searching
815+
// to find the service protocol.
816+
Future<DeviceLogReader>.value(getLogReader(includePastLogs: true)),
817+
portForwarder: portForwarder,
818+
ipv6: ipv6,
819+
devicePort: filterDevicePort,
820+
hostPort: expectedHostPort,
821+
logger: logger,
822+
);
823+
803824
@override
804825
late final DevicePortForwarder? portForwarder = () {
805826
final String? adbPath = _androidSdk.adbPath;

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

Lines changed: 33 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,11 @@ import '../compile.dart';
2121
import '../daemon.dart';
2222
import '../device.dart';
2323
import '../device_port_forwarder.dart';
24-
import '../fuchsia/fuchsia_device.dart';
24+
import '../device_vm_service_discovery_for_attach.dart';
2525
import '../ios/devices.dart';
26-
import '../ios/simulators.dart';
2726
import '../macos/macos_ipad_device.dart';
2827
import '../mdns_discovery.dart';
2928
import '../project.dart';
30-
import '../protocol_discovery.dart';
3129
import '../resident_runner.dart';
3230
import '../run_cold.dart';
3331
import '../run_hot.dart';
@@ -286,116 +284,48 @@ known, it can be explicitly provided to attach via the command-line, e.g.
286284
: null;
287285

288286
Stream<Uri>? vmServiceUri;
289-
bool usesIpv6 = ipv6!;
287+
final bool usesIpv6 = ipv6!;
290288
final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
291289
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
292290
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
293291
final bool isWirelessIOSDevice = (device is IOSDevice) && device.isWirelesslyConnected;
294292

295293
if ((debugPort == null && debugUri == null) || isWirelessIOSDevice) {
296-
if (device is FuchsiaDevice) {
297-
final String? module = stringArg('module');
298-
if (module == null) {
299-
throwToolExit("'--module' is required for attaching to a Fuchsia device");
300-
}
301-
usesIpv6 = device.ipv6;
302-
FuchsiaIsolateDiscoveryProtocol? isolateDiscoveryProtocol;
303-
try {
304-
isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
305-
vmServiceUri = Stream<Uri>.value(await isolateDiscoveryProtocol.uri).asBroadcastStream();
306-
} on Exception {
307-
isolateDiscoveryProtocol?.dispose();
308-
final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
309-
for (final ForwardedPort port in ports) {
310-
await device.portForwarder.unforward(port);
294+
// The device port we expect to have the debug port be listening
295+
final int? devicePort = debugPort ?? debugUri?.port ?? deviceVmservicePort;
296+
297+
final VMServiceDiscoveryForAttach vmServiceDiscovery = device.getVMServiceDiscoveryForAttach(
298+
appId: appId,
299+
fuchsiaModule: stringArg('module'),
300+
filterDevicePort: devicePort,
301+
expectedHostPort: hostVmservicePort,
302+
ipv6: usesIpv6,
303+
logger: _logger,
304+
);
305+
306+
_logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
307+
final Status discoveryStatus = _logger.startSpinner(
308+
timeout: const Duration(seconds: 30),
309+
slowWarningCallback: () {
310+
// On iOS we rely on mDNS to find Dart VM Service. Remind the user to allow local network permissions on the device.
311+
if (_isIOSDevice(device)) {
312+
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
313+
'Click "Allow" to the prompt on your device asking if you would like to find and connect devices on your local network. '
314+
'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
315+
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n";
311316
}
312-
rethrow;
313-
}
314-
} else if (_isIOSDevice(device)) {
315-
// Protocol Discovery relies on logging. On iOS earlier than 13, logging is gathered using syslog.
316-
// syslog is not available for iOS 13+. For iOS 13+, Protocol Discovery gathers logs from the VMService.
317-
// Since we don't have access to the VMService yet, Protocol Discovery cannot be used for iOS 13+.
318-
// Also, wireless devices must be found using mDNS and cannot use Protocol Discovery.
319-
final bool compatibleWithProtocolDiscovery = (device is IOSDevice) &&
320-
device.majorSdkVersion < IOSDeviceLogReader.minimumUniversalLoggingSdkVersion &&
321-
!isWirelessIOSDevice;
322-
323-
_logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
324-
final Status discoveryStatus = _logger.startSpinner(
325-
timeout: const Duration(seconds: 30),
326-
slowWarningCallback: () {
327-
// If relying on mDNS to find Dart VM Service, remind the user to allow local network permissions.
328-
if (!compatibleWithProtocolDiscovery) {
329-
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
330-
'Click "Allow" to the prompt asking if you would like to find and connect devices on your local network. '
331-
'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
332-
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n";
333-
}
334-
335-
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
336-
},
337-
);
338317

339-
int? devicePort;
340-
if (debugPort != null) {
341-
devicePort = debugPort;
342-
} else if (debugUri != null) {
343-
devicePort = debugUri?.port;
344-
} else if (deviceVmservicePort != null) {
345-
devicePort = deviceVmservicePort;
346-
}
318+
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
319+
},
320+
);
347321

348-
final Future<Uri?> mDNSDiscoveryFuture = MDnsVmServiceDiscovery.instance!.getVMServiceUriForAttach(
349-
appId,
350-
device,
351-
usesIpv6: usesIpv6,
352-
useDeviceIPAsHost: isWirelessIOSDevice,
353-
deviceVmservicePort: devicePort,
354-
);
322+
vmServiceUri = vmServiceDiscovery.uris;
355323

356-
Future<Uri?>? protocolDiscoveryFuture;
357-
if (compatibleWithProtocolDiscovery) {
358-
final ProtocolDiscovery vmServiceDiscovery = ProtocolDiscovery.vmService(
359-
device.getLogReader(),
360-
portForwarder: device.portForwarder,
361-
ipv6: ipv6!,
362-
devicePort: devicePort,
363-
hostPort: hostVmservicePort,
364-
logger: _logger,
365-
);
366-
protocolDiscoveryFuture = vmServiceDiscovery.uri;
367-
}
368-
369-
final Uri? foundUrl;
370-
if (protocolDiscoveryFuture == null) {
371-
foundUrl = await mDNSDiscoveryFuture;
372-
} else {
373-
foundUrl = await Future.any(
374-
<Future<Uri?>>[mDNSDiscoveryFuture, protocolDiscoveryFuture]
375-
);
376-
}
324+
// Stop the timer once we receive the first uri.
325+
vmServiceUri = vmServiceUri.map((Uri uri) {
377326
discoveryStatus.stop();
378-
379-
vmServiceUri = foundUrl == null
380-
? null
381-
: Stream<Uri>.value(foundUrl).asBroadcastStream();
382-
}
383-
// If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
384-
if (vmServiceUri == null) {
385-
final ProtocolDiscovery vmServiceDiscovery =
386-
ProtocolDiscovery.vmService(
387-
// If it's an Android device, attaching relies on past log searching
388-
// to find the service protocol.
389-
await device.getLogReader(includePastLogs: device is AndroidDevice),
390-
portForwarder: device.portForwarder,
391-
ipv6: ipv6!,
392-
devicePort: deviceVmservicePort,
393-
hostPort: hostVmservicePort,
394-
logger: _logger,
395-
);
396-
_logger.printStatus('Waiting for a connection from Flutter on ${device.name}...');
397-
vmServiceUri = vmServiceDiscovery.uris;
398-
}
327+
return uri;
328+
});
399329
} else {
400330
vmServiceUri = Stream<Uri>
401331
.fromFuture(
@@ -559,8 +489,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
559489
Future<void> _validateArguments() async { }
560490

561491
bool _isIOSDevice(Device device) {
562-
return (device is IOSDevice) ||
563-
(device is IOSSimulator) ||
492+
return (device.platformType == PlatformType.ios) ||
564493
(device is MacOSDesignedForIPadDevice);
565494
}
566495
}

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import '../convert.dart';
2121
import '../daemon.dart';
2222
import '../device.dart';
2323
import '../device_port_forwarder.dart';
24+
import '../device_vm_service_discovery_for_attach.dart';
2425
import '../emulator.dart';
2526
import '../features.dart';
2627
import '../globals.dart' as globals;
@@ -1009,6 +1010,8 @@ class DeviceDomain extends Domain {
10091010
registerHandler('shutdownDartDevelopmentService', shutdownDartDevelopmentService);
10101011
registerHandler('setExternalDevToolsUriForDartDevelopmentService', setExternalDevToolsUriForDartDevelopmentService);
10111012
registerHandler('getDiagnostics', getDiagnostics);
1013+
registerHandler('startVMServiceDiscoveryForAttach', startVMServiceDiscoveryForAttach);
1014+
registerHandler('stopVMServiceDiscoveryForAttach', stopVMServiceDiscoveryForAttach);
10121015

10131016
// Use the device manager discovery so that client provided device types
10141017
// are usable via the daemon protocol.
@@ -1325,6 +1328,41 @@ class DeviceDomain extends Domain {
13251328
...diagnostics,
13261329
];
13271330
}
1331+
1332+
final Map<String, StreamSubscription<Uri>> _vmServiceDiscoverySubscriptions = <String, StreamSubscription<Uri>>{};
1333+
1334+
Future<String> startVMServiceDiscoveryForAttach(Map<String, Object?> args) async {
1335+
final String? deviceId = _getStringArg(args, 'deviceId', required: true);
1336+
final String? appId = _getStringArg(args, 'appId');
1337+
final String? fuchsiaModule = _getStringArg(args, 'fuchsiaModule');
1338+
final int? filterDevicePort = _getIntArg(args, 'filterDevicePort');
1339+
final bool? ipv6 = _getBoolArg(args, 'ipv6');
1340+
1341+
final Device? device = await daemon.deviceDomain._getDevice(deviceId);
1342+
if (device == null) {
1343+
throw DaemonException("device '$deviceId' not found");
1344+
}
1345+
1346+
final String id = '${_id++}';
1347+
1348+
final VMServiceDiscoveryForAttach discovery = device.getVMServiceDiscoveryForAttach(
1349+
appId: appId,
1350+
fuchsiaModule: fuchsiaModule,
1351+
filterDevicePort: filterDevicePort,
1352+
ipv6: ipv6 ?? false,
1353+
logger: globals.logger
1354+
);
1355+
_vmServiceDiscoverySubscriptions[id] = discovery.uris.listen(
1356+
(Uri uri) => sendEvent('device.VMServiceDiscoveryForAttach.$id', uri.toString()),
1357+
);
1358+
1359+
return id;
1360+
}
1361+
1362+
Future<void> stopVMServiceDiscoveryForAttach(Map<String, Object?> args) async {
1363+
final String? id = _getStringArg(args, 'id', required: true);
1364+
await _vmServiceDiscoverySubscriptions.remove(id)?.cancel();
1365+
}
13281366
}
13291367

13301368
class DevToolsDomain extends Domain {

packages/flutter_tools/lib/src/device.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'base/utils.dart';
1616
import 'build_info.dart';
1717
import 'devfs.dart';
1818
import 'device_port_forwarder.dart';
19+
import 'device_vm_service_discovery_for_attach.dart';
1920
import 'project.dart';
2021
import 'vmservice.dart';
2122
import 'web/compile.dart';
@@ -737,6 +738,35 @@ abstract class Device {
737738
/// Clear the device's logs.
738739
void clearLogs();
739740

741+
/// Get the [VMServiceDiscoveryForAttach] instance for this device, which
742+
/// discovers, and forwards any necessary ports to the vm service uri of a
743+
/// running app on the device.
744+
///
745+
/// If `appId` is specified, on supported platforms, the service discovery
746+
/// will only return the VM service URI from the given app.
747+
///
748+
/// If `fuchsiaModule` is specified, this will only return the VM service uri
749+
/// from the specified Fuchsia module.
750+
///
751+
/// If `filterDevicePort` is specified, this will only return the VM service
752+
/// uri that matches the given port on the device.
753+
VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
754+
String? appId,
755+
String? fuchsiaModule,
756+
int? filterDevicePort,
757+
int? expectedHostPort,
758+
required bool ipv6,
759+
required Logger logger,
760+
}) =>
761+
LogScanningVMServiceDiscoveryForAttach(
762+
Future<DeviceLogReader>.value(getLogReader()),
763+
portForwarder: portForwarder,
764+
devicePort: filterDevicePort,
765+
hostPort: expectedHostPort,
766+
ipv6: ipv6,
767+
logger: logger,
768+
);
769+
740770
/// Start an app package on the current device.
741771
///
742772
/// [platformArgs] allows callers to pass platform-specific arguments to the

0 commit comments

Comments
 (0)