Skip to content

fix: enable Google ML Kit on Apple Silicon iOS 26+ simulators#862

Merged
fbernaly merged 5 commits into
flutter-ml:developfrom
lucasdonordeste:feat/apple-silicon-ios-simulator
Jun 4, 2026
Merged

fix: enable Google ML Kit on Apple Silicon iOS 26+ simulators#862
fbernaly merged 5 commits into
flutter-ml:developfrom
lucasdonordeste:feat/apple-silicon-ios-simulator

Conversation

@lucasdonordeste
Copy link
Copy Markdown
Contributor

Summary

Fixes #825. Lets projects depending on google_ml_kit_* build, install and run on iOS 26+ simulators on Apple Silicon Macs without Rosetta 2.

Why this is needed

Google's GoogleMLKit/* CocoaPods only ship two slices per framework: arm64-iphoneos and x86_64-iphonesimulator. Their podspecs propagate EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 to the consuming app via user_target_xcconfig. On Apple Silicon Macs running iOS 26+ simulators (where Rosetta 2 is not available by default), the simulator only runs arm64, so flutter run fails with:

Uncategorized (Xcode): Unable to find a destination matching the provided destination specifier

Root cause is upstream in Google's MLKitCommon/MLImage/MLKit* binary distribution — it must include arm64-iphonesimulator slices. Tracked at https://issuetracker.google.com/issues/178965151. Until that lands, every consumer of these plugins is locked out of iOS 26 simulators.

What this PR does

Adds an opt-in Podfile helper under google_mlkit_commons/ios/scripts/ that runs at pod install time:

  1. patch_arm64_simulator.py — walks every MLKit*/MLImage* framework binary inside Pods/, finds the arm64 slice (a BSD ar static archive of Mach-O 64 objects), and changes a single 4-byte field per object: LC_BUILD_VERSION.platform, from 2 (iOS) to 7 (iOS Simulator). Same approach as the well-known arm64-to-sim tool. Idempotent — re-running pod install is a no-op once binaries already report platform=Simulator.
  2. apple_silicon_simulator.rb — Ruby helper exposing mlkit_apple_silicon_simulator_patch(installer) for use from any consumer's Podfile. Also strips EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 from the xcconfigs CocoaPods generates from each pod's user_target_xcconfig.

The example app's Podfile is updated to call the helper from post_install, and packages/google_mlkit_commons/README.md documents the two lines a consumer needs to add to opt-in.

Files touched:

  • packages/google_mlkit_commons/ios/scripts/patch_arm64_simulator.py (new)
  • packages/google_mlkit_commons/ios/scripts/apple_silicon_simulator.rb (new)
  • packages/google_mlkit_commons/README.md (docs section under iOS Requirements)
  • packages/example/ios/Podfile (one require + one mlkit_apple_silicon_simulator_patch(installer) call)
  • packages/example/ios/Podfile.lock (auto-regen)

Device builds, release builds, IPA builds — none of them are touched. The helper only runs when consumers add the two opt-in lines, and only modifies vendored binaries inside Pods/ plus pod-generated xcconfigs.

Validation

End-to-end on this machine:

Mac Apple Silicon (M-series)
Xcode 26.2 (Build 17C52)
iOS Simulator iPhone 17 Pro / iOS 26.3.1 (23D8133), arm64 native
Flutter 3.x stable
ML Kit pods GoogleMLKit/* 9.0.0, MLKitCommon 14.0.0, MLImage 1.0.0-beta8
Pods patched 27 of 31 frameworks (the other 4 ship without LC_BUILD_VERSION and link as-is); 512 Mach-O objects relabeled total

Steps reproduced:

  1. cd packages/example && flutter pub get && cd ios && pod install — patcher runs, prints [ml_kit] Patching 31 ML Kit framework(s) for Apple Silicon iOS Simulator...
  2. flutter run -d <iOS-26-sim-id> — Xcode build succeeds in ~38 s, no linker errors
  3. App installs and launches on the Apple Silicon iOS 26.3 simulator
  4. Tapped Vision → Text Recognition → Text From Widget Example → Capture and Recognize Text
  5. Recognition Result returned the exact widget text byte-for-byte ("This is sample text\nthat will be captured\nand processed using\nthe ML Kit Text Recognizer.\nTry different fonts\nand styles to test\nthe recognition capabilities!"), proving native MLKit inference runs correctly with the relabeled binaries.

Runtime log noise observed: a Fault for MLKITx_SRLRegistry: No binding was found for required, single-bound service: CCTPolicyVending_API and an Error for MLKITx_GIPPseudonymousIDStore: Shared App Groups unavailable — both are pre-existing Clearcut-telemetry / shared-storage warnings emitted by ML Kit in any debug build (sim or device); they do not block inference.

Trade-offs and what's not validated

  • This is the same hack arm64-to-sim applies to other closed-source iOS SDKs. There is a non-zero risk that some MLKit code path expects an iOS-device-only system framework symbol that doesn't exist on the simulator. I only smoke-tested text recognition; barcode, face, pose, etc. weren't exercised. I'd appreciate maintainers' help validating other detectors before this is recommended for general use — that's why it's strictly opt-in and lives behind two Podfile lines.
  • Marked as opt-in, not as default behavior. Consumers who don't add the helper see no change.
  • The MLKit ToS prohibits "reverse engineer or attempt to extract the source code". Modifying a 4-byte LC_BUILD_VERSION.platform field in the load command isn't source-code extraction or algorithm analysis; Apple's own vtool ships -set-build-version for exactly this purpose, and arm64-to-sim (582★) has done the same on closed Google SDKs for years without legal pushback. Maintainers may still want to make their own call here.
  • I did not bump google_mlkit_commons version or touch the CHANGELOG.md — happy to do either if preferred. Adding pure tooling under ios/scripts/ doesn't affect the published Dart/native runtime.
  • The example app's PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)" is a pre-existing recursive value that breaks xcrun simctl install. To reproduce this PR's runtime smoke test you'll need to set a real bundle identifier locally first; the build itself succeeds either way. I deliberately left this out of the PR to keep scope narrow.

How to reproduce on your machine

git fetch && git checkout feat/apple-silicon-ios-simulator
cd packages/example
flutter clean && flutter pub get
cd ios && pod install                     # should print "[ml_kit] Patching ..."
flutter build ios --simulator --debug      # links cleanly on iOS 26 sim
# To `flutter run` you'll also need a real PRODUCT_BUNDLE_IDENTIFIER set
# in Runner.xcodeproj as noted above.

Test plan

  • CI (existing) still green
  • Reviewer reproduces build on Apple Silicon iOS 26 simulator
  • (Stretch) Reviewer smoke-tests at least one detector beyond Text Recognition
  • Decision on whether to bump google_mlkit_commons version

Apple removed Rosetta 2 from the default iOS 26 simulator runtime, which
breaks `flutter run` for any project depending on Google ML Kit on
Apple Silicon Macs. The published GoogleMLKit/* CocoaPods only ship
arm64-iphoneos and x86_64-iphonesimulator slices and pin
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64, so the simulator build
no longer finds a matching destination and dies with
"Unable to find a destination matching the provided destination specifier".

Until Google publishes proper arm64-iphonesimulator slices
(https://issuetracker.google.com/issues/178965151), this change ships an
opt-in Podfile helper under google_mlkit_commons that:

1. Re-labels the existing arm64 device slice of every ML Kit framework
   binary as iOS Simulator. Only the 4-byte LC_BUILD_VERSION.platform
   field is changed (2 -> 7), the same approach the well-known
   arm64-to-sim tool uses on closed-source SDKs. Idempotent.
2. Strips EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 from the
   xcconfigs CocoaPods generates from the pod's user_target_xcconfig,
   so the user's app target is allowed to build for arm64-iphonesimulator.

The example/ios Podfile is wired to call the helper from post_install,
which lets the example app build, install and run on an Apple Silicon
iOS 26.3 simulator. End-to-end validated: Text Recognition on the
Text-From-Widget view returned the exact widget text. Device builds and
release builds are not affected.

Closes flutter-ml#825
The Ruby and Python entry points already have a brief header pointing at
the README. Per-function docstrings were restating what the function name
already said, and the Podfile inline comments duplicated the helper's
self-documenting name.
@Toker38
Copy link
Copy Markdown

Toker38 commented May 13, 2026

Thanks for fix.

udiedrichsen added a commit to moinsen-dev/google_ml_kit_flutter that referenced this pull request May 16, 2026
…l_kit_flutter

- PR flutter-ml#864 / Issue flutter-ml#863: Fix iOS IOSurface memory leak in pixelBufferToVisionImage.
  Replaces CoreImage CIContext+createCGImage round-trip with VisionImage(buffer:)
  via CMSampleBuffer wrapper, eliminating ~3.5 MiB leak per frame on sustained
  camera streaming.

- PR flutter-ml#862 / Issue flutter-ml#825: Add Apple Silicon iOS 26+ simulator support.
  Ships opt-in Podfile helper (apple_silicon_simulator.rb + patch_arm64_simulator.py)
  that re-labels arm64 device slices as iOS Simulator and strips EXCLUDED_ARCHS.
  Updates README with usage instructions and wires up example app Podfile.

- Issue flutter-ml#857: Fix IllegalStateException 'Reply already submitted' crash in
  DocumentScanner on Oppo/ColorOS devices. Clears pendingResult after calling
  .success() or .error() so duplicate onActivityResult deliveries are safe.
@github-actions
Copy link
Copy Markdown

This PR is stale because it has been open for 14 days with no activity.

@github-actions github-actions Bot added the stale label May 28, 2026
@rhamnett
Copy link
Copy Markdown

It needs the release

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in CocoaPods post_install helper to let apps using google_ml_kit_* build and run on Apple Silicon iOS 26+ simulators by (1) patching ML Kit vendored framework binaries to report the simulator platform and (2) removing the EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 setting from pod-generated xcconfigs. This addresses the current upstream limitation where Google’s ML Kit pods ship no arm64-iphonesimulator slices and exclude arm64 on simulator, blocking native arm64 simulators without Rosetta.

Changes:

  • Add a Python patcher that rewrites Mach-O LC_BUILD_VERSION.platform from iOS to iOS Simulator inside ML Kit framework binaries under Pods/.
  • Add a Ruby Podfile helper that runs the patcher during pod install and strips EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 from generated .xcconfig files.
  • Document opt-in usage in google_mlkit_commons README and wire the example app’s Podfile to call the helper.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/google_mlkit_commons/README.md Documents the opt-in Podfile helper and rationale for iOS 26+ Apple Silicon simulators.
packages/google_mlkit_commons/ios/scripts/patch_arm64_simulator.py Implements the Mach-O/ar patcher for relabeling ML Kit binaries as simulator-compatible.
packages/google_mlkit_commons/ios/scripts/apple_silicon_simulator.rb Adds a Podfile helper to invoke the patcher and remove the arm64 simulator exclusion line.
packages/example/ios/Podfile Demonstrates usage by requiring the helper and calling it in post_install.
packages/example/ios/Podfile.lock Updates lockfile checksum due to Podfile change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/google_mlkit_commons/ios/scripts/patch_arm64_simulator.py Outdated
@fbernaly fbernaly self-assigned this Jun 4, 2026
@fbernaly
Copy link
Copy Markdown
Collaborator

fbernaly commented Jun 4, 2026

@lucasdonordeste : resolve this as well:
image

Odd-sized archive members are followed by a 1-byte padding so the next
header starts on a 2-byte boundary. The rewrite loop skipped this byte on
read but never re-emitted it on write, shifting every subsequent header
and corrupting the framework binary whenever a member had an odd size.

Applies the maintainer/Copilot-suggested fix verbatim.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Comment thread packages/google_mlkit_commons/ios/scripts/patch_arm64_simulator.py
Comment thread packages/google_mlkit_commons/ios/scripts/apple_silicon_simulator.rb Outdated
Comment thread packages/google_mlkit_commons/README.md Outdated
@fbernaly
Copy link
Copy Markdown
Collaborator

fbernaly commented Jun 4, 2026

@lucasdonordeste : could you take a look at the new comments

Replace the one-shot pod-install relabel (which broke device builds until
manually reverted) with a per-build Xcode script phase that relabels the arm64
slice's LC_BUILD_VERSION.platform to match the build target: iOS Simulator for
simulator builds, iOS for device builds. The same pod install now works for both
with no manual revert.

- patch_arm64_simulator.py: edit the platform field in place via mmap (no lipo
  subprocess, no temp files, no marker); bidirectional 2<->7; bounds-checked
  Mach-O walk; fail loud when no framework binaries are found; tagged errors.
- apple_silicon_simulator.rb: copy the patcher into Pods, strip the simulator
  arm64 exclusion via a robust key-based match, and install an always-out-of-date
  'set -euo pipefail' build phase on each Pods aggregate target.
- README: document the automatic per-build behavior and the full-revert steps.
@fbernaly fbernaly merged commit be7035c into flutter-ml:develop Jun 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Projects including google_ml_kit do not run on iOS 26 simulator

5 participants