1. CMake 初识:什么是 CMake?为什么需要 CMake?CMake 与 Makefile 的关系
1.1 从一个尴尬的场景说起
我记得刚入行那会儿,接手了一个老项目。
项目里有三四个模块,每个模块都有自己的编译脚本。有的用 Makefile,有的直接写 shell 脚本,还有一个干脆用 Visual Studio 的 .sln 工程文件。每次换平台编译,我都得手动改一堆路径和链接参数。最崩溃的一次——我在 Linux 上调了三天链接错误,结果发现是 Makefile 里写死了一个 Windows 下的库路径。
嗯,那时候我就想:有没有一种工具,能让我写一次编译规则,然后在任何平台上都能跑?
这就是 CMake 诞生的原因。
1.2 什么是 CMake?
CMake 的全称是 Cross-platform Make。说白了,它是一个跨平台的构建系统生成器。
你写一份 CMakeLists.txt 文件,描述你的项目结构、源文件、依赖库、编译选项。然后 CMake 会根据你当前的操作系统和编译器,自动生成对应的构建文件——比如 Linux 上的 Makefile、Windows 上的 Visual Studio 工程、macOS 上的 Xcode 工程。
一句话总结:CMake 不直接编译代码,它负责生成「用来编译代码的脚本」。
我个人习惯把 CMake 比作「翻译官」。你跟它说中文(CMakeLists.txt),它帮你翻译成英语(Makefile)、日语(VS 工程)、法语(Xcode 工程)……你只需要维护一份源文件,剩下的交给它。
1.3 为什么需要 CMake?
你可能会问:我直接用 Makefile 不行吗?
行,但仅限于小项目。一旦项目变大,Makefile 的痛点就暴露了:
- 平台差异:Linux 用
g++,Windows 用cl.exe,macOS 用clang++。Makefile 里写死了编译器路径,换个平台就得重写。 - 依赖管理:第三方库的头文件路径、库文件路径,不同系统下完全不同。手动维护这些路径,简直是噩梦。
- IDE 支持:团队里有人用 VS Code,有人用 CLion,有人用 Visual Studio。Makefile 没法直接生成 IDE 工程文件。
- 可读性:Makefile 的语法其实挺晦涩的。尤其是变量替换、条件判断、函数调用那一套,新手看了直接劝退。
我在项目中遇到过最典型的例子:一个同事把 Makefile 写成了 800 行,里面全是 ifneq 和 $(eval) 的嵌套。后来他离职了,没人敢动那个文件。
CMake 解决了这些问题:
| 痛点 | Makefile 的做法 | CMake 的做法 |
|---|---|---|
| 跨平台 | 手写多份 Makefile | 一份 CMakeLists.txt,自动生成 |
| 依赖管理 | 手动指定路径 | find_package 自动查找 |
| IDE 支持 | 不支持 | 生成 VS/Xcode 工程 |
| 语法复杂度 | 高(GNU Make 语法) | 低(类似脚本语言) |
1.4 CMake 与 Makefile 的关系
很多人刚接触 CMake 时,会搞混它和 Makefile 的关系。其实很简单:
- Makefile 是构建系统的「执行者」。它定义了编译规则,然后
make命令按照规则去编译。 - CMake 是构建系统的「生成者」。它根据你的描述,生成 Makefile(或其他构建文件)。
所以典型的构建流程是:
# 第一步:CMake 生成 Makefile
cmake -B build
# 第二步:make 执行编译
cmake --build build
你想想看,如果没有 CMake,你得手动写 Makefile。有了 CMake,你只需要写 CMakeLists.txt,然后让 CMake 帮你生成 Makefile。说白了,CMake 是 Makefile 的「上层封装」。
一个小技巧:我刚开始学 CMake 时,喜欢在生成 Makefile 后,打开看看它长什么样。这能帮你理解 CMake 到底帮你做了哪些事。比如 add_executable 在 Makefile 里对应了一大堆编译命令——嗯,看到这些你就知道 CMake 替你省了多少事。
1.5 一个最简单的例子
光说不练假把式。我们来看一个最基础的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(HelloWorld)
add_executable(hello main.cpp)
三行代码,干了什么?
cmake_minimum_required:指定 CMake 的最低版本。我建议你写 3.10 以上,太老的版本有些功能不支持。project:定义项目名称。这个名称会被 CMake 用来生成一些变量,比如PROJECT_NAME。add_executable:告诉 CMake,我要生成一个可执行文件,名字叫hello,源文件是main.cpp。
然后执行:
cmake -B build
cmake --build build
你就会在 build 目录下得到一个 hello(或 hello.exe)可执行文件。
注意:千万不要在源码目录下直接运行 cmake . 生成构建文件。我曾经这么干过,结果构建文件和源文件混在一起,清理起来特别麻烦。一定要用 -B 指定一个单独的构建目录(比如 build)。这是 CMake 推荐的「外部构建」方式。
1.6 本章小结
到这里,你应该对 CMake 有了一个基本的认识:
- CMake 是一个跨平台的构建系统生成器,不是编译器,也不是 Makefile 的替代品。
- 它解决了跨平台、依赖管理、IDE 支持、可维护性等痛点。
- CMake 生成 Makefile,Makefile 执行编译——两者是上下游关系。
- 一个最简单的 CMakeLists.txt 只需要三行代码,就能完成一个可执行文件的构建。
下一章,我们会深入 CMakeLists.txt 的语法细节,看看那些常用的命令到底是怎么工作的。到时候你会发现,CMake 其实比你想象的要简单得多。