20、CMake交叉编译:设置CMAKE_TOOLCHAIN_FILE、工具链文件编写、指定系统名称与处理器

交叉编译这事儿,说难不难,说简单也不简单。我刚开始接触嵌入式开发那会儿,最头疼的就是在PC上写好代码,一交叉编译就报一堆莫名其妙的链接错误。后来我才明白——问题的根子,往往出在工具链文件没写对

CMake的交叉编译,核心就三个东西:CMAKE_TOOLCHAIN_FILE、工具链文件本身、以及系统名称和处理器的指定。咱们一个一个来拆解。

20.1 为什么需要CMAKE_TOOLCHAIN_FILE?

你想想看,CMake默认是给本地编译用的。它自动检测你电脑上的编译器、链接器、头文件路径。但交叉编译时,目标平台(比如ARM开发板)上的工具链和PC完全不同。

CMAKE_TOOLCHAIN_FILE 就是一个“剧本”。它告诉CMake:
“嘿,别用你本机的gcc了,用这个arm-linux-gnueabihf-gcc。头文件也别去/usr/include找,去/opt/arm/sysroot/usr/include找。”

我个人习惯,把这个文件放在项目根目录的 cmake/ 文件夹下,命名成 arm-linux-gnueabihf.cmake 这种格式,一看就知道是给哪个平台用的。

20.2 工具链文件怎么写?

工具链文件本质上就是一个CMake脚本。它主要做三件事:

  1. 指定交叉编译器(C和C++)
  2. 指定目标系统名称和处理器架构
  3. 设置查找路径(sysroot、库路径等)

来看一个我实际项目中用过的例子:

# 文件名: 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++)

# 查找策略:先找目标系统的工具链
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)

# sysroot(目标系统的根文件系统路径)
set(CMAKE_SYSROOT /opt/arm/sysroot)

这里有个坑,我曾经踩过——CMAKE_FIND_ROOT_PATH_MODE_PROGRAM 一定要设成 NEVER。为什么?因为查找程序(比如find_program)时,如果去目标系统的sysroot里找,大概率找不到,反而会报错。让它只在本地找就对了。

20.3 指定系统名称与处理器

CMAKE_SYSTEM_NAMECMAKE_SYSTEM_PROCESSOR 这两个变量,是CMake判断目标平台的关键。

变量常见值说明
CMAKE_SYSTEM_NAMELinux, Windows, Android, Generic目标操作系统
CMAKE_SYSTEM_PROCESSORarm, aarch64, x86_64, mips目标CPU架构

设置这两个变量后,CMake会自动启用交叉编译模式。它会去查找对应的编译器、链接器,并且会使用 CMAKE_SYSROOT 作为头文件和库的根路径。

我记得有一次,我忘了设置 CMAKE_SYSTEM_NAME,结果CMake还是用本地编译器编译。折腾了半天才发现——CMake判断是否交叉编译,就是看这个变量有没有被显式设置。你不设,它就默认是本地编译。

20.4 使用工具链文件

写好了工具链文件,怎么用?很简单,在cmake命令行里指定:

mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-linux-gnueabihf.cmake

或者,你也可以在CMakeLists.txt里直接设置(但不推荐,因为会写死):

set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/cmake/arm-linux-gnueabihf.cmake")

我个人更推荐命令行方式。这样同一个项目,可以轻松切换不同平台——比如给ARM编译时用这个工具链文件,给x86编译时用另一个。

20.5 一个完整的实战例子

假设我们要给一个ARM Linux开发板编译一个简单的hello程序。项目结构如下:

my_project/
├── CMakeLists.txt
├── cmake/
│   └── arm-linux-gnueabihf.cmake
└── src/
    └── main.c

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(hello_arm C)

add_executable(hello src/main.c)

src/main.c

#include <stdio.h>

int main() {
    printf("Hello from ARM!\n");
    return 0;
}

编译步骤:

mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-linux-gnueabihf.cmake
make

生成的 hello 可执行文件,就是ARM架构的。你可以用 file hello 命令验证:

$ file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, ...

看到“ARM”字样,说明交叉编译成功了。

20.6 知识体系图

下面这张图,帮你理清CMake交叉编译的核心逻辑:

CMake交叉编译核心流程 CMAKE_TOOLCHAIN_FILE 工具链文件 (arm-linux-gnueabihf.cmake) CMAKE_SYSTEM_NAME → Linux CMAKE_SYSTEM_PROCESSOR → arm CMake进入交叉编译模式 核心:通过工具链文件,CMake知道“为谁编译”和“用什么编译”

20.7 避坑指南

我曾经踩过的坑,你千万别再踩:

  • 忘记设置CMAKE_SYSTEM_NAME:CMake会认为你在本地编译,结果还是用gcc。交叉编译根本没生效。
  • CMAKE_FIND_ROOT_PATH_MODE_PROGRAM设成ONLY:find_program会去sysroot里找,大概率找不到,然后报错。设成NEVER就对了。
  • 工具链文件路径写错:CMake不会报错,但会静默忽略。你查半天,发现还是本地编译。
  • sysroot路径不对:链接时找不到目标系统的库,报一堆undefined reference。检查一下sysroot里有没有对应的.so或.a文件。

我的小技巧:

每次写完工具链文件,先跑一个简单的hello world验证。用file命令确认生成的可执行文件是目标架构的。这一步能省下后面大量调试时间。

20.8 总结

CMake交叉编译,说白了就是通过一个工具链文件,告诉CMake三件事:

  • 目标系统是什么(Linux/Windows/Android)
  • 目标处理器是什么(arm/aarch64/x86)
  • 用什么编译器来编译

工具链文件写好了,后面的事情CMake会自动处理。你只需要在cmake命令行里加一个 -DCMAKE_TOOLCHAIN_FILE=xxx.cmake 就行。

嗯,这部分内容就到这里。记住,交叉编译的坑大多出在工具链文件上——写对一次,后面就顺了。


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