11、库搜索路径:CMAKE_FIND_ROOT_PATH_MODE_LIBRARY、MODE_INCLUDE、MODE_PROGRAM 详解
好,咱们今天聊一个在交叉编译里特别容易踩坑的话题——库搜索路径模式。
说白了,就是 CMake 在找头文件、找库文件、找可执行程序的时候,到底该去哪里找?是去宿主系统(你电脑上)找,还是去目标系统(开发板/嵌入式设备)的 sysroot 里找?
这三个变量:CMAKE_FIND_ROOT_PATH_MODE_LIBRARY、CMAKE_FIND_ROOT_PATH_MODE_INCLUDE、CMAKE_FIND_ROOT_PATH_MODE_PROGRAM,就是用来控制这个行为的。我刚开始做交叉编译时,就是没搞懂它们,结果链接了一堆宿主机的 .so 文件,烧到板子上直接段错误。嗯,从那以后我再也不敢小看它们了。
11.1 这三个模式到底是干嘛的?
每个变量都有三种取值:
| 取值 | 含义 |
|---|---|
ONLY |
只在 CMAKE_FIND_ROOT_PATH 指定的路径里找 |
NEVER |
只在宿主系统路径里找,忽略 CMAKE_FIND_ROOT_PATH |
BOTH |
先找 CMAKE_FIND_ROOT_PATH,找不到再去宿主系统找 |
你想想看,这三个模式分别对应三种不同的查找策略。对于交叉编译来说,最常用的是 ONLY,因为我们不希望把宿主机上的库链进去。
核心原则:交叉编译时,LIBRARY 和 INCLUDE 通常设为 ONLY,PROGRAM 通常设为 NEVER。
为什么会这样?因为库和头文件必须来自目标系统的 sysroot,而可执行程序(比如编译器、链接器本身)是运行在宿主机上的,当然要在宿主系统里找。
11.2 实战:一个典型的工具链文件配置
我个人习惯在工具链文件里这样写:
# 设置 sysroot 路径
set(CMAKE_SYSROOT /opt/arm-sysroot)
# 设置查找根路径
set(CMAKE_FIND_ROOT_PATH /opt/arm-sysroot)
# 库和头文件只在 sysroot 里找
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# 可执行程序只在宿主系统找
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
这样配置之后,find_library() 只会去 /opt/arm-sysroot/usr/lib 等路径下找,find_path() 只会去 /opt/arm-sysroot/usr/include 找。而 find_program() 则会在 /usr/bin、/usr/local/bin 这些宿主路径里找。
小技巧:如果你不确定当前查找的是哪个路径,可以在 CMakeLists.txt 里临时加一句 message(STATUS "CMAKE_FIND_ROOT_PATH = ${CMAKE_FIND_ROOT_PATH}") 来打印出来看看。
11.3 三种模式的核心逻辑图
下面这张图帮你理清这三个变量是怎么协同工作的:
11.4 避坑指南:我曾经踩过的三个坑
坑一:LIBRARY 设成 NEVER
我曾经在一个项目里,把 CMAKE_FIND_ROOT_PATH_MODE_LIBRARY 设成了 NEVER。结果 CMake 在宿主机上找到了 libz.so,链接进去了。烧到 ARM 板子上,动态链接器找不到这个库,直接报错退出。折腾了两天才发现是这个问题。
警告:交叉编译时,LIBRARY 和 INCLUDE 绝对不要设成 NEVER!除非你明确知道自己在做什么。
坑二:PROGRAM 设成 ONLY
反过来,如果把 CMAKE_FIND_ROOT_PATH_MODE_PROGRAM 设成 ONLY,CMake 就会去 sysroot 里找编译器、链接器。但 sysroot 里通常没有这些工具,结果就是 find_program() 永远返回空,编译直接失败。
坑三:BOTH 模式的陷阱
BOTH 模式看起来挺安全——先找 sysroot,找不到再找宿主。但问题在于,如果 sysroot 里有一个旧版本的库,宿主系统里有一个新版本的库,CMake 会优先用 sysroot 里的旧库。你可能还以为用的是新库呢。我个人建议,除非你有特殊需求,否则别用 BOTH。
11.5 不同场景下的推荐配置
| 场景 | MODE_LIBRARY | MODE_INCLUDE | MODE_PROGRAM |
|---|---|---|---|
| 标准交叉编译 | ONLY | ONLY | NEVER |
| 本地编译(非交叉) | NEVER | NEVER | NEVER |
| 混合编译(部分库用宿主) | BOTH | BOTH | NEVER |
| 构建工具链自身 | NEVER | NEVER | NEVER |
我的习惯:写工具链文件时,我会在文件开头用注释写明这三个变量的用途,防止几个月后自己都忘了当初为什么这么设。代码是写给人看的,顺便让机器执行而已。
11.6 调试技巧:如何确认查找路径
如果你不确定 CMake 到底在哪个路径下找到了库,可以打开 CMake 的调试模式:
# 在命令行执行 cmake 时加上 --debug-find 参数
cmake --debug-find -S . -B build
# 或者在 CMakeLists.txt 里设置
set(CMAKE_FIND_DEBUG_MODE TRUE)
这样 CMake 会打印出每一次查找的详细过程,包括去了哪些路径、找到了什么、没找到什么。我在排查链接问题时,经常用这招。
嗯,关于这三个模式,核心就是记住一句话:库和头文件去 sysroot 找,可执行程序在宿主机找。搞清楚了这一点,交叉编译的链接问题至少能少一半。
公众号:蓝海资料掘金营,微信 deep3321