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_PATHfind_program()的影响,取决于另一个变量:CMAKE_FIND_ROOT_PATH_MODE_PROGRAM

9.4 查找路径的三种交叉编译模式

CMake提供了三种模式,我画个图帮你理解:

CMAKE_FIND_ROOT_PATH_MODE_PROGRAM 三种模式 NEVER 忽略 CMAKE_FIND_ROOT_PATH 只在系统默认路径查找 适用场景: 查找宿主机工具时使用 比如:python、git、make find_program(PYTHON python) → 只在 /usr/bin 等路径找 ONLY 只在 CMAKE_FIND_ROOT_PATH 下找 忽略系统默认路径 适用场景: 查找交叉编译工具链 比如:arm-gcc、arm-ld find_program(ARM_GCC arm-gcc) → 只在 /opt/arm/sysroot 找 BOTH 先找 CMAKE_FIND_ROOT_PATH 找不到再去系统默认路径 适用场景: 需要灵活回退的情况 但容易找到宿主机工具 先找 /opt/arm/sysroot 找不到再找 /usr/bin 推荐:查找工具链用 ONLY,查找宿主机工具用 NEVER

这三种模式通过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():用来找工具链程序,记得加REQUIREDNO_DEFAULT_PATH
  • CMAKE_FIND_ROOT_PATH:设置查找的根目录,配合CMAKE_FIND_ROOT_PATH_MODE_PROGRAM使用
  • 三种模式:NEVER(忽略根目录)、ONLY(只用根目录)、BOTH(先根目录再系统)

我个人强烈建议:交叉编译时,查找工具链程序用ONLY模式,查找宿主机工具用NEVER模式。别偷懒用BOTH,它容易让你在不知不觉中用到宿主机工具,编译出来的东西跑在目标板上就崩了。

好了,今天就到这里。记住,工具链查找是交叉编译的第一道关卡,这里出问题,后面全是白搭。


公众号:蓝海资料掘金营,微信deep3321