CMake: Run-time error (dyld: Library not loaded) for dynamically linked resources on MacOS

CMake changes the RPATH for all installed targets upon running make install.

Imagine building both a shared library and an executable as part of the same CMake project. In order to be able to run the executable, it has to be able to dynamically load the shared library at runtime. Therefore, CMake by default adds the full (absolute) path to the dynamic library in the build tree to the executable's rpath. This is very convenient for developing, as we can run the executable straight from the build tree, but we probably would not want to ship the executable that way.

That's why CMake will change the rpath upon install to only contain portable paths (ie. remove the entry pointing to the build tree). That is, unless you put your shared library into one of the system default locations, the executable won't find it anymore after installing.

CMake does allow you though to specify an install rpath that will replace the removed build tree entry with your specified one. See the INSTALL_RPATH and INSTALL_RPATH_USE_LINK_PATH target properties for details.

Since all of this rpath stuff is 100% platform-dependent, OSX comes with its own, special rules. A pretty comprehensive explanation can be found on the (unfortunately rather outdated) CMake wiki:

Unlike other UNIXes, the Darwin linker, dyld, locates dependent dynamic libraries using the full path to each dylib. For example, in an executable "foo", the full paths recorded are the install names for each dependent dylib. And the library "/usr/lib/libSystem.dylib" has an install name of "/usr/lib/libSystem.B.dylib" as given by "otool -D". When linked into "foo", "foo" has a dependency on "/usr/lib/libSystem.B.dylib". This dependency can be seen with "otool -L foo". For relocatable binaries, @executable_path, @loader_path and @rpath are available to use. In the "foo" example, @executable_path and @loader_path are substituted for the location of "foo". @rpath is substituted with the RPATHs in "foo" to locate dependent dylibs. Thus the RPATH mechanism comes into play. The linker will search for @rpath/ dependencies in the following order:

  • DYLD_LIBRARY_PATH - an environment variable which holds a list of directories
  • RPATH - a list of directories which is linked into the executable. These can contain @loader_path and @executable_path.
  • builtin directories - /lib /usr/lib
  • DYLD_FALLBACK_LIBRARY_PATH - an environment variable which holds a list of directories

You should be able to solve this by tuning the respective target properties, but it is rather fiddly and can be quite a pain to get right.