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脚本。它主要做三件事:
- 指定交叉编译器(C和C++)
- 指定目标系统名称和处理器架构
- 设置查找路径(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_NAME 和 CMAKE_SYSTEM_PROCESSOR 这两个变量,是CMake判断目标平台的关键。
| 变量 | 常见值 | 说明 |
|---|---|---|
| CMAKE_SYSTEM_NAME | Linux, Windows, Android, Generic | 目标操作系统 |
| CMAKE_SYSTEM_PROCESSOR | arm, 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交叉编译的核心逻辑:
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