3、CMake基础语法:注释、变量定义与引用、set()命令、message()命令、数学运算

各位同学,欢迎来到第三章。前两章我们聊了CMake能干什么,以及怎么搭建环境。从这章开始,咱们要真正动手写CMakeLists.txt了。

说实话,我刚开始学CMake的时候,觉得语法挺别扭的。跟C++、Python都不一样,大小写不敏感,变量引用还要加个${}。但用久了你会发现,它其实很简洁,设计思路就是「把事情说清楚,别绕弯子」。

这一章,我们就把CMake最基础的语法过一遍。包括怎么写注释、怎么定义和使用变量、两个最常用的命令set()message(),以及简单的数学运算。这些都是你以后写CMake脚本的「砖瓦」。

第三章:CMake基础语法知识体系 CMake基础语法 # 与 #[[ ]] 注释 变量定义与引用 set() 命令详解 message() 输出 math() 数学运算 常见坑与最佳实践 打好基础,后面写复杂脚本才不慌

3.1 注释:给代码加点「说明书」

写注释这件事,我个人觉得比写代码本身还重要。你想想看,三个月后你回头看自己写的CMakeLists.txt,如果没有注释,你可能得花半小时回忆当初为什么这么写。

CMake支持两种注释方式:

  • 单行注释:用 # 开头,从 # 到行尾都是注释。
  • 多行注释:用 #[[ 开头,]] 结束,中间可以跨多行。
# 这是一个单行注释
set(MY_VAR "hello")  # 行尾也可以加注释

#[[
  这是一个多行注释
  可以写好几行
  适合用来临时屏蔽一大段代码
]]
我的小习惯:我一般用单行注释解释「为什么这么做」,而不是「做了什么」。比如 # 这里用STATIC是因为动态库在目标平台上有兼容性问题,而不是 # 设置变量为STATIC。后者看代码本身就能明白,前者才是真正的信息量。

3.2 变量定义与引用:CMake的「便签纸」

变量在CMake里就像便签纸——你把一个值写在上面,贴在那里,需要的时候撕下来用。定义变量用 set() 命令,引用变量用 ${变量名}

举个例子:

set(PROJECT_NAME "MyApp")
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)

message("项目名称:${PROJECT_NAME}")
message("版本号:${VERSION_MAJOR}.${VERSION_MINOR}")

这里有几个要点:

  • 变量名习惯用大写,但不是强制要求。我个人喜欢全大写,一眼就能看出是CMake变量。
  • 字符串可以不加引号,但如果字符串里有空格,必须加引号。
  • 引用变量时,${} 会直接展开成变量的值。
注意:如果引用的变量不存在,CMake不会报错,而是展开成空字符串。我曾经因为这个bug排查了半天——拼写错了变量名,结果编译出来的东西路径全是错的。所以,message() 是你的好朋友,多打印多检查。

3.3 set() 命令详解:不只是赋值

set() 是CMake里最常用的命令之一。它的基本语法是:

set(<变量名> <值> [CACHE TYPE DOCSTRING FORCE])

我们先用最简单的形式:

set(SRC_FILES main.cpp utils.cpp network.cpp)
set(DEBUG_FLAGS -g -O0 -Wall)

注意,set() 可以一次设置多个值,这些值会组成一个列表(list)。比如上面的 SRC_FILES 实际上是一个包含三个元素的列表。

稍微进阶一点,set() 还可以配合 CACHE 关键字,创建缓存变量。缓存变量会出现在CMake的GUI界面中,用户可以在配置时修改:

set(MY_OPTION "ON" CACHE BOOL "是否启用某个特性")

这个功能在制作可配置的库时非常有用。我做过一个图像处理库,用户可以通过CMake GUI开关不同的算法模块,就是靠 CACHE 变量实现的。

参数 说明 示例
变量名 标识符,习惯大写 MY_VAR
可以是字符串、数字、列表 "hello"42a b c
CACHE 创建缓存变量(可选) CACHE BOOL "描述"
FORCE 强制覆盖已有值(慎用) FORCE

3.4 message() 命令:你的调试利器

写CMake脚本最痛苦的是什么?就是出了问题不知道变量到底等于什么。message() 就是你的救命稻草。

基本用法:

message("你好,CMake!")
message("SRC_FILES = ${SRC_FILES}")

message() 还支持不同的消息类型,用关键字控制:

message(STATUS "正在检查依赖...")   # 状态信息,白色
message(WARNING "编译器版本可能过低")  # 警告,黄色
message(AUTHOR_WARNING "这个用法已弃用")  # 作者警告
message(SEND_ERROR "配置错误")  # 错误,继续执行但会失败
message(FATAL_ERROR "致命错误,停止")  # 致命错误,立即停止
实战建议:我写CMake脚本时,习惯在关键位置加 message(STATUS "...") 打印当前进度。比如「检查到OpenCV版本为4.5.0」、「源文件列表为xxx」。这样别人看你的构建日志,一目了然。

3.5 数学运算:让CMake帮你算数

你可能会问:「CMake还需要做数学运算?」嗯,确实需要。比如计算版本号、计算数组索引、或者根据条件计算编译参数。

CMake用 math() 命令做数学运算,语法是:

math(EXPR <输出变量> "<表达式>")

看例子:

set(A 10)
set(B 3)
math(EXPR SUM "${A} + ${B}")
math(EXPR DIFF "${A} - ${B}")
math(EXPR PROD "${A} * ${B}")
math(EXPR QUOT "${A} / ${B}")   # 整数除法,结果是3
math(EXPR MOD "${A} % ${B}")    # 取模,结果是1

message("和:${SUM},差:${DIFF}")
message("积:${PROD},商:${QUOT},余:${MOD}")

支持的运算符包括:+ - * / % | & ^ ~ << >>,跟C语言基本一致。注意,CMake的数学运算只支持整数,不支持浮点数。

避坑指南:我曾经在计算版本号时,直接写了 math(EXPR VER "${MAJOR}.${MINOR}"),结果CMake报错了。因为 math() 不支持浮点数,点号被当成了非法字符。正确的做法是分开计算,最后用字符串拼接。

3.6 综合示例:一个小脚本

把今天学的知识串起来,写一个完整的CMakeLists.txt片段:

cmake_minimum_required(VERSION 3.10)
project(HelloCMake)

# 定义版本号
set(VERSION_MAJOR 2)
set(VERSION_MINOR 1)
set(VERSION_PATCH 0)

# 计算下一个版本号(假设我们要打补丁)
math(EXPR NEXT_PATCH "${VERSION_PATCH} + 1")
set(NEXT_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${NEXT_PATCH}")

# 输出信息
message(STATUS "当前版本:${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
message(STATUS "下一个版本:${NEXT_VERSION}")

# 源文件列表
set(SOURCES main.cpp utils.cpp)

# 可执行文件
add_executable(myapp ${SOURCES})

这个脚本虽然简单,但包含了注释、变量定义、数学运算、消息输出,以及最终生成可执行文件。你可以在自己的项目里试试看。

总结一下:注释让代码可读,变量让代码灵活,set()message() 是你最常用的两个命令,math() 在需要计算时很有用。这些基础语法,你会在后面的每一章里反复用到。嗯,今天就到这里,下一章我们聊控制流——if、else、循环这些,到时候你会发现,CMake其实也挺「编程」的。

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