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 其实比你想象的要简单得多。