Skip to content

Commit 0b24a5a

Browse files
Implement mdns for flutter run (flutter#40447)
1 parent 431b82f commit 0b24a5a

File tree

7 files changed

+408
-298
lines changed

7 files changed

+408
-298
lines changed

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

Lines changed: 14 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
import 'dart:async';
66

7-
import 'package:multicast_dns/multicast_dns.dart';
8-
97
import '../artifacts.dart';
108
import '../base/common.dart';
119
import '../base/context.dart';
@@ -21,6 +19,7 @@ import '../fuchsia/fuchsia_device.dart';
2119
import '../globals.dart';
2220
import '../ios/devices.dart';
2321
import '../ios/simulators.dart';
22+
import '../mdns_discovery.dart';
2423
import '../project.dart';
2524
import '../protocol_discovery.dart';
2625
import '../resident_runner.dart';
@@ -207,7 +206,7 @@ class AttachCommand extends FlutterCommand {
207206
final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
208207

209208
bool attachLogger = false;
210-
if (devicePort == null && debugUri == null) {
209+
if (devicePort == null && debugUri == null) {
211210
if (device is FuchsiaDevice) {
212211
attachLogger = true;
213212
final String module = argResults['module'];
@@ -229,10 +228,11 @@ class AttachCommand extends FlutterCommand {
229228
rethrow;
230229
}
231230
} else if ((device is IOSDevice) || (device is IOSSimulator)) {
232-
final MDnsObservatoryDiscoveryResult result = await MDnsObservatoryDiscovery().query(applicationId: appId);
233-
if (result != null) {
234-
observatoryUri = await _buildObservatoryUri(device, hostname, result.port, result.authCode);
235-
}
231+
observatoryUri = await MDnsObservatoryDiscovery.instance.getObservatoryUri(
232+
appId,
233+
device,
234+
usesIpv6,
235+
);
236236
}
237237
// If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
238238
if (observatoryUri == null) {
@@ -254,8 +254,13 @@ class AttachCommand extends FlutterCommand {
254254
}
255255
}
256256
} else {
257-
observatoryUri = await _buildObservatoryUri(device,
258-
debugUri?.host ?? hostname, devicePort ?? debugUri.port, debugUri?.path);
257+
observatoryUri = await buildObservatoryUri(
258+
device,
259+
debugUri?.host ?? hostname,
260+
devicePort ?? debugUri.port,
261+
observatoryPort,
262+
debugUri?.path,
263+
);
259264
}
260265
try {
261266
final bool useHot = getBuildInfo().isDebug;
@@ -337,22 +342,6 @@ class AttachCommand extends FlutterCommand {
337342
}
338343

339344
Future<void> _validateArguments() async { }
340-
341-
Future<Uri> _buildObservatoryUri(Device device,
342-
String host, int devicePort, [String authCode]) async {
343-
String path = '/';
344-
if (authCode != null) {
345-
path = authCode;
346-
}
347-
// Not having a trailing slash can cause problems in some situations.
348-
// Ensure that there's one present.
349-
if (!path.endsWith('/')) {
350-
path += '/';
351-
}
352-
final int localPort = observatoryPort
353-
?? await device.portForwarder.forward(devicePort);
354-
return Uri(scheme: 'http', host: host, port: localPort, path: path);
355-
}
356345
}
357346

358347
class HotRunnerFactory {
@@ -383,132 +372,3 @@ class HotRunnerFactory {
383372
ipv6: ipv6,
384373
);
385374
}
386-
387-
class MDnsObservatoryDiscoveryResult {
388-
MDnsObservatoryDiscoveryResult(this.port, this.authCode);
389-
final int port;
390-
final String authCode;
391-
}
392-
393-
/// A wrapper around [MDnsClient] to find a Dart observatory instance.
394-
class MDnsObservatoryDiscovery {
395-
/// Creates a new [MDnsObservatoryDiscovery] object.
396-
///
397-
/// The [client] parameter will be defaulted to a new [MDnsClient] if null.
398-
/// The [applicationId] parameter may be null, and can be used to
399-
/// automatically select which application to use if multiple are advertising
400-
/// Dart observatory ports.
401-
MDnsObservatoryDiscovery({MDnsClient mdnsClient})
402-
: client = mdnsClient ?? MDnsClient();
403-
404-
/// The [MDnsClient] used to do a lookup.
405-
final MDnsClient client;
406-
407-
static const String dartObservatoryName = '_dartobservatory._tcp.local';
408-
409-
/// Executes an mDNS query for a Dart Observatory.
410-
///
411-
/// The [applicationId] parameter may be used to specify which application
412-
/// to find. For Android, it refers to the package name; on iOS, it refers to
413-
/// the bundle ID.
414-
///
415-
/// If it is not null, this method will find the port and authentication code
416-
/// of the Dart Observatory for that application. If it cannot find a Dart
417-
/// Observatory matching that application identifier, it will call
418-
/// [throwToolExit].
419-
///
420-
/// If it is null and there are multiple ports available, the user will be
421-
/// prompted with a list of available observatory ports and asked to select
422-
/// one.
423-
///
424-
/// If it is null and there is only one available instance of Observatory,
425-
/// it will return that instance's information regardless of what application
426-
/// the Observatory instance is for.
427-
Future<MDnsObservatoryDiscoveryResult> query({String applicationId}) async {
428-
printStatus('Checking for advertised Dart observatories...');
429-
try {
430-
await client.start();
431-
final List<PtrResourceRecord> pointerRecords = await client
432-
.lookup<PtrResourceRecord>(
433-
ResourceRecordQuery.serverPointer(dartObservatoryName),
434-
)
435-
.toList();
436-
if (pointerRecords.isEmpty) {
437-
return null;
438-
}
439-
// We have no guarantee that we won't get multiple hits from the same
440-
// service on this.
441-
final List<String> uniqueDomainNames = pointerRecords
442-
.map<String>((PtrResourceRecord record) => record.domainName)
443-
.toSet()
444-
.toList();
445-
446-
String domainName;
447-
if (applicationId != null) {
448-
for (String name in uniqueDomainNames) {
449-
if (name.toLowerCase().startsWith(applicationId.toLowerCase())) {
450-
domainName = name;
451-
break;
452-
}
453-
}
454-
if (domainName == null) {
455-
throwToolExit('Did not find a observatory port advertised for $applicationId.');
456-
}
457-
} else if (uniqueDomainNames.length > 1) {
458-
final StringBuffer buffer = StringBuffer();
459-
buffer.writeln('There are multiple observatory ports available.');
460-
buffer.writeln('Rerun this command with one of the following passed in as the appId:');
461-
buffer.writeln('');
462-
for (final String uniqueDomainName in uniqueDomainNames) {
463-
buffer.writeln(' flutter attach --app-id ${uniqueDomainName.replaceAll('.$dartObservatoryName', '')}');
464-
}
465-
throwToolExit(buffer.toString());
466-
} else {
467-
domainName = pointerRecords[0].domainName;
468-
}
469-
printStatus('Checking for available port on $domainName');
470-
// Here, if we get more than one, it should just be a duplicate.
471-
final List<SrvResourceRecord> srv = await client
472-
.lookup<SrvResourceRecord>(
473-
ResourceRecordQuery.service(domainName),
474-
)
475-
.toList();
476-
if (srv.isEmpty) {
477-
return null;
478-
}
479-
if (srv.length > 1) {
480-
printError('Unexpectedly found more than one observatory report for $domainName '
481-
'- using first one (${srv.first.port}).');
482-
}
483-
printStatus('Checking for authentication code for $domainName');
484-
final List<TxtResourceRecord> txt = await client
485-
.lookup<TxtResourceRecord>(
486-
ResourceRecordQuery.text(domainName),
487-
)
488-
?.toList();
489-
if (txt == null || txt.isEmpty) {
490-
return MDnsObservatoryDiscoveryResult(srv.first.port, '');
491-
}
492-
String authCode = '';
493-
const String authCodePrefix = 'authCode=';
494-
String raw = txt.first.text;
495-
// TXT has a format of [<length byte>, text], so if the length is 2,
496-
// that means that TXT is empty.
497-
if (raw.length > 2) {
498-
// Remove length byte from raw txt.
499-
raw = raw.substring(1);
500-
if (raw.startsWith(authCodePrefix)) {
501-
authCode = raw.substring(authCodePrefix.length);
502-
// The Observatory currently expects a trailing '/' as part of the
503-
// URI, otherwise an invalid authentication code response is given.
504-
if (!authCode.endsWith('/')) {
505-
authCode += '/';
506-
}
507-
}
508-
}
509-
return MDnsObservatoryDiscoveryResult(srv.first.port, authCode);
510-
} finally {
511-
client.stop();
512-
}
513-
}
514-
}

packages/flutter_tools/lib/src/context_runner.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import 'macos/cocoapods_validator.dart';
4545
import 'macos/macos_workflow.dart';
4646
import 'macos/xcode.dart';
4747
import 'macos/xcode_validator.dart';
48+
import 'mdns_discovery.dart';
4849
import 'reporting/reporting.dart';
4950
import 'run_hot.dart';
5051
import 'version.dart';
@@ -101,6 +102,7 @@ Future<T> runInContext<T>(
101102
LinuxWorkflow: () => const LinuxWorkflow(),
102103
Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(),
103104
MacOSWorkflow: () => const MacOSWorkflow(),
105+
MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(),
104106
OperatingSystemUtils: () => OperatingSystemUtils(),
105107
ProcessUtils: () => ProcessUtils(),
106108
SimControl: () => SimControl(),

packages/flutter_tools/lib/src/ios/devices.dart

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import '../build_info.dart';
1919
import '../convert.dart';
2020
import '../device.dart';
2121
import '../globals.dart';
22+
import '../mdns_discovery.dart';
2223
import '../project.dart';
2324
import '../protocol_discovery.dart';
2425
import '../reporting/reporting.dart';
@@ -345,7 +346,6 @@ class IOSDevice extends Device {
345346
ipv6: ipv6,
346347
);
347348
}
348-
349349
final int installationResult = await IOSDeploy.instance.runApp(
350350
deviceId: id,
351351
bundlePath: bundle.path,
@@ -363,16 +363,36 @@ class IOSDevice extends Device {
363363
return LaunchResult.succeeded();
364364
}
365365

366+
Uri localUri;
366367
try {
367368
printTrace('Application launched on the device. Waiting for observatory port.');
368-
final Uri localUri = await observatoryDiscovery.uri;
369-
return LaunchResult.succeeded(observatoryUri: localUri);
369+
localUri = await MDnsObservatoryDiscovery.instance.getObservatoryUri(
370+
package.id,
371+
this,
372+
ipv6,
373+
debuggingOptions.observatoryPort,
374+
);
375+
if (localUri != null) {
376+
return LaunchResult.succeeded(observatoryUri: localUri);
377+
}
378+
} catch (error) {
379+
printError('Failed to establish a debug connection with $id: $error');
380+
}
381+
382+
// Fallback to manual protocol discovery
383+
printTrace('mDNS lookup failed, attempting fallback to reading device log.');
384+
try {
385+
printTrace('Waiting for observatory port.');
386+
localUri = await observatoryDiscovery.uri;
387+
if (localUri != null) {
388+
return LaunchResult.succeeded(observatoryUri: localUri);
389+
}
370390
} catch (error) {
371391
printError('Failed to establish a debug connection with $id: $error');
372-
return LaunchResult.failed();
373392
} finally {
374393
await observatoryDiscovery?.cancel();
375394
}
395+
return LaunchResult.failed();
376396
} finally {
377397
installStatus.stop();
378398
}

0 commit comments

Comments
 (0)