注:此文已作废,本文存在若干事实性错误以及误导,在最新一篇文章中将重新说明。
前言
毕业前帮师兄写框架程序时,以及最近折腾公司内部项目的编译,都遇到一些以前没有遇到的问题,这里简单地记一些。
本文要讲的也就是 rpath
,即 relative path 的缩写,最初遇到这个坑时是在写 cmake
时,直接 make
生成的程序能够链接到指定的动态库,但是 make install
之后发现就链接失效了。
示例项目
这里举个简单的例子来复现,目录结构为:
1 | $ tree . |
代码组织方式是: src
目录为库目录,其源码会编译成动态库 libfoo.so
,example
为示例目录,其源码会包含 src
目录的头文件,并链接到动态库 libfoo.so
。具体代码也不长,依次贴出:
./CMakeLists.txt
:
1 | cmake_minimum_required(VERSION 3.5) |
./src/CMakeLists.txt
:
1 | add_library(foo SHARED foo.c) |
./src/foo.h
:
1 |
|
./src/foo.c
:
1 |
|
./example/CMakeLists
:
1 | add_executable(main main.c) |
./example/main.c
:
1 |
|
编译、安装及测试
老方法,新建一个临时目录用来存放中间文件,以下命令在项目根目录下执行,将动态库和可执行程序安装到根目录下的 lib
和 bin
目录,然后回到根目录:
1 | mkdir build && cd build |
此时目录结构为:
1 | $ tree . -I build |
首先我们运行 build
目录下的可执行文件,并查看其连接的 libfoo.so
路径:
1 | $ ./build/example/main |
这里 /home/xyz/RPATH
是我的项目根目录绝对路径,可以发现 make
生成的可执行文件,链接的是绝对路径,并且运行也没问题。但是再看看安装后的可执行文件和链接的库:
1 | $ ./bin/main |
我们会发现可执行文件失去了链接,因此要运行 main
,必须手动将动态库添加到系统路径中:
1 | $ LD_LIBRARY_PATH=./lib ./bin/main |
问题在哪
这个问题最初出现在我帮师兄写的框架当中,当时也找到了stackoverflow上的讨论帖:cmake: “make install” does not link against libraries in Ubuntu
简单翻译下:
系统首先会在 /etc/ld.so.conf
文件配置的路径中寻找动态库(在我的系统上该文件记录的是 /etc/ld.so.conf.d
目录下的所有 *.conf
文件),如果找不到,则有以下4个选项:
- 将库安装到系统默认路径比如
/lib
和/usr/lib
(但可能因为没有权限而无法实施); - 编辑系统范围的搜索路径(同样可能因为没有权限而无法实施);
- 设置
LD_LIBRARY_PATH
(就像我们上节末尾所做的,但它会覆盖系统路径,也就是说可能会优先选择自己的库而不是系统路径的同名库); - 设置
RPATH
,告诉可执行文件该到哪寻找它的库。
OK,现在来看问题的产生原因:RPATH
在 make install
后会被自动地清除。为什么会这样呢?因为 cmake
安装的可执行文件和动态库的相对路径,可能和 make
生成的不一样,因此无法自动记住。
cmake的解决方法
当然,cmake
本身也提供了解决方法,参见:RPATH handling。
不想看官网的长篇大论的话,针对本文的示例,在项目根目录的 CMakeLists.txt
中添加:
1 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") |
make install
安装时可以看到如下提示信息:
1 | -- Installing: /home/xyz/RPATH/bin/main |
这种方式还是设置的绝对路径,也就是 cmake
安装目录下的 lib
子目录,然后可以发现安装后的 main
成功链接到了 libfoo.so
,并且改变 main
路径,仍然可以链接到 libfoo.so
:
1 | $ ldd bin/main | grep foo |
类似地,我们可以把 CMAKE_INSTALL_RPATH
指定为相对路径 ../lib
,但这样的话局限性比较大,也就是说必须保证动态库在 $PWD/../lib
下,比如按这种方式编译安装后:
1 | $ ldd ./bin/main | grep foo |
但这种方式也有个优点,也就是说哦,只要动态库在当前工作目录的相对路径 ../lib
下,就能链接到该动态库,此时可以写一个脚本,和 main
放在同一目录:
1 |
|
第一行是取得脚本目录的绝对路径,第2行是进入该路径,这样只要动态库在可执行文件(以及运行脚本)的相对路径 ../lib
下,无论从哪个目录调用该脚本,都能成功使可执行文件链接到该动态库:
1 | $ ls bin/ |
不过既然借助了辅助脚本了,实际上在脚本里手动设置 LD_LIBRARY_PATH
为相对路径看起来更简单一些。
GCC的解决方法
为什么说到这个呢?因为我最近在使用公司内部项目的时候,发现自己的测试代码一直出错,查看日志,竟然源码路径来自其他用户的个人目录。也就是说是其他用户编译了动态库,然后使用超级权限将其安装到了系统目录。
对于这种情况,可以用 LD_LIBRARY_PATH
覆盖,但是如果修改了目录后,每次都要重新设置 LD_LIBRARY_PATH
,此时用 gcc
的链接选项就行了,还是对这个项目,手动用 gcc
进行编译:
1 | $ cd src/ |
注意,-Wl,-rpath
这个选项必不可少,它指定了 RPATH
的相对路径,为此,我将原来的 libfoo.so
放在系统目录 /lib64
下,然后修改 foo.c
(打印 "new foo"
而不是 "foo"
)后编译成动态库放在 src
目录下,测试如下:
1 | $ cd example/ |
和刚才 cmake
设置 RPATH
的测试结果一样,只要当前工作目录满足和动态库所在目录的相对路径是 RPATH
,那么运行可执行文件所链接到的动态库就是相对路径的动态库。