Apple: build dart_bridge as a dynamic framework (1.6.0) - #11
Merged
Conversation
build_xcframework.sh now produces dart_bridge.xcframework as a dynamic framework (a .framework with a dylib) instead of a static library. The FFI entry points (serious_python_run, DartBridge_*, PyInit_dart_bridge) are resolved at runtime via dlsym (Dart's DynamicLibrary.process() and Python's 'import dart_bridge'). A static library linked into the host app executable does not export its symbols -- an iOS executable exports nothing by default and the release build strips local symbols -- so dlsym(RTLD_DEFAULT) could not find them and release/device builds crashed at startup with 'Failed to lookup symbol serious_python_run'. A dynamic framework exports its default-visibility symbols and is loaded as its own image, so dlsym resolves them, matching the Android .so (CMake SHARED) that already worked. Only release/archive builds were affected; debug/simulator don't dead-strip and appeared to work. Py_*/Dart_* are left undefined (-undefined dynamic_lookup) and bound at load time from the app's Python.framework and the Dart runtime. Linux/Windows/ Android are unchanged (already dynamic).
FeodorFitsner
added a commit
to flet-dev/serious-python
that referenced
this pull request
Jul 25, 2026
…240) * Bump to 4.4.0: dart_bridge as a dynamic framework on Apple platforms Fixes built iOS apps crashing at startup with 'Failed to lookup symbol serious_python_run: dlsym(RTLD_DEFAULT, serious_python_run): symbol not found'. dart_bridge's FFI entry points (serious_python_run, DartBridge_*, PyInit_dart_bridge) are resolved at runtime via dlsym -- from Dart through DynamicLibrary.process() and from Python through 'import dart_bridge'. It previously shipped as a static archive linked into the host app executable, and an iOS executable exports nothing to the dynamic symbol table by default (release builds also strip local symbols), so those lookups failed. Only release/archive device builds were affected: debug/simulator don't dead-strip. Android was never affected -- its dart_bridge is a dynamic .so. dart_bridge 1.6.0 now builds dart_bridge.xcframework as a dynamic framework (flet-dev/dart-bridge#11), so it is embedded + signed into the app like Python.xcframework and its symbols stay exported. Removes what a loaded image no longer needs: the SwiftPM -all_load/-force_load retention, the CocoaPods -all_load, and the plugin's dead-strip keep-alive references. Re-pins the bundled python-build snapshot to 20260725 (dart_bridge 1.5.1 -> 1.6.0, Pyodide 3.14 314.0.2 -> 314.0.3). Python versions (3.12.13 / 3.13.14 / 3.14.6) are unchanged and all platform runtimes are byte-identical to 20260720. Also bumps .fvmrc to Flutter 3.44.8. * Re-pin dart_bridge to 1.6.1 (macOS framework symlink fix) python-build's 20260725 manifest was updated in place to dart_bridge 1.6.1, so regenerate the version tables against it. 1.6.1 zips the Apple xcframework with -y so the macOS slice's versioned framework symlinks (Versions/Current -> A, dart_bridge and Resources -> Versions/Current/...) survive publishing. Under 1.6.0 they were followed and stored as real files/directories, producing a malformed bundle that macOS codesign rejected ('Couldn't resolve framework symlink for .../Versions/Current', 'code object is not signed at all'). iOS was unaffected -- its slices use a flat layout with no symlinks. See flet-dev/dart-bridge#12. Binaries are unchanged from 1.6.0; only the artifact packaging differs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds
dart_bridge.xcframeworkfor Apple platforms as a dynamic framework (a.frameworkwith a dylib) instead of a static library. Bumps the version to 1.6.0.Why
The FFI entry points —
serious_python_run,DartBridge_InitDartApiDL,DartBridge_EnqueueMessage,PyInit_dart_bridge— are resolved at runtime viadlsym(Dart'sDynamicLibrary.process()and Python'simport dart_bridge).A static library linked into the host app executable does not export its symbols: an iOS executable exports nothing to the dynamic symbol table by default, and the release build strips local symbols. So
dlsym(RTLD_DEFAULT)couldn't find them, and release/device builds crashed at startup with:Debug/simulator builds don't dead-strip, so they appeared to work — the failure was release/archive (device) only. This is exactly why Android already worked (its CMake build is
add_library(... SHARED)→ a dynamic.sothat exports its symbols) while iOS did not.A dynamic framework exports its default-visibility symbols and is loaded as its own image, so
dlsymresolves them — bringing Apple in line with Android.What changed
apple/build_xcframework.sh: compile the three C sources per slice, then link a dylib (-dynamiclib,install_name @rpath/dart_bridge.framework/dart_bridge) and wrap it in a.framework(flat layout for iOS device/simulator, versioned for macOS), then-create-xcframeworkfrom the frameworks.Py_*/Dart_*stay undefined (-undefined dynamic_lookup) and bind at load time from the app's embeddedPython.frameworkand the Dart runtime — the same deferral the static build relied on at app link time, now at load time.CMakeLists.txt: version → 1.6.0.CHANGELOG.md: added (documents the change + consumer impact).The Linux/Windows/Android builds are unchanged. The output artifact name (
dist/dart_bridge-apple.xcframework.zip) and the CIbuild-applejob are unchanged.Consumer impact
serious_python_darwinembeds + signs the framework (as it already does forPython.xcframework) instead of static-linking it — no-all_load/-force_loadretention or dead-strip keep-alive needed. A matchingserious_pythonchange lands separately.Verified
Built the iOS-device slice locally and confirmed the dylib's export trie contains
serious_python_run,DartBridge_InitDartApiDL,DartBridge_EnqueueMessage,PyInit_dart_bridgewithinstall_name @rpath/dart_bridge.framework/dart_bridge. End-to-end: a Flet iOS release app that embeds it boots on-device and resolves the symbols (previously crashed at startup).Note
-undefined dynamic_lookupwarns "deprecated on iOS." It works and matches how CPython extension modules link, but App Store acceptance is worth confirming; the alternative is linking the dylib againstPython.frameworkexplicitly.