2、第一个CMake项目:单文件编译、CMakeLists.txt基本结构、cmake_minimum_required、project()命令、add_executable()

好,咱们正式开始动手写第一个CMake项目。

说实话,很多初学者一上来就被CMake的各种复杂语法吓住了。其实没必要。一个最简单的CMake项目,核心就三个命令。你只要掌握这三个,就能跑通90%的单文件编译场景。我当年刚接触CMake时,就是从这三行代码开始的。

2.1 一个最简单的例子

假设你有一个 main.cpp,里面就一个 main 函数,打印个 "Hello CMake"。你想把它编译成可执行文件。传统做法是直接敲 g++ main.cpp -o hello。但用CMake,我们需要写一个 CMakeLists.txt。

嗯,这个文件名是固定的,大小写敏感。我见过有人写成 cmakelists.txt,结果CMake死活不认。注意,是 CMakeLists.txt,C大写,L大写,中间没有空格。

来看代码:

cmake_minimum_required(VERSION 3.10)
project(HelloWorld)
add_executable(hello main.cpp)

就这三行。是不是很简单?

咱们一行一行拆开看。

2.2 cmake_minimum_required:版本声明

第一行:cmake_minimum_required(VERSION 3.10)。

这行命令告诉CMake:这个项目至少需要3.10版本的CMake才能构建。如果用户机器上装的CMake版本低于3.10,CMake会直接报错退出。

为什么要加这个?

因为CMake的语法和功能在不同版本间有变化。比如早期版本不支持某些命令,或者某些命令的行为在新版本中改了。你想想看,如果你用了一个新特性,但用户的CMake太老,编译时就会出各种莫名其妙的错误。与其让用户排查半天,不如一开始就明确告诉他:你的CMake版本不够,请升级。

注意: 版本号不要写得太高。我见过有人直接写 3.28,结果同事的机器上装的是3.20,项目直接编译不了。建议写一个你确认能工作的最低版本。我个人习惯写 3.10 或 3.16,这两个版本覆盖了绝大多数常用功能。

2.3 project():项目名称与语言

第二行:project(HelloWorld)。

这行命令干了三件事:

  • 给项目取了个名字,叫 HelloWorld。
  • 自动创建了两个变量:PROJECT_NAME 和 PROJECT_SOURCE_DIR,分别保存项目名和源码根目录。
  • 默认启用了C和C++语言支持。

你可能想问:如果我只想编译纯C代码,不想让CMake去检测C++编译器,怎么办?

可以指定语言:

project(HelloWorld LANGUAGES C)

或者同时指定多个:

project(MyProject LANGUAGES C CXX Fortran)

我在项目中遇到过一个问题:一个纯C项目,没写 LANGUAGES C,结果CMake默认启用了C++。虽然也能编译通过,但构建日志里多了一堆C++编译器的检测信息,看着很不干净。后来我养成了习惯,只要项目是纯C,就明确指定 LANGUAGES C。

2.4 add_executable():生成可执行文件

第三行:add_executable(hello main.cpp)。

这行命令告诉CMake:我要生成一个可执行文件,名字叫 hello,它的源码是 main.cpp。

语法很简单:

add_executable(目标名称 源文件1 [源文件2 ...])

目标名称 hello 就是最终生成的可执行文件名。在Windows上会生成 hello.exe,在Linux/macOS上会生成 hello。

如果有多个源文件呢?直接往后加:

add_executable(hello main.cpp utils.cpp helper.cpp)

嗯,这里要注意:源文件之间用空格分隔,不是逗号。我刚开始写CMake时,习惯性地写了逗号,结果CMake报错说找不到文件。后来才反应过来,CMake的列表是用分号或空格分隔的,不是逗号。

2.5 完整构建流程

写好了 CMakeLists.txt,怎么编译?

标准流程分两步:

  1. 配置(Configure):CMake读取 CMakeLists.txt,检测编译器、头文件、库等,生成构建系统文件(比如Makefile)。
  2. 构建(Build):用生成的构建系统去编译代码。

具体命令:

# 在项目根目录下创建一个 build 目录,用于存放构建产物
mkdir build
cd build

# 配置:告诉CMake源码在上一级目录
cmake ..

# 构建:编译生成可执行文件
cmake --build .

为什么要单独建一个 build 目录?

这叫「外部构建」。如果不建 build 目录,直接在源码目录下执行 cmake .,CMake会把一堆临时文件(CMakeCache.txt、CMakeFiles/ 等)散落在源码目录里,非常乱。我早期吃过这个亏,源码目录被搞得一团糟,后来就再也没用过内部构建。

小技巧: 如果你用的是CLion、VS Code(配合CMake插件)或Visual Studio,这些IDE会自动帮你做外部构建。你只需要点一下「运行」按钮就行。但理解背后的命令行流程,对排查问题很有帮助。

2.6 常见错误与避坑

我总结几个新手容易踩的坑:

  • 文件名写错:main.cpp 写成 main.c,或者路径不对。CMake报错时会提示「找不到源文件」,检查一下文件名和路径。
  • 忘记写 cmake_minimum_required:CMake 3.12 之后,如果不写这行,会弹出一个警告。虽然能编译,但不够规范。我建议养成习惯,每个 CMakeLists.txt 第一行都写版本声明。
  • 在源码目录下执行 cmake .:前面说了,会导致源码目录混乱。请使用外部构建。
  • 修改了 CMakeLists.txt 后忘记重新配置:如果你改了 CMakeLists.txt,需要重新执行 cmake .. 或点击IDE的「重新加载CMake项目」按钮。否则修改不会生效。
曾经有个坑: 我在一个跨平台项目里,Windows上编译好好的,Linux上却报错。排查了半天,发现是 cmake_minimum_required 写的是 3.18,而Linux服务器上装的是 3.16。从那以后,我写版本号之前都会先确认一下目标平台的最低CMake版本。

2.7 本章小结

咱们回顾一下:

命令 作用 必填?
cmake_minimum_required 声明最低CMake版本 强烈建议
project() 定义项目名称和语言 是
add_executable() 指定可执行文件和源文件 是(如果你要生成可执行文件)

这三行代码,就是CMake的骨架。你掌握了它们,就能编译任何单文件项目。下一章,咱们会引入头文件和多个源文件,看看CMake怎么管理更复杂的目录结构。

嗯,先到这里。去动手写一个你自己的 CMakeLists.txt 吧,哪怕只是打印个 "Hello World"。亲手跑通一次,比看十遍教程都管用。