cmake:如何在只有 target_link_directories (没有 target_link_libraries)的共享库中设置 rpath?

标签 c cmake dlopen rpath

我的项目目标如下:
我想从我的主可执行文件中加载一个库 (libfoo.so),该库 (libfoo.so) 会加载第二个库 (libbar.so)。

我不想在任何 filename 中指定相对或绝对路径我传递给 dlopen 的参数:即我希望我的代码读取“dlopen("libfoo.so", RTLD_LAZY) ”而不是 "/path/to/libfoo.so""../to/libfoo.so" .
我的理解是,查找共享库的方式(通过 libdl)是 1) 环境变量 LD_LIBRARY_PATH 的值,2)在二进制文件中“嵌入”RPATH,3)libdl已知的某些标准目录。

我的项目的目录结构是这样的:

.
├── CMakeLists.txt
├── build # this directory exists to perform an "out-of-source" build with "cmake .."
├── libs
│   ├── CMakeLists.txt
│   ├── bar.c
│   └── foo.c
└── main.c

main.c 可以成功执行dlopen("libfoo.so", RTLD_LAZY)
我通过添加 target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs) 来做到这一点CMakeLists.txt 中编译 main.c 的语句。
这似乎具有根据需要在 main 可执行文件中添加 RPATH 的效果:

$ objdump -x ./main | grep PATH
  RUNPATH              /home/user/code/scratch/3/build/libs

但是 foo.c 执行 dlopen("libbar.so", RTLD_LAZY) 失败即使我添加了 target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) CMakeLists.txt 中的声明。
我注意到尽管添加了 target_link_directories声明,libfoo.so 没有 RPATH:

$ objdump -x ./libs/libfoo.so | grep PATH
$

我尝试过的事情
看起来,RPATH 不会添加到共享库,除非至少有一个 target_link_libraries声明——即使它是一个“不必要的”库。
IE。如果我将 libfoo.solibbar.so 链接,则 libfoo.so 具有所需的 RPATH:

# Linking libbar works, but I'd prefer not to do this:
target_link_libraries(foo bar)

...结果:

$ objdump -x ./libs/libfoo.so | grep PATH
  RUNPATH              /home/user/code/scratch/3/build/libs

...如果我将“不必要的”共享库与 target_link_directories 一起链接,语句,那么 libfoo.so 也具有所需的 RPATH:

# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)

# Linking an unnecessary library, then doing target_link_directories also works:
target_link_libraries(foo dl)
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
$ objdump -x ./libs/libfoo.so | grep PATH
  RUNPATH              /home/user/code/scratch/3/build/libs

问题
我是否正确理解CMake的行为:target_link_directories如果至少有一个target_link_library,语句只会在共享库中产生相应的RPATH条目。共享库的声明(即使是“不必要的”库)?
如果这是正确的,有人可以解释一下理由吗?

是否有另一种“更干净”的方法将RPATH添加到共享库(最好使用 target_link_directories ),而无需任何“不必要的”语句(例如将 target_link_library 添加到不必要的库)?


代码/文件:

// main.c
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
  void* handle = dlopen("libfoo.so", RTLD_LAZY);
  if (!handle) {
    printf("dlopen error: %s\n", dlerror());
    return EXIT_FAILURE;
  }
  {
    void (*fptr)() = dlsym(handle, "func");

    if (fptr) { fptr(); }
  }
  dlclose(handle);
  return EXIT_SUCCESS;
}
// libs/foo.c
#include <dlfcn.h>
#include <stdio.h>

void func() {
  void* handle = dlopen("libbar.so", RTLD_LAZY);
  printf("here in libfoo!\n");
  if (!handle) {
    printf("dlopen error: %s\n", dlerror());
    return;
  }
  {
    void (*fptr)() = dlsym(handle, "func");
    if (fptr) { fptr(); }
  }
  dlclose(handle);
}
// libs/bar.c
#include <stdio.h>

void func() {
  printf("here in libbar!\n");
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(my_prj)

add_subdirectory(libs)

add_executable(main main.c)
target_link_libraries(main dl)
target_link_directories(main PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/libs)
# libs/CMakeLists.txt
add_library(bar SHARED bar.c)

add_library(foo SHARED foo.c)

# This is what I want, but it doesn't work:
target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

# Linking libbar works, but I'd prefer not to do this:
# target_link_libraries(foo bar)

# Linking an unnecessary library, then doing target_link_directories also works:
# target_link_libraries(foo dl)
# target_link_directories(foo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

最佳答案

这是一个工作版本。您只需调整 BUILD_RPATH属性适当,如果你写 install()规则,调整你的INSTALL_RPATH类似于我在this answer中写的内容。以下构建非常稳健,并调整 BUILD_RPATH :

cmake_minimum_required(VERSION 3.23)
project(test)

add_library(bar SHARED libs/bar.c)

add_library(foo MODULE libs/foo.c)
target_link_libraries(foo PRIVATE bar)

add_executable(main main.c)
target_link_libraries(main PRIVATE ${CMAKE_DL_LIBS})
set_property(TARGET main APPEND PROPERTY BUILD_RPATH "$<TARGET_FILE_DIR:foo>")

最后两行很重要。您必须链接到CMAKE_DL_LIBS可移植调用dlopen和 friend 。第二行确保目录包含 libfoo ,你知道main将加载,位于 RPATH .

这是控制台输出:

$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build

$ cmake --build build/ --verbose
[1/6] /usr/bin/cc -Dbar_EXPORTS  -O2 -g -DNDEBUG -fPIC -MD -MT CMakeFiles/bar.dir/libs/bar.c.o -MF CMakeFiles/bar.dir/libs/bar.c.o.d -o CMakeFiles/bar.dir/libs/bar.c.o -c /home/alex/test/libs/bar.c
[2/6] /usr/bin/cc -Dfoo_EXPORTS  -O2 -g -DNDEBUG -fPIC -MD -MT CMakeFiles/foo.dir/libs/foo.c.o -MF CMakeFiles/foo.dir/libs/foo.c.o.d -o CMakeFiles/foo.dir/libs/foo.c.o -c /home/alex/test/libs/foo.c
[3/6] /usr/bin/cc   -O2 -g -DNDEBUG -MD -MT CMakeFiles/main.dir/main.c.o -MF CMakeFiles/main.dir/main.c.o.d -o CMakeFiles/main.dir/main.c.o -c /home/alex/test/main.c
[4/6] : && /usr/bin/cc -fPIC -O2 -g -DNDEBUG   -shared -Wl,-soname,libbar.so -o libbar.so CMakeFiles/bar.dir/libs/bar.c.o   && :
[5/6] : && /usr/bin/cc -O2 -g -DNDEBUG  CMakeFiles/main.dir/main.c.o -o main  -Wl,-rpath,/home/alex/test/build  -ldl && :
[6/6] : && /usr/bin/cc -fPIC -O2 -g -DNDEBUG   -shared  -o libfoo.so CMakeFiles/foo.dir/libs/foo.c.o  -Wl,-rpath,/home/alex/test/build  libbar.so && :

$ ./build/main
here in libfoo!
here in libbar!

回答评论中的一些后续问题:

[W]hat is $<TARGET_FILE_DIR:foo>? Is TARGET_FILE_DIR a CMake variable (visible in CMakeLists.txts)?

它不是一个变量,它是一个generator expression 。这些表达式的值是在执行整个配置步骤后确定的。这样,我们就可以确定这个表达式将扩展到包含 libfoo.so实际目录。 ,而不仅仅是我们期望包含它的那个。

一般来说,只要有可能,我更喜欢使用生成器表达式而不是变量。它们倾向于使 CMake 编程更具声明性,而不是命令性,并且边缘情况较少。例如,用户可能设置值 CMAKE_RUNTIME_OUTPUT_DIRECTORY到一些意想不到的事情。如果您计算RPATH,这会破坏您的构建来自CMAKE_CURRENT_BINARY_DIR或其他什么。

[C]an you speak to the difference between target_link_libraries(main dl) (my version) and target_link_libraries(main PRIVATE ${CMAKE_DL_LIBS}) (your version)?

这里有两个区别,都很重要:

  1. 使用target_link_libraries 没有可见性说明符会将其置于一种奇怪的边缘状态,有点类似于 PRIVATE ,也许,取决于策略设置以及是否有任何其他调用 target_link_libraries有一个可见性说明符。为了清楚起见并避免这些陷阱,您应该始终指定 PRIVATE 之一, INTERFACE ,或PUBLIC .
  2. 使用 CMAKE_DL_LIBS 是链接到包含 dlopen 的库的正确方法。库函数族。您知道 HP-UX 使用 -ldld ?或者 AIX 使用 -lld ?或者 BSD(包括 macOS)没有为此提供单独的库?嗯,路过-ldl这些平台已损坏。

使用target_link_libraries在现代(后 CMake ~3.5)时代,没有可见性说明符或传递原始链接标志都是严重的代码味道。尽量避免他们,并在您认为无法做到时提出问题。

关于cmake:如何在只有 target_link_directories (没有 target_link_libraries)的共享库中设置 rpath?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73263834/

相关文章:

c++ - 将 matlab 数组(MAT 文件)转换为 C++ 数组

c - 如何确定 snprintf 目标缓冲区大小

c++ - 如何在 cmake 中使用 QML_ELEMENT

c - fgetc 逐行读取文件

CMake 查找从源代码编译的 protobuf

c++ - 使用 CMake 构建时缺少依赖项

python c 扩展,mac os 上的 dlopen 问题

c++ - 相对路径的 dlopen 错误

c++ - 在 C++ 中,可以从动态库中访问外部定义的全局变量吗?

c - C中的数组数组,其中数组的长度不同