第1章 基础语法:CMakeLists.txt文件结构、注释、命令调用、变量基础、消息打印

说实话,我刚开始接触CMake的时候,第一反应是:这玩意儿不就是个makefile生成器吗?后来被项目毒打了几次,才明白CMakeLists.txt才是整个构建系统的灵魂。今天咱们就从最基础的东西聊起。

1.1 CMakeLists.txt 文件结构

每个CMake项目,根目录下必须有一个CMakeLists.txt。这是硬性规定,没得商量。我见过新手把文件命名为cmakelists.txt或者CMakeList.txt——少个s,CMake直接不认。嗯,这种坑我踩过。

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

cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)

# 添加可执行文件
add_executable(my_app main.cpp utils.cpp)

# 添加库
add_library(my_lib STATIC lib.cpp)

# 链接库
target_link_libraries(my_app PRIVATE my_lib)

结构其实很清晰:

  • 开头声明:指定CMake最低版本和项目信息
  • 中间构建逻辑:定义目标(可执行文件、库)
  • 尾部依赖管理:链接库、添加编译选项

我个人习惯把每个逻辑块用空行隔开,就像写代码一样。你想想看,一个几百行的CMakeLists.txt,如果全挤在一起,后期维护简直是噩梦。

1.2 注释

CMake的注释只有一种:#。没有多行注释,没有块注释。就是这么朴素。

# 这是单行注释
# 这也是注释
# 想写多行?每行前面加#吧

# 我一般会在关键逻辑前加注释说明
# 比如:这个库是给单元测试用的
add_library(test_utils STATIC test_helpers.cpp)

我曾经在一个遗留项目里看到有人用#[[ ]]做块注释——那是CMake 3.0之后才支持的语法。如果项目要求兼容老版本,千万别这么写。我的建议是:老老实实用#,兼容性最好,也最直观。

1.3 命令调用

CMake的命令调用,说白了就是函数调用。格式是:

命令名(参数1 参数2 ...)

注意几点:

  • 命令名不区分大小写,但社区惯例用小写
  • 参数用空格分隔,不是逗号
  • 字符串参数可以加引号,也可以不加(除非包含空格)
# 正确写法
add_executable(my_app main.cpp)
set(MY_VAR "hello world")  # 有空格,必须加引号

# 错误写法
add_executable(my_app, main.cpp)  # 逗号会被当成参数的一部分
set(MY_VAR hello world)           # 不加引号,world会被当成第二个参数

这里有个坑:if()foreach()这类控制流命令,后面必须跟小括号,而且endif()endforeach()也要写完整。我见过有人写endif漏了括号,CMake直接报语法错误。

1.4 变量基础

CMake的变量,本质上都是字符串。但你可以用它来存列表、路径、开关值。

定义变量:

set(MY_VAR "hello")
set(MY_LIST "a" "b" "c")  # 列表,实际上是个分号分隔的字符串

引用变量:

message("${MY_VAR}")  # 输出 hello
message(${MY_LIST})   # 输出 a;b;c

注意:引用变量必须用${}包裹。我刚开始学的时候,老忘记花括号,结果CMake把变量名当成普通字符串处理——查了半天bug,最后发现是少了个花括号。

变量作用域:

作用域 说明 示例
全局 在CMakeLists.txt顶层定义,整个项目可见 set(GLOBAL_VAR "xxx")
目录 在子目录中定义,仅该目录及子目录可见 set(DIR_VAR "xxx" PARENT_SCOPE)
函数 在函数内定义,仅函数内部可见 function(foo) set(LOCAL_VAR "xxx") endfunction()

我个人建议:全局变量尽量少用,多用目录作用域和函数作用域。不然项目大了,变量名冲突会让你欲哭无泪。

1.5 消息打印

调试CMakeLists.txt,最常用的就是message()命令。它就像C++里的cout,Python里的print

message("hello world")           # 普通消息
message(STATUS "当前版本: 1.0")   # 状态消息,前面会加--
message(WARNING "这个用法已弃用") # 警告,不会终止构建
message(FATAL_ERROR "出错了!")   # 致命错误,终止构建

我调试时最喜欢用STATUS级别,因为它不会刷屏,又能看到关键信息。比如:

message(STATUS "源文件列表: ${SRC_FILES}")
message(STATUS "编译器: ${CMAKE_CXX_COMPILER}")

曾经有一次,我在排查链接错误,死活找不到原因。最后加了一行message(STATUS "链接库路径: ${LIBRARY_DIRS}"),才发现路径配错了。嗯,message()真的是调试神器。

1.6 本章知识体系

下面这张图,把本章的核心知识点串起来了:

CMakeLists.txt 文件结构 • cmake_minimum_required() • project() • add_executable() / add_library() • target_link_libraries() 注释 • 单行注释: # • 无多行注释语法 • 建议:关键逻辑前加注释 命令调用 • 命令名(参数1 参数2 ...) • 参数用空格分隔 • 字符串含空格需加引号 • 控制流命令需完整配对 变量基础 • set() 定义变量 • ${} 引用变量 • 作用域:全局/目录/函数 消息打印 • message() 命令 • 级别:STATUS/WARNING/ERROR • 调试利器,多用STATUS

核心要点回顾:

  • CMakeLists.txt 是CMake的入口文件,结构清晰比什么都重要
  • 注释用#,别整花里胡哨的
  • 命令调用注意参数分隔符,别用逗号
  • 变量用${}引用,作用域要心里有数
  • message()是调试第一工具,别怕多用

我的小技巧:写CMakeLists.txt时,先搭骨架(项目声明+主要目标),再填细节(编译选项、链接库)。这样不容易乱。另外,每加一个功能就跑一次cmake验证,别攒一堆再调试——你会疯的。

注意:CMake对语法很敏感。少个括号、多个空格、引号不匹配,都可能引发莫名其妙的错误。遇到报错先检查语法,别急着怀疑人生。


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