9、查找工具链程序:find_program()与CMAKE_FIND_ROOT_PATH、查找路径的交叉编译模式
各位同学,咱们今天聊一个在交叉编译中特别容易踩坑的话题——查找工具链程序。
说白了,就是CMake怎么找到你交叉编译器、链接器、还有各种辅助工具。我刚开始做嵌入式交叉编译那会儿,就因为这个折腾了两天。明明工具链装好了,CMake就是找不到,最后发现是查找路径的配置出了问题。
9.1 为什么需要专门查找工具链程序?
你想想看,在本地编译时,gcc、ld这些工具都在系统默认路径里,CMake一找一个准。但交叉编译就不一样了——你的工具链可能放在/opt/arm-gcc/bin/,或者/home/tools/gcc-arm-none-eabi/bin/,甚至可能在一个U盘里。
CMake默认只会在系统标准路径下找程序。如果你不告诉它「嘿,去那边找」,它就傻乎乎地在/usr/bin、/usr/local/bin这些地方翻,当然找不到。
所以,我们需要两个东西:
- find_program():CMake用来查找可执行文件的命令
- CMAKE_FIND_ROOT_PATH:告诉CMake「你的搜索范围在这里」
9.2 find_program() 的基本用法
这个命令其实很简单,我直接上代码:
find_program(
ARM_GCC
NAMES arm-none-eabi-gcc arm-gcc
PATHS /opt/arm-gcc/bin
/home/tools/gcc-arm/bin
REQUIRED
)
这里我解释一下:
- ARM_GCC:变量名,找到后路径会存到这里
- NAMES:要查找的程序名字,可以给多个备选
- PATHS:指定搜索路径
- REQUIRED:找不到就报错,别偷偷摸摸失败
我在项目中习惯把工具链的查找放在一个单独的函数里,比如这样:
function(find_toolchain_program var_name program_name)
find_program(${var_name}
NAMES ${program_name}
PATHS ${TOOLCHAIN_BIN_DIR}
NO_DEFAULT_PATH
REQUIRED
)
if(NOT ${var_name})
message(FATAL_ERROR "找不到 ${program_name},请检查工具链路径")
endif()
endfunction()
# 使用示例
find_toolchain_program(CMAKE_C_COMPILER arm-none-eabi-gcc)
find_toolchain_program(CMAKE_CXX_COMPILER arm-none-eabi-g++)
find_toolchain_program(CMAKE_OBJCOPY arm-none-eabi-objcopy)
嗯,这里要注意NO_DEFAULT_PATH这个参数。加了它,CMake就不会去系统默认路径找了。交叉编译时我建议加上,避免不小心找到宿主机的gcc。
9.3 CMAKE_FIND_ROOT_PATH 的妙用
这个变量才是今天的重头戏。我个人觉得,理解CMAKE_FIND_ROOT_PATH是掌握交叉编译查找机制的关键。
它的作用很简单:给所有查找操作加一个「根目录」。
举个例子:
set(CMAKE_FIND_ROOT_PATH /opt/arm-gcc/sysroot)
设置了之后,CMake在查找头文件、库文件时,会优先在这个根目录下找。比如你找stdio.h,它就会去/opt/arm-gcc/sysroot/usr/include/stdio.h找。
但这里有个坑——CMAKE_FIND_ROOT_PATH对find_program()的影响,取决于另一个变量:CMAKE_FIND_ROOT_PATH_MODE_PROGRAM。
9.4 查找路径的三种交叉编译模式
CMake提供了三种模式,我画个图帮你理解:
这三种模式通过CMAKE_FIND_ROOT_PATH_MODE_PROGRAM变量控制:
| 模式 | 行为 | 推荐场景 |
|---|---|---|
| NEVER | 忽略 CMAKE_FIND_ROOT_PATH,只在系统路径查找 | 查找宿主机工具(python、git) |
| ONLY | 只在 CMAKE_FIND_ROOT_PATH 下查找 | 查找交叉编译工具链(gcc、ld) |
| BOTH | 先找 CMAKE_FIND_ROOT_PATH,找不到再找系统路径 | 需要回退机制时使用,但需谨慎 |
9.5 实战配置:一个完整的工具链查找示例
好了,理论说完了,咱们看一个实际项目中的配置。这是我去年做STM32MP1项目时用的:
# 工具链根路径
set(TOOLCHAIN_DIR /opt/stm32mp1/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf)
set(TOOLCHAIN_BIN_DIR ${TOOLCHAIN_DIR}/bin)
set(SYSROOT_DIR ${TOOLCHAIN_DIR}/arm-none-linux-gnueabihf/libc)
# 设置查找根路径
set(CMAKE_FIND_ROOT_PATH ${SYSROOT_DIR})
# 设置查找模式
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# 查找工具链程序
find_program(CMAKE_C_COMPILER
NAMES arm-none-linux-gnueabihf-gcc
PATHS ${TOOLCHAIN_BIN_DIR}
NO_DEFAULT_PATH
REQUIRED
)
find_program(CMAKE_CXX_COMPILER
NAMES arm-none-linux-gnueabihf-g++
PATHS ${TOOLCHAIN_BIN_DIR}
NO_DEFAULT_PATH
REQUIRED
)
find_program(CMAKE_ASM_COMPILER
NAMES arm-none-linux-gnueabihf-as
PATHS ${TOOLCHAIN_BIN_DIR}
NO_DEFAULT_PATH
REQUIRED
)
# 查找辅助工具
find_program(CMAKE_OBJCOPY
NAMES arm-none-linux-gnueabihf-objcopy
PATHS ${TOOLCHAIN_BIN_DIR}
NO_DEFAULT_PATH
REQUIRED
)
find_program(CMAKE_OBJDUMP
NAMES arm-none-linux-gnueabihf-objdump
PATHS ${TOOLCHAIN_BIN_DIR}
NO_DEFAULT_PATH
REQUIRED
)
find_program(CMAKE_SIZE
NAMES arm-none-linux-gnueabihf-size
PATHS ${TOOLCHAIN_BIN_DIR}
NO_DEFAULT_PATH
REQUIRED
)
message(STATUS "C编译器: ${CMAKE_C_COMPILER}")
message(STATUS "C++编译器: ${CMAKE_CXX_COMPILER}")
message(STATUS "汇编器: ${CMAKE_ASM_COMPILER}")
关键点:
- 所有查找都加了
NO_DEFAULT_PATH,防止误找到宿主机工具 - 查找模式全部设为
ONLY,严格限定在工具链目录 - 每个查找都加了
REQUIRED,找不到就报错,不藏着掖着
9.6 避坑指南
我曾经踩过的坑,你千万别再踩:
- 坑1:忘记设置 CMAKE_FIND_ROOT_PATH_MODE_PROGRAM
默认是NEVER,如果你没改,CMAKE_FIND_ROOT_PATH对find_program()完全无效。我当年就是没设这个,折腾了一下午。 - 坑2:NO_DEFAULT_PATH 和 ONLY 模式混用
其实NO_DEFAULT_PATH已经禁止了系统路径,ONLY模式也禁止了系统路径。两个一起用没问题,但别以为只设一个就够了。 - 坑3:路径末尾加不加斜杠
CMake对路径末尾的斜杠处理比较宽松,但我个人习惯不加,避免某些老旧版本出问题。 - 坑4:工具链名字带版本号
比如arm-gcc-9.2,如果你写死了版本号,换工具链就得改代码。我建议用软链接或者用NAMES给多个备选。
我的小技巧:
在工具链文件的最后,加一段验证代码,打印出所有找到的工具路径。这样一运行CMake,就能一眼看出有没有问题:
foreach(tool CMAKE_C_COMPILER CMAKE_CXX_COMPILER CMAKE_OBJCOPY CMAKE_SIZE)
if(NOT ${tool})
message(FATAL_ERROR "${tool} 未找到!")
else()
message(STATUS "${tool} = ${${tool}}")
endif()
endforeach()
9.7 总结一下
今天的内容其实就三个核心点:
- find_program():用来找工具链程序,记得加
REQUIRED和NO_DEFAULT_PATH - CMAKE_FIND_ROOT_PATH:设置查找的根目录,配合
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM使用 - 三种模式:NEVER(忽略根目录)、ONLY(只用根目录)、BOTH(先根目录再系统)
我个人强烈建议:交叉编译时,查找工具链程序用ONLY模式,查找宿主机工具用NEVER模式。别偷懒用BOTH,它容易让你在不知不觉中用到宿主机工具,编译出来的东西跑在目标板上就崩了。
好了,今天就到这里。记住,工具链查找是交叉编译的第一道关卡,这里出问题,后面全是白搭。
公众号:蓝海资料掘金营,微信deep3321