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,怎么编译?
标准流程分两步:
- 配置(Configure):CMake读取
CMakeLists.txt,检测编译器、头文件、库等,生成构建系统文件(比如Makefile)。 - 构建(Build):用生成的构建系统去编译代码。
具体命令:
# 在项目根目录下创建一个 build 目录,用于存放构建产物
mkdir build
cd build
# 配置:告诉CMake源码在上一级目录
cmake ..
# 构建:编译生成可执行文件
cmake --build .
为什么要单独建一个 build 目录?
这叫「外部构建」。如果不建 build 目录,直接在源码目录下执行 cmake .,CMake会把一堆临时文件(CMakeCache.txt、CMakeFiles/ 等)散落在源码目录里,非常乱。我早期吃过这个亏,源码目录被搞得一团糟,后来就再也没用过内部构建。
2.6 常见错误与避坑
我总结几个新手容易踩的坑:
- 文件名写错:
main.cpp写成main.c,或者路径不对。CMake报错时会提示「找不到源文件」,检查一下文件名和路径。 - 忘记写
cmake_minimum_required:CMake 3.12 之后,如果不写这行,会弹出一个警告。虽然能编译,但不够规范。我建议养成习惯,每个CMakeLists.txt第一行都写版本声明。 - 在源码目录下执行
cmake .:前面说了,会导致源码目录混乱。请使用外部构建。 - 修改了
CMakeLists.txt后忘记重新配置:如果你改了CMakeLists.txt,需要重新执行cmake ..或点击IDE的「重新加载CMake项目」按钮。否则修改不会生效。
cmake_minimum_required 写的是 3.18,而Linux服务器上装的是 3.16。从那以后,我写版本号之前都会先确认一下目标平台的最低CMake版本。
2.7 本章小结
咱们回顾一下:
| 命令 | 作用 | 必填? |
|---|---|---|
cmake_minimum_required |
声明最低CMake版本 | 强烈建议 |
project() |
定义项目名称和语言 | 是 |
add_executable() |
指定可执行文件和源文件 | 是(如果你要生成可执行文件) |
这三行代码,就是CMake的骨架。你掌握了它们,就能编译任何单文件项目。下一章,咱们会引入头文件和多个源文件,看看CMake怎么管理更复杂的目录结构。
嗯,先到这里。去动手写一个你自己的 CMakeLists.txt 吧,哪怕只是打印个 "Hello World"。亲手跑通一次,比看十遍教程都管用。