2、CMake基础语法:CMakeLists.txt文件结构、cmake_minimum_required、project、add_executable、message命令

好,咱们正式开始写CMake。说实话,很多初学者一上来就被各种CMakeLists.txt吓住了,觉得这玩意儿比C++代码还难懂。其实你想想看,它就是个配置文件,告诉编译器怎么干活而已。我当年刚接触CMake时,也是从这几条命令开始的,一步步摸索过来的。

2.1 CMakeLists.txt 文件结构

每个CMake项目,根目录下必须有一个CMakeLists.txt。这是CMake的入口文件,就像C++程序的main函数一样。我习惯把它叫做「项目的说明书」。

一个典型的CMakeLists.txt长这样:

cmake_minimum_required(VERSION 3.16)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)

add_executable(my_app main.cpp utils.cpp)

target_include_directories(my_app PRIVATE include)

嗯,结构其实很清晰:

  • 开头:声明CMake最低版本
  • 第二行:定义项目名称、版本、语言
  • 中间:添加可执行文件或库
  • 后面:配置各种编译选项、依赖关系

说白了,CMakeLists.txt就是从上往下执行的脚本。每条命令都有它的作用,咱们一条条拆开讲。

我的小习惯:我会在CMakeLists.txt里加一些空行和注释,把不同功能块隔开。比如「编译选项区」、「依赖管理区」、「测试配置区」。这样半年后回来看,自己还能看懂。

2.2 cmake_minimum_required — 版本底线

这条命令写在最前面,告诉CMake:「低于这个版本就别跑了」。为什么要有这个?因为不同版本的CMake,语法和功能有差异。我曾经在项目里用过3.10版本的特性,结果同事的机器上装的是3.5,一运行就报错,折腾了半天。

cmake_minimum_required(VERSION 3.16)

这里有几个要点:

  • VERSION 关键字不能少
  • 版本号建议写你实际测试过的版本
  • 如果项目用了较新的特性(比如3.14才有的FetchContent),就写3.14
注意:不要为了兼容性把版本写得太低。比如你用了3.20的特性,却写3.10,那CMake不会报错,但编译时会莫名其妙失败。我见过有人debug了一整天,最后发现是版本号写错了。

2.3 project — 给项目起个名

project命令定义项目的元信息。我个人觉得这是CMakeLists.txt里最「有仪式感」的一行——它标志着你的代码正式成为一个项目。

project(MyApp VERSION 1.0.0 LANGUAGES CXX)

参数说明:

参数 含义 示例
项目名称 你的项目叫什么 MyApp
VERSION 版本号,可选 1.0.0
LANGUAGES 使用的编程语言 CXX (C++)、C、Fortran

为什么推荐写VERSION?因为CMake会自动生成一些变量,比如 PROJECT_VERSIONMyApp_VERSION,后面做版本管理时特别方便。我在做持续集成时,经常用这些变量来打标签。

至于LANGUAGES,默认是C和CXX。如果你只用C++,写CXX就够了,能少检测一些编译器,加快配置速度。

2.4 add_executable — 生成可执行文件

这是最常用的命令之一。它的作用就是告诉CMake:「我要编译一个可执行程序,源文件是这些」。

add_executable(my_app main.cpp utils.cpp)

第一个参数是目标名称,第二个及以后是源文件列表。目标名称可以随便起,但建议和项目名保持一致,或者用下划线连接。我习惯用项目名的小写形式。

这里有个坑:源文件路径是相对于当前CMakeLists.txt的。如果你把源文件放在src目录下,就要写成:

add_executable(my_app src/main.cpp src/utils.cpp)

或者更优雅的方式——用变量:

set(SOURCES src/main.cpp src/utils.cpp)
add_executable(my_app ${SOURCES})
核心要点:add_executable只是声明了目标,还没告诉编译器怎么编译。后面我们会用target_include_directories、target_link_libraries来补充细节。

2.5 message — 调试利器

message命令就是CMake里的printf。它可以在配置阶段打印信息,帮你排查问题。我调试CMakeLists.txt时,用得最多的就是它。

message("Hello from CMake!")
message(STATUS "Project version: ${PROJECT_VERSION}")
message(WARNING "This is a warning")
message(FATAL_ERROR "Something went wrong, stop!")

message有几种模式:

  • 无前缀:直接打印,不带任何标记
  • STATUS:普通信息,前面会加两个横线,看起来更规范
  • WARNING:警告,会显示为黄色(在终端里)
  • FATAL_ERROR:致命错误,打印后直接终止CMake配置

我曾经在排查一个链接错误时,在CMakeLists.txt里加了十几条message,打印各种变量的值。最后发现是一个路径写错了。嗯,从那以后我写CMake时,总会顺手加几条message,方便后面调试。

实用技巧:用 message(STATUS "---") 打印分隔线,能让输出更清晰。比如在打印变量列表前后各加一条,一眼就能看出范围。

2.6 知识体系总览

下面这张图,把本章的核心命令和它们的关系画出来了。你可以把它当作CMakeLists.txt的「骨架」:

CMakeLists.txt 核心命令结构 CMakeLists.txt cmake_minimum_required 设置CMake最低版本 project 定义项目名称/版本/语言 add_executable 声明可执行文件目标 message 打印调试信息 这四条命令构成了CMakeLists.txt的基础骨架,其他命令都是在此基础上扩展

2.7 实战:写一个完整的CMakeLists.txt

咱们把今天学的串起来,写一个能用的例子。假设项目结构如下:

my_project/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   └── hello.cpp
└── include/
    └── hello.h

CMakeLists.txt内容:

cmake_minimum_required(VERSION 3.16)

project(HelloWorld VERSION 1.0.0 LANGUAGES CXX)

message(STATUS "Configuring HelloWorld v${PROJECT_VERSION}")

add_executable(hello src/main.cpp src/hello.cpp)

target_include_directories(hello PRIVATE include)

message(STATUS "Build target: hello")

你看,整个文件就十几行。配置时CMake会打印:

-- Configuring HelloWorld v1.0.0
-- Build target: hello
-- Configuring done
-- Generating done

干净利落。这就是CMake的魅力——用最少的代码,做最多的事。

总结一下
• cmake_minimum_required 是「门槛」,写在第一行
• project 是「身份证」,定义项目基本信息
• add_executable 是「生产线」,把源文件变成可执行文件
• message 是「对讲机」,帮你随时了解配置状态

嗯,基础语法就这些。别小看这四条命令,它们能撑起90%的小型项目。后面章节我们会在这个基础上,加入库管理、测试、安装等功能,一步步把CMakeLists.txt变强大。


公众号:蓝海资料掘金营,微信deep3321