Hide error on mDNS registration failure and print warning in flutter attach (#166782)

Publishing an mDNS service stopped working on macOS 15.4 for the iOS
Simulator. `DNSServiceRegister` always returns a callback with error
code `kDNSServiceErr_PolicyDenied`, as if the user had rejected the
permissions popup (except no popup is ever shown). mDNS is used to
discover the Dart VM when running `flutter attach`.

This PR does not display the error message when `DNSServiceRegister`
fails for iOS Simulators. Instead, it prints a warning if `flutter
attach` takes too long with instructions to either use `--debug-url` or
to run `flutter attach` before running the app.

If `DNSServiceRegister` works again in a future macOS update, mDNS will
continue to work the way it did previously.

Workaround for https://github.com/flutter/flutter/issues/166333.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Victoria Ashworth 2025-04-17 09:20:23 -05:00 committed by GitHub
parent fbed8a7194
commit ecabb1a052
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 148 additions and 6 deletions

View File

@ -76,8 +76,6 @@ FLUTTER_ASSERT_ARC
}
- (void)publishServiceProtocolPort:(NSURL*)url {
// TODO(vashworth): Remove once done debugging https://github.com/flutter/flutter/issues/129836
FML_LOG(INFO) << "Publish Service Protocol Port";
DNSServiceFlags flags = kDNSServiceFlagsDefault;
#if TARGET_IPHONE_SIMULATOR
// Simulator needs to use local loopback explicitly to work.
@ -126,10 +124,20 @@ static void DNSSD_API RegistrationCallback(DNSServiceRef sdRef,
if (errorCode == kDNSServiceErr_NoError) {
FML_DLOG(INFO) << "FlutterDartVMServicePublisher is ready!";
} else if (errorCode == kDNSServiceErr_PolicyDenied) {
// Local Network permissions on simulators stopped working in macOS 15.4 and will always return
// kDNSServiceErr_PolicyDenied. See
// https://github.com/flutter/flutter/issues/166333#issuecomment-2786720560.
#if TARGET_IPHONE_SIMULATOR
FML_DLOG(WARNING)
<< "Could not register as server for FlutterDartVMServicePublisher, permission "
<< "denied. Check your 'Local Network' permissions for this app in the Privacy section of "
<< "the system Settings.";
#else // TARGET_IPHONE_SIMULATOR
FML_LOG(ERROR)
<< "Could not register as server for FlutterDartVMServicePublisher, permission "
<< "denied. Check your 'Local Network' permissions for this app in the Privacy section of "
<< "the system Settings.";
#endif // TARGET_IPHONE_SIMULATOR
} else {
FML_LOG(ERROR) << "Could not register as server for FlutterDartVMServicePublisher. Check your "
"network settings and relaunch the application.";

View File

@ -867,8 +867,6 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: "
<< entrypoint.UTF8String;
} else {
// TODO(vashworth): Remove once done debugging https://github.com/flutter/flutter/issues/129836
FML_LOG(INFO) << "Enabled VM Service Publication: " << settings.enable_vm_service_publication;
[self setUpShell:std::move(shell)
withVMServicePublication:settings.enable_vm_service_publication];
if ([FlutterEngine isProfilerEnabled]) {

View File

@ -23,6 +23,7 @@ import '../device.dart';
import '../device_port_forwarder.dart';
import '../device_vm_service_discovery_for_attach.dart';
import '../ios/devices.dart';
import '../ios/simulators.dart';
import '../macos/macos_ipad_device.dart';
import '../mdns_discovery.dart';
import '../project.dart';
@ -316,8 +317,20 @@ known, it can be explicitly provided to attach via the command-line, e.g.
final Status discoveryStatus = _logger.startSpinner(
timeout: const Duration(seconds: 30),
slowWarningCallback: () {
// On iOS we rely on mDNS to find Dart VM Service. Remind the user to allow local network permissions on the device.
if (_isIOSDevice(device)) {
// On iOS we rely on mDNS to find Dart VM Service.
if (device is IOSSimulator) {
// mDNS on simulators stopped working in macOS 15.4.
// See https://github.com/flutter/flutter/issues/166333.
return 'The Dart VM Service was not discovered after 30 seconds. '
'This may be due to limited mDNS support in the iOS Simulator.\n\n'
'Click "Allow" to the prompt on your device asking if you would like to find and connect devices on your local network. '
'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n\n"
'If you do not receive a prompt, either run "flutter attach" before starting the '
'app or use the Dart VM service URL from the Xcode console with '
'"flutter attach --debug-url=<URL>".\n';
} else if (_isIOSDevice(device)) {
// Remind the user to allow local network permissions on the device.
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n\n'
'Click "Allow" to the prompt on your device asking if you would like to find and connect devices on your local network. '
'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
@ -326,6 +339,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
return 'The Dart VM Service was not discovered after 30 seconds. This is taking much longer than expected...\n';
},
warningColor: TerminalColor.cyan,
);
vmServiceUri = vmServiceDiscovery.uris;

View File

@ -4,6 +4,7 @@
import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/application_package.dart';
@ -25,6 +26,7 @@ import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/device_vm_service_discovery_for_attach.dart';
import 'package:flutter_tools/src/ios/application_package.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/simulators.dart';
import 'package:flutter_tools/src/macos/macos_ipad_device.dart';
import 'package:flutter_tools/src/mdns_discovery.dart';
import 'package:flutter_tools/src/project.dart';
@ -1528,6 +1530,52 @@ void main() {
DeviceManager: () => testDeviceManager,
},
);
group('prints warning when too slow', () {
late SlowWarningCallbackBufferLogger logger;
setUp(() {
logger = SlowWarningCallbackBufferLogger.test();
});
testUsingContext(
'to find on iOS Simulator',
() async {
final FakeIOSSimulator device = FakeIOSSimulator();
testDeviceManager.devices = <Device>[device];
FakeAsync().run((FakeAsync fakeAsync) {
createTestCommandRunner(
AttachCommand(
stdio: stdio,
logger: logger,
terminal: terminal,
signals: signals,
platform: platform,
processInfo: processInfo,
fileSystem: testFileSystem,
),
).run(<String>['attach']);
logger.expectedWarning =
'The Dart VM Service was not discovered after 30 seconds. '
'This may be due to limited mDNS support in the iOS Simulator.\n\n'
'Click "Allow" to the prompt on your device asking if you would like to find and connect devices on your local network. '
'If you selected "Don\'t Allow", you can turn it on in Settings > Your App Name > Local Network. '
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again.\n\n"
'If you do not receive a prompt, either run "flutter attach" before starting the '
'app or use the Dart VM service URL from the Xcode console with '
'"flutter attach --debug-url=<URL>".\n';
fakeAsync.elapse(const Duration(seconds: 30));
});
},
overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
Logger: () => logger,
DeviceManager: () => testDeviceManager,
},
);
});
});
}
@ -1966,6 +2014,62 @@ class FakeIOSDevice extends Fake implements IOSDevice {
}
}
class FakeIOSSimulator extends Fake implements IOSSimulator {
@override
final String name = 'name';
@override
String get displayName => name;
@override
bool isSupported() => true;
@override
bool isSupportedForProject(FlutterProject flutterProject) => true;
@override
bool get isConnected => true;
@override
DeviceConnectionInterface get connectionInterface => DeviceConnectionInterface.attached;
@override
bool get ephemeral => true;
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
@override
final PlatformType platformType = PlatformType.ios;
@override
bool get isWirelesslyConnected => false;
@override
DevicePortForwarder portForwarder = RecordingPortForwarder();
@override
VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
String? appId,
String? fuchsiaModule,
int? filterDevicePort,
int? expectedHostPort,
required bool ipv6,
required Logger logger,
}) {
final MdnsVMServiceDiscoveryForAttach mdnsVMServiceDiscoveryForAttach =
MdnsVMServiceDiscoveryForAttach(
device: this,
appId: appId,
deviceVmservicePort: filterDevicePort,
hostVmservicePort: expectedHostPort,
usesIpv6: ipv6,
useDeviceIPAsHost: isWirelesslyConnected,
);
return mdnsVMServiceDiscoveryForAttach;
}
}
class FakeMDnsClient extends Fake implements MDnsClient {
FakeMDnsClient(
this.ptrRecords,
@ -2054,3 +2158,21 @@ class FakeTerminal extends Fake implements AnsiTerminal {
@override
Stream<String> get keystrokes => StreamController<String>().stream;
}
class SlowWarningCallbackBufferLogger extends BufferLogger {
SlowWarningCallbackBufferLogger.test() : super.test();
String? expectedWarning;
@override
Status startSpinner({
VoidCallback? onFinish,
Duration? timeout,
SlowWarningCallback? slowWarningCallback,
TerminalColor? warningColor,
}) {
expect(slowWarningCallback, isNotNull);
expect(slowWarningCallback!(), expectedWarning);
return SilentStatus(stopwatch: Stopwatch(), onFinish: onFinish)..start();
}
}