On MacOS, I get linking problems at runtime for a CMake project that depends on dynamically linked resources – but only after installing the project! The problem does not occur when I only build the binary without installing it.
$ ./testapp
Hello world!
$ $INSTALLDIR/testapp
dyld: Library not loaded: @rpath/libvtkDomainsChemistryOpenGL2-7.1.1.dylib
Referenced from: /Users/normanius/workspace/installdir/testapp
Reason: image not found
[1] 76964 trace trap /Users/normanius/workspace/installdir/testapp
I am able to reproduce the problem in a minimal setup consisting of CMakeLists.txt
and main.cpp
. The library I am linking to is called VTK (v7.1.1) which has been built with shared libs (see below for further details).
# CMakeLists.txt
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(test)
# Test application.
add_executable(testapp
main.cpp)
# Find vtk (library that has to be linked to dynamically).
find_package(VTK REQUIRED)
include(${VTK_USE_FILE})
target_link_libraries(testapp ${VTK_LIBRARIES}) # <---- this causes the problem
# Install instructions.
install(TARGETS testapp DESTINATION "${CMAKE_INSTALL_PREFIX}")
The main.cpp
not even makes use of any VTK objects.
// main.cpp
#include <iostream>
int main (int argc, char* argv[])
{
std::cout << "Hello world!" << std::endl;
return 0;
}
I build the project with the following commands. The flag CMAKE_PREFIX_PATH
I set to give CMake a hint about where to find the VTK library.
$ INSTALLDIR="path/to/installation"
$ mkdir build && cd build
$ cmake .. -DCMAKE_PREFIX_PATH="$DEVPATH/lib/vtk/cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$INSTALLDIR"
$ make
$ make install
When executing the testapp
in the build folder, everything looks fine:
$ ./testapp
Hello world!
$ cp testapp $INSTALLDIR/testapp
$ $INSTALLDIR/testapp
Hello world!
However, if I run the executable in the INSTALLDIR
I get a run-time error:
$ $INSTALLDIR/testapp
dyld: Library not loaded: @rpath/libvtkDomainsChemistryOpenGL2-7.1.1.dylib
Referenced from: /Users/normanius/workspace/installdir/testapp
Reason: image not found
[1] 76964 trace trap /Users/normanius/workspace/installdir/testapp
Naturally, the problem goes away if I remove the target_link_libraries()
instruction in the CMakeLists.txt
.
So what exactly happens when installing a CMake project? And what goes wrong in my case? I tested different CMake versions (3.5, 3.9 and 3.10) - but the behaviour is the same.
Apparently, the RPATH mechanism on MacOS is not properly set up for the example.
This is an excerpt of the linking structure of the testapp
binary:
$ otool -L testapp
testapp:
@rpath/libvtkDomainsChemistryOpenGL2-7.1.1.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libvtkFiltersFlowPaths-7.1.1.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libvtkFiltersGeneric-7.1.1.dylib (compatibility version 0.0.0, current version 0.0.0)
@rpath/libvtkFiltersHyperTree-7.1.1.dylib (compatibility version 0.0.0, current version 0.0.0)
...
Because it may play a role how the VTK library (another CMake project) was built: For python support, one has to set the project flags VTK_WRAP_PYTHON=ON
and BUILD_SHARED_LIBS=ON
. The installation prefix was set to CMAKE_INSTALL_PREFIX="$VTK_INSTALL_DIR"
. To make sure that the resources are found at runtime, one has to additionally enable RPATH support via CMAKE_MACOSX_RPATH=ON
and CMAKE_INSTALL_RPATH="$VTK_INSTALL_DIR/lib"
.
What do I conceptually get wrong? What happens when installing the project with make install
? Can this problem be solved within CMake? Or is it related only to VTK and how the shared libs were built?
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.
This fixed the issue for me:
set(CMAKE_MACOSX_RPATH OFF)
add_library(your-lib SHARED)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With