CMakeLists 脚本命令讲解

文章目录

    • 核心心智模型
    • 一、项目根命令
      • [1.1 `cmake_minimum_required(VERSION <版本号>)`](#1.1 cmake_minimum_required(VERSION <版本号>))
      • [1.2 `project(<项目名> 语言 VERSION \<版本\>)`](#1.2 project(<项目名> [语言] [VERSION <版本>]))
    • 二、变量与流程控制
      • [2.1 `set(变量名 值 CACHE 类型 描述)` --- 设置变量](#2.1 set(变量名 值 [CACHE 类型 描述]) — 设置变量)
      • [2.2 `option(变量名 描述 默认值)` --- 布尔开关](#2.2 option(变量名 描述 默认值) — 布尔开关)
      • [2.3 `message(模式 "消息")` --- 打印信息](#2.3 message([模式] "消息") — 打印信息)
      • [2.4 条件判断](#2.4 条件判断)
      • [2.5 循环](#2.5 循环)
    • 三、核心构建命令:定义目标
      • [3.1 `add_executable(<目标名> <源文件...>)` --- 生成可执行文件](#3.1 add_executable(<目标名> <源文件...>) — 生成可执行文件)
      • [3.2 `add_library(<目标名> <类型> <源文件...>)` --- 生成库](#3.2 add_library(<目标名> <类型> <源文件...>) — 生成库)
    • 四、目标属性设置命令
      • [4.1 `target_compile_options(<目标> <可见性> <选项...>)` --- 编译选项](#4.1 target_compile_options(<目标> <可见性> <选项...>) — 编译选项)
      • [4.2 `target_include_directories(<目标> <可见性> <路径...>)` --- 头文件搜索路径](#4.2 target_include_directories(<目标> <可见性> <路径...>) — 头文件搜索路径)
      • [4.3 `target_compile_definitions(<目标> <可见性> <宏...>)` --- 定义预处理器宏](#4.3 target_compile_definitions(<目标> <可见性> <宏...>) — 定义预处理器宏)
      • [4.4 `target_link_libraries(<目标> <可见性> <库...>)` --- 链接库](#4.4 target_link_libraries(<目标> <可见性> <库...>) — 链接库)
    • [五、可见性关键字:PUBLIC / PRIVATE / INTERFACE](#五、可见性关键字:PUBLIC / PRIVATE / INTERFACE)
    • [六、生成器表达式:`\<...\>\`](#六、生成器表达式:`<...>`)
    • 七、自定义命令与目标
      • [7.1 `add_custom_command()` --- 添加自定义构建步骤](#7.1 add_custom_command() — 添加自定义构建步骤)
      • [7.2 `add_custom_target()` --- 添加纯命令目标](#7.2 add_custom_target() — 添加纯命令目标)
    • 八、其他实用命令速览
      • [8.1 `file()` --- 文件操作](#8.1 file() — 文件操作)
      • [8.2 `find_package()` --- 查找外部库](#8.2 find_package() — 查找外部库)
      • [8.3 `add_subdirectory()` --- 引入子项目](#8.3 add_subdirectory() — 引入子项目)
      • [8.4 `include()` --- 包含外部 CMake 文件](#8.4 include() — 包含外部 CMake 文件)
      • [8.5 `install()` --- 安装规则](#8.5 install() — 安装规则)
    • [九、完整实战:PID 项目进阶 CMakeLists.txt](#九、完整实战:PID 项目进阶 CMakeLists.txt)
    • 十、命令速查表
    • [十一、调试 CMakeLists.txt 的小技巧](#十一、调试 CMakeLists.txt 的小技巧)

好的,我将从零开始,系统性地讲解 CMakeLists.txt 中的脚本命令。这是 CMake 的核心------用声明式的方式描述你的项目如何构建。


核心心智模型

CMakeLists.txt 不是一种编程语言,而是一种构建描述语言。你告诉 CMake:

  1. 你的项目叫什么,用什么语言
  2. 你想要生成什么(可执行文件?库?)
  3. 这些目标由哪些源文件组成
  4. 它们需要什么编译选项、头文件路径、链接库

CMake 根据这些描述,为你生成对应平台的构建文件。


一、项目根命令

1.1 cmake_minimum_required(VERSION <版本号>)

必须是第一行命令。指定项目需要的最低 CMake 版本,避免用户在老版本上遇到奇怪的错误。

cmake 复制代码
cmake_minimum_required(VERSION 3.15)

1.2 project(<项目名> [语言] [VERSION <版本>])

定义项目名称、版本和使用的编程语言。这会自动设置许多变量。

cmake 复制代码
project(PID_Sim VERSION 1.0.0 LANGUAGES C)

执行后,CMake 自动创建以下变量(你可以直接使用):

变量 含义
PROJECT_NAME PID_Sim 项目名称
PROJECT_VERSION 1.0.0 项目版本
PROJECT_SOURCE_DIR D:/Desktop/demoCmk 源码根目录
PROJECT_BINARY_DIR D:/Desktop/demoCmk/build 构建根目录
CMAKE_C_COMPILER gcc 自动检测到的C编译器

重要project() 还隐式调用了 enable_language(C),告诉 CMake 去检测 C 编译器。这也是为什么 project() 必须在所有编译相关命令之前。


二、变量与流程控制

2.1 set(变量名 值 [CACHE 类型 描述]) --- 设置变量

普通变量(只在当前 CMakeLists.txt 及子目录可见):

cmake 复制代码
set(SOURCES main.c pid.c)
set(COMPILE_OPTS -O0 -g -Wall)

缓存变量 (可在命令行用 -D 覆盖,存储在 CMakeCache.txt 中):

cmake 复制代码
set(CMAKE_C_STANDARD 11 CACHE STRING "C standard to use")
set(ENABLE_LOGGING OFF CACHE BOOL "Enable debug logging")

列表变量(用分号或空格分隔,实际存储为分号分隔的字符串):

cmake 复制代码
set(MY_LIST a b c)       # 等价于 "a;b;c"
set(MY_LIST "a;b;c")     # 同上

2.2 option(变量名 描述 默认值) --- 布尔开关

optionset(... CACHE BOOL ...) 的简写,专门用于开关。

cmake 复制代码
option(ENABLE_DEBUG "Enable debug output" OFF)

命令行覆盖:cmake -B build -DENABLE_DEBUG=ON

2.3 message([模式] "消息") --- 打印信息

cmake 复制代码
message(STATUS "Building project: ${PROJECT_NAME}")       # 配置时打印,带 --
message(WARNING "This is a warning")                      # 警告,继续执行
message(FATAL_ERROR "Cannot continue, missing dependency") # 错误,停止配置

2.4 条件判断

cmake 复制代码
if(条件)
    # ...
elseif(条件)
    # ...
else()
    # ...
endif()

常用条件表达式

cmake 复制代码
# 比较
if(CMAKE_BUILD_TYPE STREQUAL "Debug")           # 字符串相等
if(MY_VAR MATCHES "pattern")                    # 正则匹配
if(NOT DEFINED MY_VAR)                          # 变量是否未定义

# 布尔
if(ENABLE_LOGGING)                              # 如果为 ON/TRUE/1
if(NOT ENABLE_LOGGING)                          # 如果为 OFF/FALSE/0

# 文件系统
if(EXISTS "${CMAKE_SOURCE_DIR}/pid.h")          # 文件/目录存在
if(IS_DIRECTORY "${CMAKE_SOURCE_DIR}/libs")     # 是否目录

完整示例(应用到 PID 项目)

cmake 复制代码
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(STATUS "Debug build: using -O0 -g")
    set(EXTRA_FLAGS -O0 -g)
else()
    message(STATUS "Release build: using -O2")
    set(EXTRA_FLAGS -O2)
endif()

2.5 循环

cmake 复制代码
# foreach 循环
foreach(ITEM IN LISTS MY_LIST)
    message(STATUS "Item: ${ITEM}")
endforeach()

# 范围循环
foreach(IDX RANGE 0 5)          # 0,1,2,3,4,5
    message(STATUS "Index: ${IDX}")
endforeach()

# while 循环(较少用)
set(COUNT 0)
while(COUNT LESS 5)
    message(STATUS "Count: ${COUNT}")
    math(EXPR COUNT "${COUNT} + 1")
endwhile()

三、核心构建命令:定义目标

这是 CMakeLists.txt 的灵魂。所有构建都围绕"目标"(target)展开。

3.1 add_executable(<目标名> <源文件...>) --- 生成可执行文件

cmake 复制代码
set(SOURCES main.c pid.c)
add_executable(pid_offline ${SOURCES})
  • 第一个参数是目标名(可执行文件名,Windows 自动加 .exe
  • 后面是所有 .c 文件
  • 执行后,CMake 会自动为每个 .c 生成编译规则,最后链接成 pid_offline

3.2 add_library(<目标名> <类型> <源文件...>) --- 生成库

库的类型:

类型 含义
STATIC 静态库 (.a / .lib),编译时打包进可执行文件
SHARED 动态库 (.so / .dll),运行时加载
MODULE 插件式动态库,不能被链接,只能 dlopen 加载
INTERFACE 只有头文件的库(如 header-only 库),不生成实际文件

示例(把 PID 算法做成静态库)

cmake 复制代码
# 静态库
add_library(pidlib STATIC pid.c)

# 使用 pidlib 的可执行文件
add_executable(pid_test test.c)
target_link_libraries(pid_test PRIVATE pidlib)

为什么推荐做库?

  • 代码复用:多个可执行文件可以共用同一个库
  • 清晰架构:核心算法和测试/应用分离
  • 编译加速:库单独编译,修改应用代码不会触发库重新编译

四、目标属性设置命令

这是现代 CMake 最强大的部分:所有设置都作用在目标上,而不是全局的

4.1 target_compile_options(<目标> <可见性> <选项...>) --- 编译选项

cmake 复制代码
target_compile_options(pid_offline PRIVATE -O0 -g -Wall)

4.2 target_include_directories(<目标> <可见性> <路径...>) --- 头文件搜索路径

cmake 复制代码
# 假设头文件在 include/ 目录下
target_include_directories(pid_offline PRIVATE include)

等效于 gcc 的 -I include

4.3 target_compile_definitions(<目标> <可见性> <宏...>) --- 定义预处理器宏

cmake 复制代码
target_compile_definitions(pid_offline PRIVATE PID_DEBUG=1 VERSION="1.0")

等效于在源代码中写 #define PID_DEBUG 1#define VERSION "1.0",或 gcc 的 -DPID_DEBUG=1

cmake 复制代码
# 链接数学库
target_link_libraries(pid_offline PRIVATE m)

# 链接自己的库
target_link_libraries(pid_offline PRIVATE pidlib)

五、可见性关键字:PUBLIC / PRIVATE / INTERFACE

这是现代 CMake 的精髓,决定了依赖如何"传染"。

关键字 含义 使用场景
PRIVATE 只用于当前目标的构建,不传染给依赖者 内部实现细节,如 -Wall
INTERFACE 不用于当前目标,但传染给依赖者 只有头文件的库
PUBLIC 既用于当前目标,也传染给依赖者 当前目标头文件中暴露的依赖

示例:

cmake 复制代码
# pidlib 是一个静态库,它的头文件 pid.h 中包含了 <math.h>
# 所以任何链接 pidlib 的目标也需要链接数学库
target_link_libraries(pidlib PUBLIC m)

# pid_offline 链接了 pidlib,因为 PUBLIC,它自动获得了 m
target_link_libraries(pid_offline PRIVATE pidlib)
# 不需要再写 target_link_libraries(pid_offline PRIVATE m)

可见性决策口诀:

  • 你的 .c 文件需要的 → PRIVATE
  • 你的 .h 文件需要的 → PUBLIC
  • 你不编译代码,只是传递接口 → INTERFACE

六、生成器表达式:$<...>

生成器表达式在生成构建系统时才求值,可以做更复杂的条件逻辑。

语法$<表达式>

常用表达式

表达式 含义
$<CONFIG:Debug> 如果当前配置是 Debug,返回 1
$<TARGET_FILE:目标> 目标文件的完整路径
$<INSTALL_INTERFACE:路径> 安装后的路径

示例:根据构建类型设置不同编译选项

cmake 复制代码
target_compile_options(pid_offline PRIVATE
    $<$<CONFIG:Debug>:-O0;-g>
    $<$<CONFIG:Release>:-O2>
)

Debug 时加 -O0 -g,Release 时加 -O2

示例:在自定义命令中获取可执行文件路径

cmake 复制代码
add_custom_target(run
    COMMAND $<TARGET_FILE:pid_offline> > result.csv
    DEPENDS pid_offline
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

$<TARGET_FILE:pid_offline> 在构建时会被替换成 D:/Desktop/demoCmk/build/pid_offline.exe


七、自定义命令与目标

7.1 add_custom_command() --- 添加自定义构建步骤

两种主要形式:

形式 1:生成文件

cmake 复制代码
add_custom_command(
    OUTPUT generated.c
    COMMAND some_tool input.txt -o generated.c
    DEPENDS input.txt
    COMMENT "Generating source file..."
)

形式 2:作为目标的构建前/后事件

cmake 复制代码
add_custom_command(
    TARGET pid_offline POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pid_offline> ${CMAKE_BINARY_DIR}/backup/
    COMMENT "Backing up executable..."
)
  • PRE_BUILD:编译前
  • PRE_LINK:链接前
  • POST_BUILD:构建后(最常用)

7.2 add_custom_target() --- 添加纯命令目标

这就是你用的 run 目标。

cmake 复制代码
add_custom_target(run
    COMMAND ${CMAKE_COMMAND} -E echo "Running simulation..."
    COMMAND $<TARGET_FILE:pid_offline> > ${CMAKE_BINARY_DIR}/output.csv
    DEPENDS pid_offline
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    COMMENT "Generating CSV..."
)

要点

  • 没有 OUTPUT,所以总是被视为过期的 ,每次 --target run 都会执行
  • COMMAND 可以写多个,按顺序执行
  • DEPENDS 保证依赖的目标先构建
  • WORKING_DIRECTORY 设置执行时的当前目录

八、其他实用命令速览

8.1 file() --- 文件操作

cmake 复制代码
# 收集所有 .c 文件(等同于 Makefile 的 wildcard)
file(GLOB SRC_FILES "*.c")

# 递归收集
file(GLOB_RECURSE ALL_SOURCES "src/*.c" "src/*.h")

# 读取文件内容到变量
file(READ "config.txt" CONFIG_CONTENT)

# 写入内容到文件
file(WRITE "output.txt" "Hello, CMake!")

注意file(GLOB) 很方便,但 CMake 官方不推荐用于收集源文件,因为新增文件后 CMake 不会自动检测到,需要手动重新运行 cmake -B build

8.2 find_package() --- 查找外部库

cmake 复制代码
# 查找并链接数学库
find_package(OpenGL REQUIRED)
target_link_libraries(my_app PRIVATE OpenGL::GL)

8.3 add_subdirectory() --- 引入子项目

cmake 复制代码
# 项目结构:
# CMakeLists.txt (根)
# src/CMakeLists.txt
# tests/CMakeLists.txt

add_subdirectory(src)
add_subdirectory(tests)

每个子目录的 CMakeLists.txt 可以独立管理自己的目标。

8.4 include() --- 包含外部 CMake 文件

cmake 复制代码
include(cmake/compiler_flags.cmake)

用于复用公共的编译设置等。

8.5 install() --- 安装规则

cmake 复制代码
# 安装可执行文件到 bin/
install(TARGETS pid_offline RUNTIME DESTINATION bin)

# 安装头文件到 include/
install(FILES pid.h DESTINATION include)

然后用户可以通过 cmake --install build --prefix /usr/local 安装。


九、完整实战:PID 项目进阶 CMakeLists.txt

结合上面所有知识,这是你 PID 项目的"毕业版" CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.15)
project(PID_Sim VERSION 1.0.0 LANGUAGES C)

# ---------- 全局设置 ----------
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

# 可配置选项
option(ENABLE_SANITIZER "Enable AddressSanitizer" ON)
option(BUILD_TESTS "Build test programs" OFF)

# ---------- 核心算法库 ----------
add_library(pidlib STATIC pid.c)
target_include_directories(pidlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(pidlib PRIVATE -Wall -Wextra)
if(ENABLE_SANITIZER)
    target_compile_options(pidlib PRIVATE -fsanitize=address)
    target_link_libraries(pidlib PUBLIC -fsanitize=address)
endif()

# ---------- 离线仿真程序 ----------
add_executable(pid_offline main.c)
target_link_libraries(pid_offline PRIVATE pidlib)
target_compile_options(pid_offline PRIVATE -g -O0)
target_compile_definitions(pid_offline PRIVATE APP_VERSION="${PROJECT_VERSION}")

# ---------- 自定义任务 ----------
# 生成 CSV
add_custom_target(run
    COMMAND $<TARGET_FILE:pid_offline> > ${CMAKE_BINARY_DIR}/output.csv
    DEPENDS pid_offline
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    COMMENT "Running PID simulation..."
)

# 内存检查
add_custom_target(check
    COMMAND $<TARGET_FILE:pid_offline>
    DEPENDS pid_offline
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    COMMENT "Running with AddressSanitizer enabled..."
)

# ---------- 单元测试(可选) ----------
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

# ---------- 安装规则 ----------
install(TARGETS pid_offline RUNTIME DESTINATION bin)
install(TARGETS pidlib ARCHIVE DESTINATION lib)
install(FILES pid.h DESTINATION include)

十、命令速查表

命令 作用 简记
cmake_minimum_required 最低版本要求 第一行
project() 项目定义 第二行
set() 设置变量 存储
option() 布尔选项 开关
message() 打印信息 调试
if/elseif/else/endif 条件 分支
foreach/while 循环 遍历
add_executable() 可执行文件 程序
add_library() 复用
target_compile_options() 编译选项 -O -g
target_include_directories() 头文件路径 -I
target_compile_definitions() 宏定义 -D
target_link_libraries() 链接库 -l
add_custom_command() 自定义构建步骤 生成文件
add_custom_target() 自定义命令目标 运行脚本
find_package() 查找外部库 依赖
add_subdirectory() 子目录 模块化
file() 文件操作 工具
install() 安装规则 分发

十一、调试 CMakeLists.txt 的小技巧

  1. 打印变量值

    cmake 复制代码
    message(STATUS "SOURCES = ${SOURCES}")
    message(STATUS "Build type = ${CMAKE_BUILD_TYPE}")
  2. 打印所有缓存变量

    powershell 复制代码
    cmake -B build -N -LAH

    -LAH 列出所有缓存变量及其帮助信息。

  3. 使用 cmake --trace 跟踪执行

    powershell 复制代码
    cmake -B build --trace

    会输出每一行 CMake 脚本的执行过程,用于定位复杂 bug。

  4. 查看构建生成的详细命令

    powershell 复制代码
    cmake --build build --verbose

    cmake --build build -v,等价于 make VERBOSE=1


现在你手中的 CMake 知识,已经覆盖了从单文件编译到中型项目的全部需求。把上面的"毕业版" CMakeLists.txt 保存起来,未来的项目都可以以此为模板开始。CMake 最大的门槛已经跨过,剩下的只是经验的积累。

相关推荐
jieyucx6 小时前
Windows11 & Linux 环境Docker部署与镜像拉取效率优化实战指南
linux·运维·docker
AC赳赳老秦6 小时前
Excel 动态仪表盘制作:用 OpenClaw 自动处理数据、生成交互式图表并实时更新仪表盘
大数据·服务器·后端·测试用例·excel·deepseek·openclaw
西游音月6 小时前
Windows环境下的WSL+Claude Code安装配置
windows·大模型·ai编程·wsl·claude code
weixin_307779136 小时前
Linux下ClickHouse数据仓库故障系统化诊断脚本
linux·运维·clickhouse·自动化·数据库开发
小职boy16 小时前
不忘初心Windows系统工具箱下载 - 系统优化与维护工具(v1.1)
windows·系统·电脑软件·工具箱
RisunJan18 小时前
Linux命令-rlogin(远程登录)
linux·运维
深圳恒讯18 小时前
菲律宾云服务器与传统VPS的架构差异
运维·服务器·架构
Mininglamp_271818 小时前
Claude Code 封禁中国开发者之后:本地 AI 编程工具的替代方案实测
开发语言·人工智能·windows·开源软件·ai-native
蓝天下的守望者18 小时前
svt_apb_if里的宏定义问题
运维·服务器·数据库
小林ixn18 小时前
从“玩具工具”到“跨语言利器”:MCP 协议实战解析
运维·服务器·网络