17、第三方库交叉编译:使用find_package()在交叉编译环境中查找库、设置库路径
说实话,交叉编译里最让人头疼的,就是第三方库的查找问题。
你在本地开发环境里,find_package() 一调用,CMake 自动就把库路径、头文件路径全给你找好了。但一换到交叉编译环境,同样的代码,同样的 CMakeLists.txt,啪,报错——找不到库。我刚开始做嵌入式项目时,就被这个问题折腾了好几天。
为什么会这样?说白了,find_package() 默认找的是本地系统的库。你交叉编译器的目标平台是 ARM 或者 RISC-V,但 CMake 还在 x86 的 /usr/lib 里翻来翻去,能找到才怪。
所以这一章,我们就来聊聊怎么在交叉编译环境中,让 find_package() 乖乖地找到你交叉编译好的第三方库。
17.1 find_package() 的工作原理回顾
先简单回顾一下 find_package() 的两种模式:
- 模块模式(Module Mode):CMake 自带的 FindXXX.cmake 模块,或者你自己写的 FindXXX.cmake,放在 CMAKE_MODULE_PATH 里。
- 配置模式(Config Mode):库本身提供了 XXXConfig.cmake 或 xxx-config.cmake 文件,直接导入目标。
在交叉编译中,我个人的经验是:优先用配置模式。因为配置模式的文件通常由库的构建系统生成,里面包含了库的完整路径、编译选项、依赖关系等信息,更可靠。
核心思路:交叉编译时,你需要告诉 CMake 去哪里找这些 XXXConfig.cmake 文件。这个路径,就是交叉编译好的第三方库的安装目录。
17.2 设置库搜索路径的几种方法
嗯,这里要注意,方法不止一种。我按推荐程度从高到低列出来:
方法一:在工具链文件中设置 CMAKE_PREFIX_PATH
这是我最常用的方式。在工具链文件里,把交叉编译好的库的安装根目录加进去:
# my_toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 设置交叉编译器
set(CMAKE_C_COMPILER /opt/arm-gcc/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /opt/arm-gcc/bin/arm-linux-gnueabihf-g++)
# 关键:设置第三方库的搜索路径
set(CMAKE_PREFIX_PATH /opt/arm-sysroot/usr /opt/arm-libs)
# 其他设置...
这样,CMake 在调用 find_package() 时,就会自动去 /opt/arm-sysroot/usr 和 /opt/arm-libs 下找对应的 Config.cmake 文件。
小技巧:CMAKE_PREFIX_PATH 可以设置多个路径,用分号分隔。CMake 会按顺序搜索。
方法二:使用 变量
如果你只想针对某一个库设置路径,可以用这个库名对应的 <PackageName>_DIR 变量。比如你要找 OpenCV:
# 在工具链文件或 CMakeLists.txt 中
set(OpenCV_DIR /opt/arm-sysroot/usr/lib/cmake/opencv4)
find_package(OpenCV REQUIRED)
这个方法的好处是精确,不会影响其他库的查找。但缺点也很明显——你得知道每个库的具体安装位置。我在项目中遇到过,一个项目依赖七八个第三方库,每个都这么设,工具链文件变得又臭又长。
方法三:使用 CMAKE_FIND_ROOT_PATH
这个变量是 CMake 专门为交叉编译设计的。它的作用有点像给文件系统加了一个「根目录」:
# 在工具链文件中
set(CMAKE_FIND_ROOT_PATH /opt/arm-sysroot)
设置了 CMAKE_FIND_ROOT_PATH 后,CMake 在查找库、头文件、程序时,会优先在这个根目录下搜索。配合 CMAKE_FIND_ROOT_PATH_MODE_LIBRARY、CMAKE_FIND_ROOT_PATH_MODE_INCLUDE 等变量,可以精细控制搜索行为。
我曾经踩过的坑:CMAKE_FIND_ROOT_PATH 和 CMAKE_PREFIX_PATH 同时设置时,搜索顺序可能会让人困惑。我的建议是:如果用了 CMAKE_FIND_ROOT_PATH,就把 CMAKE_PREFIX_PATH 也设置成相对于这个根目录的路径,避免混乱。
17.3 实战:交叉编译一个依赖库的项目
光说不练假把式。我们来看一个完整的例子。
假设我们要交叉编译一个项目,它依赖 libpng 和 zlib。这两个库我们已经交叉编译好,安装到了 /opt/arm-sysroot/usr 下。
工具链文件 arm-linux-gnueabihf.cmake:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
# 设置 sysroot
set(CMAKE_SYSROOT /opt/arm-sysroot)
# 设置库搜索路径
set(CMAKE_FIND_ROOT_PATH /opt/arm-sysroot)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
项目的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(MyApp)
# 查找依赖库
find_package(PNG REQUIRED)
find_package(ZLIB REQUIRED)
add_executable(myapp main.c)
target_link_libraries(myapp PRIVATE PNG::PNG ZLIB::ZLIB)
然后构建:
mkdir build-arm && cd build-arm
cmake .. -DCMAKE_TOOLCHAIN_FILE=../arm-linux-gnueabihf.cmake
make
这样,CMake 就会在 /opt/arm-sysroot 下找到 libpng 和 zlib 的 Config.cmake 文件,顺利链接。
17.4 知识体系:交叉编译库查找的核心逻辑
我把整个流程画了一张图,方便你理解:
17.5 常见问题与避坑指南
我整理了几个实际开发中经常遇到的问题:
| 问题 | 原因 | 解决方法 |
|---|---|---|
| find_package 找到的是本地库 | CMAKE_FIND_ROOT_PATH_MODE_PACKAGE 没设成 ONLY | 在工具链文件中设置该变量为 ONLY |
| 提示找不到 Config.cmake | 库的安装路径没加到搜索路径中 | 检查 CMAKE_PREFIX_PATH 或 <PackageName>_DIR 是否正确 |
| 找到的库版本不对 | sysroot 中有多个版本 | 使用 find_package 的 EXACT 或 VERSION 参数指定版本 |
| 链接时提示 undefined reference | 库本身编译时用的编译器版本或选项不一致 | 确保第三方库和项目使用相同的工具链和编译选项 |
我曾经犯过的错:有一次我交叉编译一个项目,find_package 明明找到了库,但链接时总是报错。折腾了半天才发现,那个库是用旧版编译器编译的,而我项目用的新版编译器,ABI 不兼容。所以记住:第三方库的编译器和你的项目编译器必须一致。
17.6 总结
交叉编译中第三方库的查找,说白了就是两件事:
- 把库装对地方——交叉编译好的库要放到 sysroot 或指定的安装目录下。
- 让 CMake 找对地方——通过工具链文件设置 CMAKE_PREFIX_PATH、CMAKE_FIND_ROOT_PATH 等变量。
我个人习惯用 CMAKE_FIND_ROOT_PATH 配合 CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY,这样最干净,不会污染本地环境。你想想看,如果一不小心链接了本地库,那交叉编译出来的程序在目标板上跑不起来,排查起来多痛苦。
嗯,这一章的内容就到这里。记住:工具链文件是交叉编译的「总开关」,把库路径在这里设好,后面就省心了。
公众号:蓝海资料掘金营,微信deep3321