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

2

9

Problem

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   

Minimum example

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.

Details

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".

Wrap-up

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?

Introject answered 7/12, 2017 at 14:54 Comment(0)
L
14

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.

Lamrert answered 7/12, 2017 at 16:21 Comment(2)
What a fantastic answer. Thank you so much!!! Finally I understand how this works. Indeed, if I add the flag -DCMAKE_INSTALL_RPATH_USE_LINK_PATH="ON" to the command cmake command, the problem is solved!Introject
I fully agree that the linking of shared libraries is a bit fiddly. Before Mac OS ElCapitan, I usually relied on the DYLD_LIBRARY_PATH trick, but this "hack" does not always work since Apple introduced the System Integrity Protection, a problem I dealt with in an earlier SO postIntroject
T
2

This fixed the issue for me:

set(CMAKE_MACOSX_RPATH OFF)

add_library(your-lib SHARED)
Theresa answered 24/4, 2019 at 21:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.