8、系统名称与处理器:CMAKE_SYSTEM_NAME详解(Linux、Generic、Android等)、CMAKE_SYSTEM_PROCESSOR
好,咱们接着聊交叉编译。上一章我们把工具链文件的骨架搭起来了,但说实话,那个骨架里最核心的两个变量——CMAKE_SYSTEM_NAME 和 CMAKE_SYSTEM_PROCESSOR——我还没细讲。这两个家伙,才是真正决定「你要编译成什么玩意儿」的关键。
我刚开始用 CMake 做交叉编译时,就吃过它们的亏。当时给一个 ARM Linux 板子写工具链,随手把 CMAKE_SYSTEM_NAME 写成了 Linux,结果 CMake 自作主张检测了一堆宿主机上的库路径,编译出来根本跑不了。后来才明白,这玩意儿不是随便填的。
CMAKE_SYSTEM_NAME:告诉 CMake 你在为谁编译
说白了,CMAKE_SYSTEM_NAME 就是告诉 CMake:「嘿,我现在要编译的目标系统是什么?」。默认情况下,如果你不设置它,CMake 会认为你在为本机编译,也就是 CMAKE_HOST_SYSTEM_NAME 的值。但一旦你显式设置了它,CMake 就会切换到交叉编译模式。
常用的值有哪些?我列一下:
| 值 | 适用场景 | 我踩过的坑 |
|---|---|---|
Linux |
标准 Linux 系统(ARM、x86、RISC-V 等) | 注意它会启用 find_library 搜索系统路径,容易找到宿主机库 |
Generic |
裸机、RTOS、没有完整操作系统的环境 | 我曾经用它编译 FreeRTOS 项目,CMake 不会尝试检测任何系统特性 |
Android |
Android NDK 交叉编译 | 这个值会触发 CMake 内部一套特殊的 Android 处理逻辑 |
Windows |
MinGW 或交叉编译 Windows 程序 | 在 Linux 上交叉编译 Windows 程序时用 |
Darwin |
macOS / iOS 交叉编译 | 一般配合 Xcode 工具链使用 |
QNX |
QNX 实时操作系统 | 汽车电子项目中常见 |
嗯,这里要注意:Generic 这个值特别有意思。它告诉 CMake:「别管什么系统特性,你就当我是个裸板」。我在做 STM32 裸机项目时,就用的 Generic。CMake 不会去检查 dlopen、pthread 这些库,也不会尝试检测 sizeof(int)——因为根本没有操作系统在运行。
核心原则:CMAKE_SYSTEM_NAME 决定了 CMake 的「行为模式」。选错了,轻则编译警告,重则链接出一堆没用的符号。
CMAKE_SYSTEM_PROCESSOR:告诉 CMake 你的 CPU 是什么
这个变量指定目标处理器的架构。比如 arm、aarch64、x86_64、riscv64、mips 等等。
你可能会问:「CMake 拿这个干嘛用?」
其实它主要影响两件事:
- 编译器默认参数——有些工具链会根据处理器名字自动添加
-march或-mtune标志 - 系统库搜索路径——比如在 Linux 上,
aarch64会搜索/usr/aarch64-linux-gnu/lib这样的路径
我个人习惯把 CMAKE_SYSTEM_PROCESSOR 设置成工具链前缀中的那个架构名。比如工具链叫 arm-linux-gnueabihf-gcc,那我就写 arm。如果是 aarch64-linux-gnu-gcc,就写 aarch64。
小技巧:如果你不确定该填什么,可以运行 ${CROSS_COMPILE}gcc -dumpmachine,输出的前半部分通常就是处理器名。比如输出 arm-linux-gnueabihf,那处理器就是 arm。
实战:一个完整的工具链文件示例
光说不练假把式。咱们写一个给 ARM Cortex-A7 用的 Linux 工具链文件:
# 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++)
# 告诉 CMake 不要检测编译器(交叉编译下检测容易出错)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# 指定 sysroot(如果有的话)
set(CMAKE_SYSROOT /opt/arm-linux-gnueabihf/sysroot)
# 查找库和头文件的路径
set(CMAKE_FIND_ROOT_PATH /opt/arm-linux-gnueabihf/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)
你看,CMAKE_SYSTEM_NAME 和 CMAKE_SYSTEM_PROCESSOR 就两行,但它们是整个文件的基石。没有它们,CMake 就不知道自己在交叉编译。
Android 的特殊之处
Android 的情况比较特殊。当你设置 CMAKE_SYSTEM_NAME 为 Android 时,CMake 会启用一套内置的 Android NDK 支持逻辑。这时候你通常不需要手动指定编译器,而是通过 CMAKE_ANDROID_NDK 指向 NDK 路径。
我记得有一次帮朋友调试一个 Android 的 native 库,他死活编译不过。我一看,他把 CMAKE_SYSTEM_NAME 写成了 Linux,然后手动指定了 NDK 里的编译器。结果 CMake 找不到 Android 特有的库路径,链接阶段报了一堆 undefined reference。改成 Android 后,一切正常。
注意:Android 交叉编译时,CMAKE_SYSTEM_PROCESSOR 的值要填 armv7-a、aarch64、x86_64 等,而不是简单的 arm。因为 Android 的 ABI 区分很细,填错了会导致运行时崩溃。
Generic 的妙用:裸机与 RTOS
如果你在做嵌入式裸机开发(比如 STM32、ESP32),或者用 FreeRTOS、RT-Thread 这类 RTOS,Generic 就是你的好朋友。
为什么?因为 Generic 模式下,CMake 不会做任何系统相关的检测。它不会去检查 dlfcn.h 是否存在,不会尝试链接 libpthread,也不会检测 signals 支持。这些在裸机环境下统统没有,强行检测只会报错。
我曾经接手过一个项目,前任工程师用 Linux 作为 CMAKE_SYSTEM_NAME 来编译 FreeRTOS 项目。结果 CMake 在配置阶段就报错,说找不到 dlopen。我改成 Generic 后,问题迎刃而解。
# stm32f4-freertos.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
# 裸机编译不需要 sysroot
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)
知识体系:系统名称与处理器的决策树
为了让你更直观地理解这两个变量的关系,我画了一张图:
避坑指南:我踩过的几个雷
最后,分享几个我实际工作中遇到的坑:
- CMAKE_SYSTEM_NAME 和 CMAKE_SYSTEM_PROCESSOR 写反了——别笑,我真见过有人把处理器名填到系统名字段里。结果 CMake 报错说找不到名为
arm的操作系统。 - 大小写问题——
Linux首字母大写,Generic也是首字母大写。写成linux或generic小写,CMake 不认。 - 处理器名不要带版本号——比如
cortex-a7这种具体型号,应该填arm。具体优化参数通过CMAKE_C_FLAGS传递-mcpu=cortex-a7。 - Android 的处理器名要精确——
armv7-a和armv8-a不能混用,否则链接的库 ABI 不匹配。
好了,关于 CMAKE_SYSTEM_NAME 和 CMAKE_SYSTEM_PROCESSOR 就讲这么多。这两个变量虽然简单,但它们是交叉编译的「门神」。门神站对了,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321