CMake 从入门到精通:完整教程 / 第 1 章:CMake 简介与背景
第 1 章:CMake 简介与背景
1.1 什么是 CMake
CMake(Cross-platform Make)是一个开源的、跨平台的构建系统生成器。它本身并不是构建工具,而是生成特定平台的构建文件(如 Unix Makefile、Ninja 文件、Visual Studio 项目文件等)的元构建系统(meta-build system)。
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ ┌──────────┐
│ CMakeLists │ ──> │ CMake │ ──> │ 构建文件 │ ──> │ 可执行文件│
│ .txt │ │ (配置阶段) │ │ (Makefile/Ninja) │ │ / 库文件 │
└─────────────┘ └─────────────┘ └─────────────────┘ └──────────┘
用户编写 cmake .. cmake --build . 最终产物
1.2 历史与发展
发展时间线
| 时间 | 事件 |
|---|---|
| 2000 年 | Kitware 公司为 ITK(Insight Segmentation and Registration Toolkit)项目开发了 CMake |
| 2001 年 | CMake 1.0 发布 |
| 2006 年 | KDE 4 采用 CMake 作为构建系统,推动了 CMake 的广泛普及 |
| 2011 年 | CMake 2.8 引入了 target_link_libraries 等现代特性 |
| 2014 年 | CMake 3.0 发布,引入生成器表达式(Generator Expressions) |
| 2018 年 | CMake 3.12 引入 FetchContent 模块 |
| 2020 年 | CMake 3.19 引入 CMake Presets(预设文件) |
| 2024 年 | CMake 3.30 持续改进 C++20 模块支持 |
项目名称由来
CMake = Cross-platform + Make
最初是为 C 语言设计的,但现在已经支持 C++、CUDA、Objective-C、Fortran、Swift 等多种语言。
1.3 设计理念
CMake 的设计遵循以下核心理念:
1.3.1 声明式而非命令式
CMake 鼓励使用声明式的方式来描述构建目标和依赖关系:
# 声明式:描述"是什么"
add_executable(myapp main.cpp utils.cpp)
target_link_libraries(myapp PRIVATE fmt::fmt)
target_include_directories(myapp PRIVATE include)
对比 Makefile 的命令式风格:
# 命令式:描述"怎么做"
myapp: main.o utils.o
g++ -o myapp main.o utils.o -lfmt -Iinclude
main.o: main.cpp
g++ -c main.cpp -Iinclude
utils.o: utils.cpp
g++ -c utils.cpp -Iinclude
1.3.2 目标(Target)为中心
现代 CMake 以 Target 为核心概念。每个目标携带自己的属性(源文件、编译选项、链接依赖、包含目录等),这些属性会自动传递:
# 创建一个库目标
add_library(mylib src/mylib.cpp)
target_include_directories(mylib PUBLIC include/)
target_compile_features(mylib PUBLIC cxx_std_17)
# 创建一个可执行文件目标
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# myapp 会自动获得 mylib 的 include 目录和 C++17 标准
1.3.3 跨平台抽象
CMake 在不同平台上提供统一的接口:
# 这段代码在 Linux、macOS、Windows 上都能正确工作
if(WIN32)
target_compile_definitions(myapp PRIVATE PLATFORM_WINDOWS)
elseif(APPLE)
target_compile_definitions(myapp PRIVATE PLATFORM_MACOS)
elseif(UNIX)
target_compile_definitions(myapp PRIVATE PLATFORM_LINUX)
endif()
1.4 CMake vs 其他构建系统
1.4.1 对比总表
| 特性 | CMake | Makefile | Ninja | Meson | Gradle | Bazel |
|---|---|---|---|---|---|---|
| 跨平台 | ✅ | ❌ | ⚠️ | ✅ | ✅ | ✅ |
| 语言支持 | C/C++/CUDA/Fortran/Swift 等 | 任意 | 任意 | C/C++/Fortran/Rust | Java/Kotlin/C++ | 任意 |
| 依赖管理 | FetchContent/vcpkg/Conan | 手动 | 手动 | Wrap | 内置 Maven 仓库 | 内置 Bzlmod |
| IDE 集成 | ✅ 非常好 | ❌ | ❌ | ⚠️ 一般 | ✅ 非常好 | ⚠️ 一般 |
| 学习曲线 | 中等 | 低 | 极低 | 低 | 中等 | 高 |
| 构建速度 | 较快 | 慢 | 极快 | 快 | 慢 | 极快 |
| 社区规模 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 采用项目 | Qt, LLVM, OpenCV, KDE | Linux Kernel | Chrome, LLVM(构建) | GNOME, systemd | Android | Google 内部 |
1.4.2 CMake vs Makefile
| 方面 | CMake | Makefile |
|---|---|---|
| 跨平台 | 自动生成各平台构建文件 | 仅限 Unix/MSYS |
| 依赖追踪 | 自动头文件依赖扫描 | 需手动或 gcc -MM |
| 可维护性 | 声明式,易于维护 | 大型项目维护困难 |
| 灵活性 | 较高,但有抽象层 | 极高,完全控制 |
| 构建速度 | 配置慢,构建快 | 构建较慢 |
Makefile 更适合:内核模块、嵌入式脚本构建等小型或极度定制化场景。
CMake 更适合:跨平台项目、团队协作、需要 IDE 支持的项目。
1.4.3 CMake vs Gradle
| 方面 | CMake | Gradle |
|---|---|---|
| 生态 | C/C++/Fortran 原生 | Java/Kotlin 原生,C++ 为插件 |
| 构建模型 | Target 为中心 | Task 为中心 |
| 依赖管理 | 通过 vcpkg/Conan | Maven 仓库原生支持 |
| 增量构建 | 通过 Ninja 实现 | 原生支持 |
| 配置语言 | CMake 脚本语言 | Groovy/Kotlin DSL |
| 构建速度 | 快 | 较慢(JVM 启动开销) |
Gradle 更适合:Android 开发、Java 为主的项目中包含 C++ 组件。
CMake 更适合:纯 C/C++ 项目、游戏引擎、科学计算库。
1.4.4 CMake vs Meson
| 方面 | CMake | Meson |
|---|---|---|
| 语法 | CMake 脚本 | Python-like |
| 后端 | 多种(Make/Ninja/VS) | 主要 Ninja |
| 依赖管理 | FetchContent/vcpkg/Conan | Wrap 系统 |
| 社区成熟度 | 非常成熟 | 快速成长 |
| 配置速度 | 较快 | 非常快 |
1.4.5 CMake vs Bazel
| 方面 | CMake | Bazel |
|---|---|---|
| 适用规模 | 小到大型 | 中到超大型 |
| 可重现性 | 一般 | 极强(沙箱构建) |
| 远程缓存 | 需额外配置 | 原生支持 |
| 学习成本 | 中等 | 高 |
| 部署复杂度 | 低 | 高(需要服务端) |
1.5 CMake 的工作流程
CMake 的构建分为两个阶段:
阶段一:配置(Configure)
mkdir build && cd build
cmake ..
此阶段 CMake 执行以下工作:
- 读取
CMakeLists.txt文件 - 检测编译器和平台特性
- 处理
find_package()查找依赖 - 生成缓存文件
CMakeCache.txt - 生成构建系统文件(如 Makefile 或
build.ninja)
阶段二:构建(Build)
cmake --build .
# 或直接
make
# 或
ninja
此阶段使用生成的构建文件来编译和链接源代码。
完整流程图
CMakeLists.txt ──┐
│ cmake configure
▼
┌───────────┐
│ CMakeCache │ 存储用户选项和检测结果
│ .txt │
└─────┬─────┘
│
▼
┌───────────┐
│ 构建文件 │ Makefile / build.ninja / .sln
└─────┬─────┘
│ cmake --build
▼
┌───────────┐
│ 目标产物 │ 可执行文件 / 库文件
└───────────┘
1.6 适用场景
✅ 推荐使用 CMake 的场景
| 场景 | 原因 |
|---|---|
| 跨平台 C/C++ 项目 | CMake 的核心优势 |
| 需要 IDE 支持 | 自动生成 VS/Xcode/CLion 项目 |
| 使用 CTest/CDash | CMake 原生测试支持 |
| 大型项目的依赖管理 | FetchContent + vcpkg/Conan |
| 开源库发布 | find_package 支持使得下游使用方便 |
| CI/CD 流水线 | 广泛支持,配置灵活 |
⚠️ 可能不太适合的场景
| 场景 | 建议 |
|---|---|
| 纯 Java/Kotlin 项目 | 使用 Gradle |
| 纯 Python 项目 | 使用 setuptools/poetry |
| Android NDK 开发 | 可以用 CMake,但 Gradle 是更自然的选择 |
| 极小项目(单文件) | 直接 g++ main.cpp 更简单 |
| 需要完全可重现构建 | 考虑 Bazel |
1.7 CMake 的版本演进要点
了解版本特性有助于选择合适的最低版本:
| CMake 版本 | 重要特性 |
|---|---|
| 3.0 | 生成器表达式、cmake_minimum_required 提升 |
| 3.1 | target_compile_features、C++ 标准设置 |
| 3.7 | cmake_parse_arguments 内置、Android 支持 |
| 3.11 | FetchContent 模块 |
| 3.13 | target_link_directories、add_link_options |
| 3.16 | target_precompile_headers、Unity Build |
| 3.19 | CMake Presets(CMakePresets.json) |
| 3.20 | cmake_path 命令、预设版本 2 |
| 3.21 | FILE_SET 支持、C++23 标准 |
| 3.24 | FIND_PACKAGE_ARGS in FetchContent |
| 3.25 | SYSTEM 属性在 FetchContent 中 |
| 3.28 | C++20 模块支持改进 |
1.8 业务场景:选择构建系统
场景:新启动的跨平台 C++ 项目
假设你是一个 5 人团队,要开发一个跨 Linux、macOS、Windows 的网络服务框架:
需求分析:
├── 需要跨平台? → 是
├── 使用 C++17? → 是
├── 依赖第三方库(OpenSSL, Protobuf)? → 是
├── 团队成员使用不同 IDE? → 是(VSCode、CLion、Visual Studio)
├── 需要 CI/CD 集成? → 是
└── 评估结果: → CMake 是最佳选择 ✅
选择 CMake 的理由:
- 所有主流 IDE 原生支持
find_package生态覆盖主要 C++ 库- vcpkg/Conan 提供丰富的包管理
- CI 平台(GitHub Actions、GitLab CI)广泛支持
1.9 扩展阅读
- CMake 官方文档
- Professional CMake: A Practical Guide — Craig Scott 著,最好的 CMake 参考书
- Modern CMake — Modern CMake 在线教程
- Effective Modern CMake — 最佳实践总结
- CMake Examples — 实用示例集