Running a Catalyst application via XCTest loads macOS specific frameworks

When trying to test a Catalyst application with XCTest the application crashes at startup with Symbol not found error when certain frameworks are in use. This is caused by XCTest / Xcode adding /Applications/Xcode.app/Contents/SharedFrameworks to DYLD_FRAMEWORK_PATH causing dyld to load the macOS specific framework variant of for example RealityKit instead from /System/iOSSupport/System/Library/Frameworks as defined in the load commands.

Which leads to symbol mismatches, for example ARView.Environment.Color is a UIColor on Mac Catalyst but an NSColor on macOS (_$s10RealityKit6ARViewC11EnvironmentV10BackgroundV5coloryAGSo7UIColorCFZ vs. _$s10RealityKit6ARViewC11EnvironmentV10BackgroundV5coloryAGSo7NSColorCFZ)

Tried prepending /System/iOSSupport/System/Library/Frameworks to the DYLD_FRAMEWORK_PATH env var of the test target, but via some private frameworks still wrong framework variants were loaded.

Any ideas for possible fixes or workarounds?

Have you filed a bug about this already? If so, what was the bug number?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Yes, it's FB13800154

it's FB13800154

Thanks for that.

I had a good look at this today and I can’t see any reasonable way around it )-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I had a good look at this today and I can’t see any reasonable way around it )-:

Thanks for checking anyway.

Would it be possible to weakly load the framework somehow, so that at least we can run other tests without having to remove code at build time?

Would it be possible to weakly load the framework somehow … ?

That’s not really how weak linking works. The behaviour for a weak linked library is that the dynamic linker will not trigger a fatal error if the library is missing. However, in your case the library is present but has the wrong symbols. You could potentially get around that by weak linking to the specific symbols, but… yeah… that’s gonna got pretty weird pretty fast.

Earlier you wrote:

via some private frameworks still wrong framework variants were loaded.

I’m be curious what’s causing that. If you tweak your app code to remove all references to RealityKit and then test the app with the DYLD_PRINT_SEARCHING environment variable set (see the dyld man page), what ends up bringing it RealityKit?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

You could potentially get around that by weak linking to the specific symbols

That's what I meant by "weakly loading".

what ends up bringing it RealityKit?

It's not that RealityKit is brought in regardless if it's used, but that a different incompatible framework is loaded if /System/iOSSupport/System/Library/Frameworks is prepended to DYLD_FRAMEWORK_PATH.

dyld[44019]: find path "/System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation"
dyld[44019]:   possible path(DYLD_FRAMEWORK/LIBRARY_PATH): "/System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation"
dyld[44019]: no pseudo-dylibs to search
dyld[44019]:   found: dylib-from-cache: (0x0A52) "/System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation"

// ...

dyld[44019]: found '/Applications/Xcode.app/Contents/SharedFrameworks/RealityFoundation.framework/Versions/A/RealityFoundation' which invalidates PrebuiltLoader for '/System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation'
dyld[44019]: PrebuiltLoader 0x24059cf38 'RealityFoundation' not used because a file was found that overrides it
dyld[44019]: using JustInTimeLoader 0x1143e1c20 for /System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation
dyld[44019]: <91D6CFF8-6AC5-34BC-B1FE-83EA3CB7C7E3> /System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation

// ...

dyld[44019]: find path "/System/Library/PrivateFrameworks/RealityIO.framework/Versions/A/RealityIO"
dyld[44019]:   possible path(DYLD_FRAMEWORK/LIBRARY_PATH): "/System/iOSSupport/System/Library/Frameworks/RealityIO.framework/Versions/A/RealityIO"
dyld[44019]: no pseudo-dylibs to search
dyld[44019]:   possible path(DYLD_FRAMEWORK/LIBRARY_PATH): "/Users/spacecadet/projects/shapr3d/src/shapr3d/build/cmake/DerivedData/Shapr3D/Build/Products/Debug-maccatalyst/RealityIO.framework/Versions/A/RealityIO"
dyld[44019]: no pseudo-dylibs to search
dyld[44019]:   possible path(DYLD_FRAMEWORK/LIBRARY_PATH): "/Users/spacecadet/projects/shapr3d/src/shapr3d/build/products/Debug-maccatalyst/RealityIO.framework/Versions/A/RealityIO"
dyld[44019]: no pseudo-dylibs to search
dyld[44019]:   possible path(DYLD_FRAMEWORK/LIBRARY_PATH): "/Applications/Xcode.app/Contents/SharedFrameworks/RealityIO.framework/Versions/A/RealityIO"
dyld[44019]: no pseudo-dylibs to search
dyld[44019]:   found: dylib-from-disk-to-override-cache: "/Applications/Xcode.app/Contents/SharedFrameworks/RealityIO.framework/Versions/A/RealityIO"
dyld[44019]: using JustInTimeLoader 0x1143e5410 for /Applications/Xcode.app/Contents/SharedFrameworks/RealityIO.framework/Versions/A/RealityIO
dyld[44019]: <6194C114-E51C-38BF-AC13-23DF6A3435E3> /Applications/Xcode.app/Contents/SharedFrameworks/RealityIO.framework/Versions/A/RealityIO

// ...

dyld[44019]: find path "@rpath/RealityFoundation.framework/Versions/A/RealityFoundation"
dyld[44019]:   possible path(DYLD_FRAMEWORK/LIBRARY_PATH): "/System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation"
dyld[44019]:   found: already-loaded-by-path: "/System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation"

// and then in the end

dyld[44019]: Symbol not found: _$s10RealityKit11__EntityRefV0A10FoundationE28__validInteractionIdentifier0E04UUIDVvg
  Referenced from: <6194C114-E51C-38BF-AC13-23DF6A3435E3> /Applications/Xcode.app/Contents/SharedFrameworks/RealityIO.framework/Versions/A/RealityIO
  Expected in:     <91D6CFF8-6AC5-34BC-B1FE-83EA3CB7C7E3> /System/iOSSupport/System/Library/Frameworks/RealityFoundation.framework/Versions/A/RealityFoundation
That's what I meant by "weakly loading".

I don’t think there’s a way to do that without heroic measures.

a different incompatible framework is loaded

Right. This is a ‘feature’ of DYLD_FRAMEWORK_PATH. Notice the a file was found that overrides it text in that log. In the dyld man page it says:

It allows you to test new versions of existing frameworks.

and that’s exactly what the dynamic linker thinks you’re doing here )-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

There has to be some difference between RealityKit and RealityFoundation because the first one is picked up from /System/iOSSupport/System/Library/Frameworks/ if this is the first path in DYLD_FRAMEWORK_PATH, but for second the mentioned override happens. Anyway I need more extensive study on dyld source for better understanding.

The options I found so far:

  • Replace LC_LOAD_DYLIB / LC_ID_DYLIB in the executable to point to a custom linker, which given the amount of hardcoded /usr/lib/dyld paths is probably not an option, and I would guess the linker location is also enforced by the kernel.
  • Write a custom XCTest driver.
  • Remove / rename RealityKit.framework from Xcode provided SharedFrameworks. This actually makes the executable start and run, and seems like a cheap workaround to allow running and testing RealityKit independent code.

Will go with the last one for now.

Running a Catalyst application via XCTest loads macOS specific frameworks
 
 
Q