Question

I am dealing with a weird behavior of XCode:

dyld: Library not loaded: /Library/Frameworks/SBJson.framework/Versions/A/SBJson

Basically it ignores my Runpath Search Path (LD_RUNPATH_SEARCH_PATHS) configuration that is actually @loader_path/../Frameworks.

I am not able to load any embedded framework in this moment :/

otool says

otool -L /Users/kilian/Library/Developer/Xcode/DerivedData/r-ghohkslxtxgpnuepmblogfjtuefx/Build/Products/Debug/r.app/Contents/MacOS/r
/Users/kilian/Library/Developer/Xcode/DerivedData/r-ghohkslxtxgpnuepmblogfjtuefx/Build/Products/Debug/r.app/Contents/MacOS/r:
  /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 19.0.0)
  /Library/Frameworks/SBJson.framework/Versions/A/SBJson (compatibility version 1.0.0, current version 37.0.0)
  /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 945.0.0)
  /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)
  /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 1186.0.0)

P.S. If you're wondering if I added the copy to framework build phase, the answer is yes.

Était-ce utile?

La solution

The short version

The problem is that when you built SBJSON.framework, the install name wasn't configured to use @rpath. As a result, when your app goes to load the dynamic library, it's looking in /Library/Frameworks instead of where your app's runpath tells it to look.

To fix it, you need to change a build setting on the SBJSON.framework target. Change the the Dynamic Library Install Name setting to @rpath/${EXECUTABLE_PATH}. Then just build again, linking against the newly-built framework, and you're good to go.

The long version

Frameworks are, at the core, just dynamic libraries. This means that the code contained in the library is not embedded into your app, but rather pulled in from the SBJSON.framework bundle at runtime. In order to do this, your app needs to know where to look for the dynamic library.

The way this works is that when you link against a framework, the linker doesn't actually embed the whole library into your app. Instead, it just adds a small section that tells the app where to find the library when it runs. Of course, that means that the compiler has to know where the library will be at runtime. It finds that information by looking at the "Install Name" of the dynamic library.

A dynamic library's "Install Name" is essentially just the path where the library is expected to be. Historically, most dynamic libraries and frameworks have been shared system-wide. Things like Foundation.framework and CoreData.framework, for example, live in /System/Library/Frameworks, and all apps can reasonably expect to find them there. Thus, when CoreData.framework is built, its install name is set to /System/Library/Frameworks/.... When Xcode links your app against Core Data, it looks at the framework's install name and tells it to load in the framework at that path when the app is launched.

That's all fine and well, but it doesn't help you when you need to embed a framework inside your app. You don't know where the app will be located on the system at runtime. The user may run it from /Applications, but they might also run it from ~/Downloads. There's no single path you could provide as the install name that would always correctly point to the framework at runtime.

To deal with this, you can set the framework's install name to @loader_path/../Frameworks. When the dynamic loader sees @loader_path, it replaces it with the path of the currently-loading app. Using this install name would allow the framework to be installed inside the Frameworks folder of any app.

However, things still aren't perfect. The framework is still dictating where it should be placed. If another app wanted to place the framework inside a Libraries folder instead, for example, it's out of luck. The framework is in charge of where it can be placed, instead of the app. This is an inversion of the dependency tree, and is not ideal. The app should be able to load the framework from wherever it wants to stash it, regardless of what other frameworks do.

Thus, in OS X 10.5, @rpath was introduced. If the install name of a dylib or framework begins with @rpath, then the loader will turn around and ask the app what it's "Runpath search paths" are, and substitute those in. This allows the app to specify where its frameworks will live. By using @rpath, the framework delegates the decision back to the app. The app can use @loader_path if it wants, or can specify an absolute path if it wants. It could even specify a shared folder that a whole suite of apps will use.

Your problem

So, on to your problem. You're correctly setting the app's runpath search path to @loader_path/../Frameworks. However, the framework's install name isn't using @rpath; in fact, it's still using a hard-coded path to /Library/Frameworks/.... Since the framework's install name doesn't use @rpath, the app's runpaths aren't even consulted. It's simply trying to link in SBJSON from the /Library folder. Since it's not there, your app crashes before it even launches.

You need to change the install name of the SBJSON framework to use @rpath. Set the Dynamic Library Install Name setting to @rpath/${EXECUTABLE_PATH}. (${EXECUTABLE_PATH} is the relative path to the internal dynamic library, from the folder containing the framework.)

Once you've built the framework with the new install name, you should be able to link against the new framework, make sure it's copied into the app bundle's Frameworks/ folder, and you're good to go!

P.S. If this isn't all clear, Mike Ash did a pretty nice review of @rpath and friends. You can find it here.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top