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

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

问题

在 MacOS 上,我在 运行 时遇到依赖于动态链接资源的 CMake 项目的链接问题 – 但仅在安装项目之后!当我只构建二进制文件而不安装它时,不会出现此问题。

$ ./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   

最小示例

我能够在由 CMakeLists.txtmain.cpp 组成的最小设置中重现该问题。我链接到的库称为 VTK (v7.1.1),它是使用共享库构建的(有关详细信息,请参见下文)。

# 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}")

main.cpp甚至没有使用任何 VTK 对象。

// main.cpp
#include <iostream>

int main (int argc, char* argv[])
{
    std::cout << "Hello world!" << std::endl;
    return 0;
}

我使用以下命令构建项目。我设置的标志 CMAKE_PREFIX_PATH 是为了给 CMake 一个关于在哪里可以找到 VTK 库的提示。

$ 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

在构建文件夹中执行 testapp 时,一切正常:

$ ./testapp
Hello world!
$ cp testapp $INSTALLDIR/testapp
$ $INSTALLDIR/testapp
Hello world!

但是,如果我 运行 INSTALLDIR 中的可执行文件,我会得到一个 运行-time 错误:

$ $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    

当然,如果我删除 CMakeLists.txt 中的 target_link_libraries() 指令,问题就会消失。

那么在安装 CMake 项目时到底发生了什么?我的情况出了什么问题?我测试了不同的 CMake 版本(3.5、3.9 和 3.10)——但行为是一样的。

详情

显然,MacOS 上的 RPATH 机制没有为该示例正确设置。

这是 testapp 二进制链接结构的摘录:

$ 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)
...

因为它可能会影响 VTK 库(另一个 CMake 项目)的构建方式:为了 python 支持,必须设置项目标志 VTK_WRAP_PYTHON=ONBUILD_SHARED_LIBS=ON。安装前缀设置为 CMAKE_INSTALL_PREFIX="$VTK_INSTALL_DIR"。为确保在 运行 时间找到资源,必须通过 CMAKE_MACOSX_RPATH=ONCMAKE_INSTALL_RPATH="$VTK_INSTALL_DIR/lib".

额外启用 RPATH 支持

总结

我在概念上有什么错误?使用 make install 安装项目时会发生什么?这个问题可以在 CMake 中解决吗?还是仅与 VTK 以及共享库的构建方式有关?

CMake 在 运行ning make install 后更改所有已安装目标的 RPATH。

想象一下,将共享库和可执行文件构建为同一个 CMake 项目的一部分。为了能够 运行 可执行文件,它必须能够在 运行 时动态加载共享库。因此,CMake 默认情况下将 构建树中的动态库 的完整(绝对)路径添加到可执行文件的 rpath。这对于开发来说非常方便,因为我们可以 运行 直接从构建树中获取可执行文件,但我们可能不希望以这种方式发布可执行文件。

这就是为什么 CMake 会在安装时将 rpath 更改为仅包含 可移植 路径(即删除指向构建树的条目)。也就是说,除非您将共享库放入系统默认位置之一,否则可执行文件在安装后将不再找到它。

CMake 确实允许您指定一个安装 rpath,它将用您指定的构建树条目替换已删除的构建树条目。有关详细信息,请参阅 INSTALL_RPATH and INSTALL_RPATH_USE_LINK_PATH 目标属性。

因为所有这些 rpath 东西都是 100% 平台相关的,OSX 有它自己的特殊规则。可以在(不幸的是相当过时)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

您应该可以通过调整相应的目标属性来解决这个问题,但这相当繁琐,而且要做到正确可能会很痛苦。

这解决了我的问题:

set(CMAKE_MACOSX_RPATH OFF)

add_library(your-lib SHARED)