预设与工具链:CMakePresets.json,工具链文件,交叉编译入门
说实话,CMake 的项目配置,早期是个挺让人头疼的事。你想想看,同一个项目,要在 Windows 上编译,要在 Linux 上编译,还要给嵌入式 ARM 板子交叉编译。每次换环境,都得重新敲一堆 -D 参数,或者改 CMakeLists.txt 里的逻辑。我个人习惯是,能自动化的事情绝不手动重复。所以 CMakePresets.json 这个东西一出来,我就觉得,嗯,这才是现代构建该有的样子。
CMakePresets.json:统一配置的利器
CMakePresets 是 CMake 3.19 引入的特性。它的核心作用,说白了就是把所有构建配置集中到一个 JSON 文件里。你不需要再记那些长长的命令行参数了。
一个典型的 CMakePresets.json 文件长这样:
{
"version": 3,
"configurePresets": [
{
"name": "default",
"displayName": "默认配置",
"description": "使用 Ninja 生成器,Debug 模式",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_STANDARD": "17"
}
},
{
"name": "release",
"displayName": "发布配置",
"description": "Release 模式,优化全开",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_FLAGS": "-O3 -march=native"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default"
},
{
"name": "release",
"configurePreset": "release"
}
]
}
有了这个文件,你只需要执行:
cmake --preset=release
cmake --build --preset=release
搞定。我在项目中遇到过团队里有人总记不住 -DCMAKE_BUILD_TYPE=Release 怎么拼,用了 Presets 之后,这种问题再也没出现过。
inherit 字段。你可以定义一个基础 preset,然后其他 preset 继承它,只覆盖需要改的字段。这样能减少大量重复配置。
工具链文件(Toolchain):跨平台编译的基石
工具链文件,是 CMake 里一个非常强大但容易被忽视的功能。它本质上是一个 CMake 脚本文件,在项目配置的早期阶段被加载,用来告诉 CMake:你要用什么编译器、链接器、以及各种工具的位置。
为什么要用工具链文件?因为有时候你没法直接靠 -DCMAKE_C_COMPILER=xxx 搞定一切。比如交叉编译时,不光编译器要换,系统根目录、链接器、归档器、甚至 pkg-config 的路径都得换。工具链文件就是干这个的。
一个典型的 ARM 交叉编译工具链文件 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++)
# 指定系统根目录(sysroot)
set(CMAKE_SYSROOT /opt/arm-linux-gnueabihf/sysroot)
# 查找程序时,只在 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 -DCMAKE_TOOLCHAIN_FILE=arm-linux-gnueabihf.cmake ..
cmake 配置时指定。之后如果切换工具链,必须删除 CMakeCache.txt 和 CMakeFiles 目录,重新配置。我曾经因为忘了删缓存,折腾了整整一个下午才发现问题。
交叉编译入门:从原理到实践
交叉编译,简单说就是在一台机器(宿主机)上编译出能在另一台机器(目标机)上运行的程序。比如你在 x86 的笔记本上编译 ARM 架构的 Linux 程序。
为什么会需要交叉编译?因为目标机往往资源有限——跑个 Linux 系统都勉强,更别说跑 GCC 了。你想想看,一个嵌入式设备可能只有 64MB 内存,而编译 Chromium 需要 16GB 内存。所以,我们得在强大的宿主机上完成编译工作。
交叉编译的核心三要素:
- 交叉编译器:能生成目标架构机器码的编译器。比如
arm-linux-gnueabihf-gcc。 - 系统根目录(sysroot):目标机的根文件系统副本,包含目标机上的库和头文件。
- 工具链文件:告诉 CMake 以上两样东西在哪。
下面我用一个完整的例子,带你走一遍交叉编译的流程。
实战:为 ARM Linux 交叉编译一个 Hello World
项目结构:
cross-hello/
├── CMakeLists.txt
├── arm-linux-gnueabihf.cmake
└── main.cpp
main.cpp:
#include <iostream>
int main() {
std::cout << "Hello from ARM!" << std::endl;
return 0;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(CrossHello LANGUAGES CXX)
add_executable(hello main.cpp)
# 可选:设置输出名称前缀,方便识别
set_target_properties(hello PROPERTIES
OUTPUT_NAME "hello_arm"
)
然后执行:
mkdir build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../arm-linux-gnueabihf.cmake ..
make
编译完成后,用 file 命令检查生成的可执行文件:
$ file hello_arm
hello_arm: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
看到 ARM 字样了吗?说明编译成功了。把这个文件拷贝到 ARM 开发板上,就能直接运行。
结合 CMakePresets 与工具链文件
你完全可以把工具链文件路径写到 Presets 里。这样团队里每个人都能一键交叉编译,不用再记那些复杂的路径。
{
"version": 3,
"configurePresets": [
{
"name": "arm-debug",
"displayName": "ARM Debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/arm-debug",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/arm-linux-gnueabihf.cmake",
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
然后:
cmake --preset=arm-debug
cmake --build --preset=arm-debug
简单明了。我在公司里推广这套方案后,新同事上手交叉编译的时间从半天缩短到了十分钟。
知识体系总览
下面这张图,帮你理清本章的核心逻辑:
避坑指南
最后,分享几个我踩过的坑:
- 缓存问题:切换工具链时,一定要清空
build目录。CMake 会缓存编译器检测结果,不清空的话,它会用旧的编译器继续编译。 - sysroot 路径:工具链文件里的
CMAKE_SYSROOT路径不要写错。我曾经把路径写成了/opt/arm-sysroot但实际是/opt/arm-sysroot/(多了一个斜杠),结果 CMake 找不到任何库,报了一堆莫名其妙的链接错误。 - Presets 版本:CMakePresets.json 的
version字段要写对。目前最新是 version 6,但如果你用旧版 CMake(比如 3.19),只能用 version 3。我建议至少用 CMake 3.23 以上,支持更完整的 Presets 特性。 - 工具链文件中的变量作用域:工具链文件里设置的变量,比如
CMAKE_C_COMPILER,是全局的。不要在 CMakeLists.txt 里用set(... CACHE ...)去覆盖它,否则会出问题。
嗯,以上就是本章的全部内容。记住,工具链文件和 Presets 是 CMake 项目工程化的基石。把它们用好,你的构建系统会变得非常清晰和可维护。
公众号:蓝海资料掘金营,微信 deep3321