You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On modern iOS (iOS 16+) and the matching simulators, this call always fails — Apple moved system frameworks fully into the dyld
shared cache and removed the on-disk copies at /System/Library/Frameworks/.
The function then prints two noisy lines to stdout on every launch:
Got dlopen error on Foundation: dlopen(/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation, 0x0001):
tried: '…/Foundation' (no such file), …
'/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' (no such file, not in dyld cache), …
Got fallback dlopen error on Foundation: dlopen(/Groups/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation, 0x0001):
…
The fact that pyobjus still works correctly afterwards is a useful
clue: nothing downstream depends on the dlopen handle.
Two closed prior issues raised this same warning a decade ago
(#22, #36); neither actually fixed the root cause. Filing fresh
with current context — Xcode 16 / iOS 18.3 simulator / dyld shared
cache architecture — and a concrete fix proposal.
Starting with iOS 16 (2022), Apple removed the on-disk copies of
system frameworks from the device filesystem. They now live only in the dyld shared cache — a single mmap'd blob that
contains pre-linked images of every system library. The simulator
runtime followed suit shortly after; on iOS 18.3 simulator there
is no file at /System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation,
just like on a real device.
dlopen() of an absolute path that doesn't exist on disk fails,
regardless of code signing or entitlements. The dyld shared cache
provides Foundation to every process by default, but only via direct linking and dlopen(NULL, …) / dlsym lookup — not via
absolute filesystem dlopen.
The runtime symptom is documented in Apple's iOS 16 release notes and reproducible on
any iOS 16+ device or simulator.
Why it's "harmless" today
Apps that link pyobjus also link Foundation through the normal
linker path (-framework Foundation, declared in kivy-ios's objc
recipe pbx_frameworks). By the time pyobjc_internal_init() runs,
Foundation is already mapped into the process and every Objective-C
class it defines (NSAutoreleasePool, NSString, …) is reachable
through the Objective-C runtime (objc_getClass, class_createInstance, objc_msgSend).
The dlopen handle is not subsequently read in the current code — _runtime.h stores it in a static variable and returns. The next
function in the file, allocAndInitAutoreleasePool(), reaches NSAutoreleasePool via objc_getClass("NSAutoreleasePool"), which
goes through the Objective-C runtime's class registry and doesn't
depend on the dlopen handle. So when the call fails, downstream
ObjC operations continue to work.
Why it should still be fixed
Noise. Two scary-looking dlopen error lines on every app
launch are confusing to anyone reading device logs for the first
time. They look like a real failure but aren't. New users
regularly file bugs against kivy-ios and kivy thinking
something is broken.
/Groups/System/... is a bogus path. The fallback dlopen("/Groups/System/Library/Frameworks/Foundation.framework/...")
does not correspond to any real directory on any version of macOS
or iOS. It looks like a typo — possibly /System/Library/...
typed with the wrong autocomplete, or a paste from somewhere
else's filesystem layout. Got dlopen error on Foundation #22 (closed 2016-10-28) flagged this
exact bogus path a decade ago; it was never fixed.
Future-proofing. Apple has been progressively tightening
absolute-path dlopen over the last several releases (sandbox
restrictions, library validation, hardened runtime, etc.).
Simplifying the init path now reduces the chance that a future
iOS / macOS release introduces a new failure mode here.
macOS sandbox / Mac App Store. On sandboxed macOS apps the
absolute-path dlopen of a system framework can also fail
depending on temporary-exception entitlements. The function's
silent-failure behavior masks this from developers who would
otherwise want to know.
Suggested fix
Delete the body of pyobjc_internal_init():
staticvoidpyobjc_internal_init() {
// Foundation is provided by the dyld shared cache on iOS and by// direct linking on macOS. The Objective-C runtime (objc_getClass// etc.) works without an explicit dlopen handle, so no action is// needed here.
}
Or remove the function and its single call site in pyobjus.pyx:70 entirely. Either way the result is the same.
Why this is safe
There is no compatibility consideration that requires keeping the
dlopen mechanism:
The function is private.static storage class makes it
translation-unit-local. Not part of any external API; no
downstream caller can reference it.
The handle is unused. The static void *foundation
variable in _runtime.h is the only place the dlopen result is
stored. It's written once at init time and never read by
anything. Verified by searching the pyobjus tree:
$ grep -rn 'foundation' pyobjus/
pyobjus/_runtime.h:10: static void *foundation = NULL; # decl
pyobjus/_runtime.h:11: if ( foundation == NULL ) { # write
pyobjus/_runtime.h:12: foundation = dlopen( # write
pyobjus/_runtime.h:14: if ( foundation == NULL ) { # write
pyobjus/_runtime.h:19: foundation = dlopen( # write
pyobjus/_runtime.h:21: if ( foundation == NULL ) { # write
All writes, no reads.
Foundation is always already loaded by the time pyobjc_internal_init() runs. pyobjus declares Foundation in
its own frameworks list (pyobjus/dylib_manager.py:105, pyobjus/consts/__init__.py:74) and on kivy-ios is also
linked into the app via the objc recipe's pbx_frameworks.
dyld loads Foundation when import pyobjus triggers the C
extension load — strictly before the Python-visible pyobjc_internal_init() call at pyobjus.pyx:70 runs.
The only behavioral change from removing the call is that two
stdout lines disappear from every launch.
Related (closed without fix)
Got dlopen error on Foundation #22 — "Got dlopen error on Foundation" (closed 2016-10-28).
Reporter flagged the bogus /Groups/System/... fallback path.
Closed as "completed" but the typo was never corrected. Two
separate users posted "I'm still hitting this" follow-ups in
2017 and 2019.
dlopen error on Foundation #36 — "dlopen error on Foundation" (closed 2016-11-08).
Discussion pivoted to a separate-but-related symptom (a sys.stdout/sys.stderr fake-redirect block in the kivy-ios
cookiecutter main.m swallowing logs). The fake-redirect was
conditionalized but the underlying dlopen warning was dismissed
with "this only happens in the simulator, as the log message now
indicates." That dismissal is no longer accurate post-iOS-16 —
the path doesn't exist on devices either.
Summary
pyobjus/_runtime.h::pyobjc_internal_init()tries todlopen()Foundation by absolute filesystem path:
On modern iOS (iOS 16+) and the matching simulators, this call
always fails — Apple moved system frameworks fully into the dyld
shared cache and removed the on-disk copies at
/System/Library/Frameworks/.The function then prints two noisy lines to stdout on every launch:
The fact that pyobjus still works correctly afterwards is a useful
clue: nothing downstream depends on the dlopen handle.
Two closed prior issues raised this same warning a decade ago
(#22, #36); neither actually fixed the root cause. Filing fresh
with current context — Xcode 16 / iOS 18.3 simulator / dyld shared
cache architecture — and a concrete fix proposal.
Environment
master(verified_runtime.his byte-identical towhat's been there since 2016)
master@54426ed, Kivy 3.0feature/video-avfoundationWhy the dlopen always fails on modern iOS
Starting with iOS 16 (2022), Apple removed the on-disk copies of
system frameworks from the device filesystem. They now live
only in the dyld shared cache — a single mmap'd blob that
contains pre-linked images of every system library. The simulator
runtime followed suit shortly after; on iOS 18.3 simulator there
is no file at
/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation,just like on a real device.
dlopen()of an absolute path that doesn't exist on disk fails,regardless of code signing or entitlements. The dyld shared cache
provides Foundation to every process by default, but only via
direct linking and
dlopen(NULL, …)/dlsymlookup — not viaabsolute filesystem
dlopen.The runtime symptom is documented in
Apple's iOS 16 release notes and reproducible on
any iOS 16+ device or simulator.
Why it's "harmless" today
Apps that link pyobjus also link Foundation through the normal
linker path (
-framework Foundation, declared in kivy-ios'sobjcrecipe
pbx_frameworks). By the timepyobjc_internal_init()runs,Foundation is already mapped into the process and every Objective-C
class it defines (
NSAutoreleasePool,NSString, …) is reachablethrough the Objective-C runtime (
objc_getClass,class_createInstance,objc_msgSend).The
dlopenhandle is not subsequently read in the current code —_runtime.hstores it in a static variable and returns. The nextfunction in the file,
allocAndInitAutoreleasePool(), reachesNSAutoreleasePoolviaobjc_getClass("NSAutoreleasePool"), whichgoes through the Objective-C runtime's class registry and doesn't
depend on the dlopen handle. So when the call fails, downstream
ObjC operations continue to work.
Why it should still be fixed
Noise. Two scary-looking
dlopenerror lines on every applaunch are confusing to anyone reading device logs for the first
time. They look like a real failure but aren't. New users
regularly file bugs against
kivy-iosandkivythinkingsomething is broken.
/Groups/System/...is a bogus path. The fallbackdlopen("/Groups/System/Library/Frameworks/Foundation.framework/...")does not correspond to any real directory on any version of macOS
or iOS. It looks like a typo — possibly
/System/Library/...typed with the wrong autocomplete, or a paste from somewhere
else's filesystem layout. Got dlopen error on Foundation #22 (closed 2016-10-28) flagged this
exact bogus path a decade ago; it was never fixed.
Future-proofing. Apple has been progressively tightening
absolute-path
dlopenover the last several releases (sandboxrestrictions, library validation, hardened runtime, etc.).
Simplifying the init path now reduces the chance that a future
iOS / macOS release introduces a new failure mode here.
macOS sandbox / Mac App Store. On sandboxed macOS apps the
absolute-path
dlopenof a system framework can also faildepending on temporary-exception entitlements. The function's
silent-failure behavior masks this from developers who would
otherwise want to know.
Suggested fix
Delete the body of
pyobjc_internal_init():Or remove the function and its single call site in
pyobjus.pyx:70entirely. Either way the result is the same.Why this is safe
There is no compatibility consideration that requires keeping the
dlopen mechanism:
The function is private.
staticstorage class makes ittranslation-unit-local. Not part of any external API; no
downstream caller can reference it.
The handle is unused. The
static void *foundationvariable in
_runtime.his the only place the dlopen result isstored. It's written once at init time and never read by
anything. Verified by searching the pyobjus tree:
All writes, no reads.
Foundation is always already loaded by the time
pyobjc_internal_init()runs. pyobjus declares Foundation inits own
frameworkslist (pyobjus/dylib_manager.py:105,pyobjus/consts/__init__.py:74) and on kivy-ios is alsolinked into the app via the
objcrecipe'spbx_frameworks.dyld loads Foundation when
import pyobjustriggers the Cextension load — strictly before the Python-visible
pyobjc_internal_init()call atpyobjus.pyx:70runs.The only behavioral change from removing the call is that two
stdout lines disappear from every launch.
Related (closed without fix)
Reporter flagged the bogus
/Groups/System/...fallback path.Closed as "completed" but the typo was never corrected. Two
separate users posted "I'm still hitting this" follow-ups in
2017 and 2019.
Discussion pivoted to a separate-but-related symptom (a
sys.stdout/sys.stderrfake-redirect block in the kivy-ioscookiecutter
main.mswallowing logs). The fake-redirect wasconditionalized but the underlying
dlopenwarning was dismissedwith "this only happens in the simulator, as the log message now
indicates." That dismissal is no longer accurate post-iOS-16 —
the path doesn't exist on devices either.