App crashes when XCode on target system is out of date

Some users of my swift 4.2 apps distributed on the macOS App store are reporting that the app crashes on startup if the target system has an out of date XCode installed.


Typical crashlogs look like this


Dyld Error Message:
Symbol not found: __TMSi
Referenced from: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswiftos.dylib
Expected in: /Applications/MYAPP.app/Contents/MacOS/../Frameworks/libswiftCore.dylib
in /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswiftos.dylib

Why is the dynamic linker using libraries in the /Applications/XCode bundle when everything that the app needs is in the app bundle Frameworks folder ?


The above example was with an app built with XCode 10.2, run on a 10.13.6 machine, with XCode 9. Similar errors occur on a machine running 10.14.4 but with XCode 10.1 installed. The crash can be avoided by deleting or renaming XCode.


All the swift libraries are in the application Frameworks folder and using otool -L on the executable shows that all the swift libraries (bar one) are in the linked library list and are simply prefixed by @rpath. None of the linked libraries refer to XCode.


I do note however that while the library libswiftos.dylib is present in the app bundle Frameworks, it is not listed by otool -L.


How can I prevent the app using potentially incompatible swift libraries in any old XCode, and force it to just use the libraries it is bundled with instead ?


Thanks for any pointers.

I found one other similar report here, but there was no soluton mentioned

Accepted Answer

It’s very likely that you have rpath problems. You should check the rpath for each Mach-O image in your app (the main app executable and any embedded frameworks, dynamic libraries, bundles, or command-line tools) to make sure they are set as expected.

You can do this with

otool
command. Run it with the
-l
option and look for rpath additions (the
LC_RPATH
command). Here’s what you’d expect to see for an app that uses Swift built with Xcode 10.2:
$ otool -l Your.app/Contents/MacOS/Your | grep -B 1 -A 2 RPATH
Load command 30
          cmd LC_RPATH
      cmdsize 32
         path /usr/lib/swift (offset 12)
--
--
Load command 31
          cmd LC_RPATH
      cmdsize 48
         path @executable_path/../Frameworks (offset 12)

This tells the dyld (dynamic linker) to search for rpath-relative libraries in

/usr/lib/swift
first and then in
@executable_path/../Frameworks
(where
@executable_path
is replaced by the path to the main executable). This causes dyld to find the system’s built-in Swift libraries first, and then fall back to the ones embedded in your app.

You should examine

LC_RPATH
for all Mach-O images, but I expect that you’ll only find this on the main app executable.

Once you’ve checked for rpath additions, you should check for rpath uses. You do this with the

-L
option:
$ otool -L Your.app/Contents/Frameworks/libswiftAppKit.dylib 
Your.app/Contents/Frameworks/libswiftAppKit.dylib:
    …
    @rpath/libswiftCoreData.dylib (compatibility version 1.0.0, current version 1001.0.69)
    …

Here you can see that the reference from

libswiftAppKit.dylib
to
libswiftCoreData.dylib
is rpath-relative, which is what we’d expect.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn,

that is very helpful indeed. otool -l on my app shows the LC_RPATH appears to be setup correctly:


Load command 55
          cmd LC_RPATH
      cmdsize 32
         path /usr/lib/swift (offset 12)
--
--
Load command 56
          cmd LC_RPATH
      cmdsize 40
         path @loader_path/../Frameworks (offset 12)
--
--
Load command 57
          cmd LC_RPATH
      cmdsize 48
         path @executable_path/../Frameworks (offset 12)


However I embed a framework which itself includes multiple other frameworks, including swift NIO. All of those frameworks have the following LC_RPATH


Load command 22
          cmd LC_RPATH
      cmdsize 32
         path /usr/lib/swift (offset 12)
--
--
Load command 23
          cmd LC_RPATH
      cmdsize 112
         path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx (offset 12)

The XCode project for that embedded framework was created with swift package manager. the Package,swift file contains no special references to /Applications/XCode/... however I note that in the generated XCode project settings, LD_RUNPATH_SEARCH_PATHS is set to


$(inherited)
$(TOOLCHAIN_DIR)/usr/lib/swift/macosx


and FRAMEWORK_SEARCH_PATHS is set to


$(inherited)
$(PLATFORM_DIR)/Developer/Library/Frameworks


Both of which end up pointing into the XCode bundle. Is it safe for me to remove those references to the XCode bundle, and why is swift package manager creating these references into XCodes internals in the first place ?

I have removed $(TOOLCHAIN_DIR)/usr/lib/swift/macosx from the Runpath settings for each of my 20 odd framework dependencies. The app complies and runs without a hitch. Thanks so much Quinn, it looks like this fixes the issue.


To dig into this further, I tried out a fresh swift project from github (attaswift/BigInt), and ran swift package generate-xcodeproj

Lo and behold, the Runpath settings in the resulting XCode project had the same hardcoded path into XCode internals.


I dug into the package manager source and discovered that this was a somewhat dubious intentional feature...


            // We currently force a search path to the toolchain, since we can't
            // establish an expected location for the Swift standard libraries.
            //
            // Note that this means that the built binaries are not suitable for
            // distribution, among other things.
            targetSettings.common.LD_RUNPATH_SEARCH_PATHS = ["$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"]

I find it mindboggling that the swift package manager knowingly hardcodes the library search path to the XCode internals, admitting in the comments that the resulting binary is not going to be suitable for distribution.


Surely it would make more sense to set the default search path to



            "$(inherited)", "@executable_path/../Frameworks", "@loader_path/Frameworks"



Infact just $(inherited) seems to be enough. XCode could have helped here, if the archiving or uploading step in XCode code gave a warning that there are LD_Runpaths in the frameworks that are 'unsuitable' for distribution. I expect other developers have been unknowingly caught out by this odd swift package manager feature.


Cheers,

Guy

I dug into the package manager source and discovered that this was a somewhat dubious intentional feature

Fascinating. Thanks for digging down to find the root cause of this.

I don’t really track Swift Package Manager in detail, so I can’t comment on this ‘feature’. My recommendation is that you bounce over to Swift Forums > Using Swift and see if folks over there have input on this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
App crashes when XCode on target system is out of date
 
 
Q