[CP-stable] Prepare and warn when missing iOS Device Support Symbols - #192301
[CP-stable] Prepare and warn when missing iOS Device Support Symbols#192301vashworth wants to merge 2 commits into
Conversation
When debugging for iOS devices, Device Support symbols are required to be copied from the iOS device to the host. Previously the only way to trigger this was to open Xcode. We recently discovered there's now a command that automates this. This PR adds a command that calls `xcodebuild -prepareDeviceSupport` before installing/launching the app so that Device Support symbols are verified to be installed. Toward flutter#189284. - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [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]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [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 PR explicitly sets the iOS Device Support symbols in LLDB if we're able to identify them. If LLDB takes too long or receives a `warning: libobjc.A.dylib is being read from process memory.` log, we print out a message indicating that symbols may be missing and that you need to open Xcode to trigger it to copy them. Fixes flutter#189284. - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [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]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [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
There was a problem hiding this comment.
Code Review
This pull request introduces the IOSDeviceSupport class to manage and prepare iOS device support symbols using xcodebuild -prepareDeviceSupport for Xcode 16.3+, integrating it into the IOSDevice and LLDB launch and debugging flows. Feedback on the changes suggests several robustness improvements, including catching all exceptions during symbol preparation to prevent tool crashes, using a LineSplitter to robustly parse stream output, ensuring the entire device directory is cleared when removing cached symbols, correcting a version typo in the comments, and adding a timeout to the LLDB platform status check to prevent potential hangs.
| } on ProcessException catch (exception, stackTrace) { | ||
| _logger.printError( | ||
| 'Process exception running "xcodebuild -prepareDeviceSupport": $exception', | ||
| ); | ||
| _logger.printTrace('$stackTrace'); | ||
| } |
There was a problem hiding this comment.
Since prepareDeviceSupport() is called during the critical application startup path (startApp), any uncaught exception will crash the entire flutter run command. Catching only ProcessException is risky because other types of exceptions (such as ArgumentError, FileSystemException, or StateError) could be thrown during process startup or stream processing. Catching all exceptions (catch (exception, stackTrace)) ensures that any unexpected error is gracefully logged to trace/error without blocking the user from running their app.
| } on ProcessException catch (exception, stackTrace) { | |
| _logger.printError( | |
| 'Process exception running "xcodebuild -prepareDeviceSupport": $exception', | |
| ); | |
| _logger.printTrace('$stackTrace'); | |
| } | |
| } catch (exception, stackTrace) { | |
| _logger.printError( | |
| 'Error running "xcodebuild -prepareDeviceSupport": $exception', | |
| ); | |
| _logger.printTrace('$stackTrace'); | |
| } |
| final StreamSubscription<String> stdoutSubscription = process.stdout | ||
| .transform(utf8.decoder) | ||
| .listen((String text) { | ||
| if (text.contains('Copying')) { | ||
| printToTrace = false; | ||
| _logger.printStatus( | ||
| 'Copying Device Support symbols. This may take several minutes to complete...\n' | ||
| 'Please do not connect or disconnect your device or open Xcode until finished.', | ||
| ); | ||
| timer.cancel(); | ||
| } | ||
| if (printToTrace) { | ||
| _logger.printTrace(text); | ||
| } else { | ||
| _logger.printStatus(text, newline: false); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Searching for 'Copying' directly on raw stream chunks can fail if the word is split across chunk boundaries (which can happen due to OS block-buffering on pipes). Using LineSplitter ensures that we process complete lines, making the substring search robust and preventing potential false negatives where the status message is missed and the 10-second warning is erroneously triggered.
| final StreamSubscription<String> stdoutSubscription = process.stdout | |
| .transform(utf8.decoder) | |
| .listen((String text) { | |
| if (text.contains('Copying')) { | |
| printToTrace = false; | |
| _logger.printStatus( | |
| 'Copying Device Support symbols. This may take several minutes to complete...\n' | |
| 'Please do not connect or disconnect your device or open Xcode until finished.', | |
| ); | |
| timer.cancel(); | |
| } | |
| if (printToTrace) { | |
| _logger.printTrace(text); | |
| } else { | |
| _logger.printStatus(text, newline: false); | |
| } | |
| }); | |
| final StreamSubscription<String> stdoutSubscription = process.stdout | |
| .transform(utf8.decoder) | |
| .transform(const LineSplitter()) | |
| .listen((String line) { | |
| if (line.contains('Copying')) { | |
| printToTrace = false; | |
| _logger.printStatus( | |
| 'Copying Device Support symbols. This may take several minutes to complete...\n' | |
| 'Please do not connect or disconnect your device or open Xcode until finished.', | |
| ); | |
| timer.cancel(); | |
| } | |
| if (printToTrace) { | |
| _logger.printTrace(line); | |
| } else { | |
| _logger.printStatus(line); | |
| } | |
| }); |
| return 'Xcode Device Support symbols exist for this device, but are being read from ' | ||
| 'process memory. $reducePerformance\n' | ||
| 'To re-copy symbols from your device, complete the following steps:\n' | ||
| ' 1. Remove cached symbols:\n' | ||
| ' rm -rf "${existingDeviceSupportSymbols!.parent.path}"\n' | ||
| ' 2. $action'; |
There was a problem hiding this comment.
If existingDeviceSupportSymbols is archSymbolDirectory (e.g., deviceDirectory/arm64e/Symbols), then existingDeviceSupportSymbols!.parent is deviceDirectory/arm64e. Deleting only deviceDirectory/arm64e leaves other parts of the device directory intact, which might prevent Xcode from cleanly re-copying all symbols. To completely clear the cache for this device, we should delete the entire deviceDirectory (which is symbolDirectory.parent).
final (Directory? symbolDirectory, _) = _findSymbolDirectories();
final String deletePath = symbolDirectory?.parent.path ?? existingDeviceSupportSymbols!.parent.path;
return 'Xcode Device Support symbols exist for this device, but are being read from '
'process memory. $reducePerformance\n'
'To re-copy symbols from your device, complete the following steps:\n'
' 1. Remove cached symbols:\n'
' rm -rf "$deletePath"\n'
' 2. $action';| /// Returns a tuple of the symbol directory and the architecture-specific symbol directory. | ||
| /// If the paths to the directories cannot be constructed, returns (null, null). | ||
| /// | ||
| /// The architecture-specific symbol directory is only present on newer versions of iOS (27+). |
There was a problem hiding this comment.
The version number iOS (27+) is a typo for iOS (17+). iOS 17 is the version where Apple introduced the new CoreDevice connectivity and changed the symbol path structure. Please correct this typo here and on line 241 to avoid confusing future maintainers.
| /// The architecture-specific symbol directory is only present on newer versions of iOS (27+). | |
| /// The architecture-specific symbol directory is only present on newer versions of iOS (17+). |
There was a problem hiding this comment.
If platform status fails or doesn't output the expected Platform: pattern (e.g., due to an unexpected LLDB error or environment difference), await futureLog will hang indefinitely, blocking the entire flutter run startup. Adding a reasonable timeout (e.g., 5 seconds) ensures that the tool remains robust and continues startup even if the status command fails to produce the expected output.
Future<void> _printDeviceSupportStatus() async {
final Future<String> futureLog = _startWaitingForLog(
RegExp(r'\s*Platform:'),
).then((value) => value, onError: _handleAsyncError);
await _lldbProcess?.stdinWriteln('platform status');
await futureLog.timeout(
const Duration(seconds: 5),
onTimeout: () => '',
);
}
Issue Link:
#189284
Impact Description:
When debugging on iOS 27 devices, the app may launch and hang for multiple minutes.
Changelog Description:
[flutter/189284] When debugging with an iOS 27 device, the app may launch to a white screen and hang for multiple minutes.
Workaround:
Run
xcrun xcodebuild -prepareDeviceSupport -destination id=[insert device id]Risk:
What is the risk level of this cherry-pick?
Test Coverage:
Are you confident that your fix is well-tested by automated tests?
Validation Steps:
See repro steps in #189284.