【CMake指南】第3篇:编写可维护的构建脚本(变量与作用域管理)

源码及CMakeLists.txt沿用第1篇或第2篇的均可,本文沿用第2篇的相关文件

1. CMake变量的基础操作

1.1 定义与修改变量

在CMakeLists.txt后面加入如下代码:

cpp 复制代码
# 定义普通变量
set(MY_VAR "Hello World")
# 修改变量值
set(MY_VAR "New Value")
# 列表变量(分号分隔)
set(MY_LIST a.cpp b.cpp c.cpp)
# 或显式列表
set(MY_LIST "a.cpp;b.cpp;c.cpp")

# 打印变量内容
message("变量值: ${MY_VAR}")  # 输出: 变量值: New Value
# 打印列表内容
message("列表内容: ${MY_LIST}")  # 输出: 列表内容: a.cpp;b.cpp;c.cpp

执行cmake --build .可在结果编译的输出信息中找到对应的输出,如下图:

1.2 删除变量

cpp 复制代码
unset(MY_VAR)  # 删除当前作用域的变量

2. 变量的作用域层级

修改目录结构如下:

plaintext 复制代码
hello_cmake/
  ├── CMakeLists.txt
  ├── include/
  │   └── utils.h
  ├── src/
  │   ├── utils.cpp
  │   └── main.cpp
  └── build/
  └── subdir/
  │   └── CMakeLists.txt

2.1 目录作用域(Directory Scope)

cpp 复制代码
# 父目录的CMakeLists.txt
set(PARENT_VAR "Parent Value")
add_subdirectory(subdir)
message("父目录中读取被改变的变量: ${PARENT_VAR}")  # 可读取
cpp 复制代码
# 子目录subdir/CMakeLists.txt
message("子目录读取父变量: ${PARENT_VAR}")    # 可读取
set(PARENT_VAR "Child Modified" PARENT_VAR)  # 修改父作用域的变量

输出结果如下:

作用域规则
  • 父目录的变量在子目录中是只读副本,子目录对变量的修改不会影响父目录。
  • 每个 add_subdirectory() 都会创建一个新的作用域(类似函数调用)。
修改父目录变量的方法
  • 在子目录中使用 set(... PARENT_SCOPE) 将变量传递到父作用域。
  • 缓存变量是全局的,所有作用域共享。
cpp 复制代码
# 父目录 CMakeLists.txt
set(MY_CACHE_VAR "初始值" CACHE STRING "说明")

# 子目录 subdir/CMakeLists.txt
set(MY_CACHE_VAR "新值" CACHE STRING "说明" FORCE)  # 强制修改缓存变量
message("子目录中的 MY_CACHE_VAR = ${MY_CACHE_VAR}")  # 输出 "新值"
  • 使用全局属性(set_property
cpp 复制代码
# 父目录 CMakeLists.txt
set_property(GLOBAL PROPERTY MY_GLOBAL_VAR "初始值")

# 子目录 subdir/CMakeLists.txt
get_property(tmp GLOBAL PROPERTY MY_GLOBAL_VAR)
set_property(GLOBAL PROPERTY MY_GLOBAL_VAR "${tmp}_子目录修改")

2.2 函数/宏作用域

cpp 复制代码
function(my_function)
    set(LOCAL_VAR "Local Value" PARENT_SCOPE)  # 修改上级作用域
endfunction()

my_function()
message("函数外部: ${LOCAL_VAR}")  # 输出: 函数外部: Local Value

2.3 目标作用域(Target Scope)

cpp 复制代码
add_library(mylib STATIC mylib.cpp)

# 仅对mylib目标生效
target_compile_definitions(mylib PRIVATE DEBUG_MODE=1)

3. 缓存变量(CACHE Variables)详解

3.1 定义缓存变量

cpp 复制代码
# 语法:set(<变量名> <值> CACHE <类型> <描述> [FORCE])
set(BUILD_TESTS OFF CACHE BOOL "是否启用测试")

# 高级用法:下拉选项
set(COMPILER_TYPE "GCC" CACHE STRING "选择编译器")
set_property(CACHE COMPILER_TYPE PROPERTY STRINGS "GCC;Clang;MSVC")

以上的修改在纯命令行编译时无法体型,在CMake GUI中可以看到会增加对应的可选项。

3.2 修改与覆盖

命令行覆盖

bash 复制代码
cmake -DBUILD_TESTS=ON ..

脚本强制修改

cpp 复制代码
set(BUILD_TESTS ON CACHE BOOL "覆盖值" FORCE)

3.3 缓存变量类型

类型 ​描述 ​示例 ​适用场景 ​ GUI显示形式
​BOOL 布尔值,表示开关选项(ON/OFF)。 set(USE_FEATURE_X ON CACHE BOOL "...") 控制功能开关(如 ENABLE_TESTING)。 复选框(Checkbox)。
​STRING 普通字符串,可配合 STRINGS 属性限定可选值。 set(BUILD_TYPE "Debug" CACHE STRING "...") 配置构建类型(如 Debug/Release)。 下拉框(若定义了 STRINGS 属性)。
​PATH 表示文件系统目录路径,CMake GUI 提供路径选择按钮。 set(INSTALL_PREFIX "/usr/local" CACHE PATH "...") 指定安装目录、依赖库路径等。 路径选择对话框(带浏览按钮)。
​FILEPATH 表示文件路径,CMake GUI 提供文件选择按钮。 set(CONFIG_FILE "config.cfg" CACHE FILEPATH "...") 指定配置文件、输入文件路径。 文件选择对话框(带浏览按钮)。
​INTERNAL 内部变量,不在 GUI 中显示,但值会持久化到缓存。 set(INTERNAL_CACHE "secret" CACHE INTERNAL "...") 存储内部配置(如哈希值、临时标记)。 不显示。
​STATIC 静态描述文本,仅用于在 GUI 中分组或注释(实际不存储值)。 set(HELP_TEXT "Options:" CACHE STATIC "...") GUI 界面中的分组标题或说明文本。 只读文本标签。

4. 环境变量操作

4.1 读取系统环境变量

cpp 复制代码
# 读取PATH环境变量
message("系统PATH: $ENV{PATH}")

# 在代码中使用(通过编译定义传递)
add_definitions(-DMY_ENV_PATH="$ENV{PATH}")

4.2 设置临时环境变量

cpp 复制代码
# 仅影响当前CMake进程
set(ENV{CFLAGS} "-O2")

5. 实战技巧与避坑指南

5.1 变量优先级规则

cpp 复制代码
普通变量 > 缓存变量(无FORCE时) > 环境变量

5.2 避免变量污染

cpp 复制代码
# 错误示例:全局变量污染
set(SOURCES main.cpp)  # 全局作用域

# 正确做法:限定作用域
function(add_module)
    set(SOURCES a.cpp b.cpp)  # 函数作用域
    add_library(mymodule ${SOURCES})
endfunction()

5.3 类型转换技巧

cpp 复制代码
# 字符串转列表
string(REPLACE ";" " " MY_STR "${MY_LIST}")  # "a.cpp b.cpp c.cpp"

# 列表操作
list(APPEND MY_LIST d.cpp)    # 添加元素
list(REMOVE_ITEM MY_LIST a.cpp) # 删除元素

6. 常见问题解答

Q1:CACHE变量和普通变量有什么区别?

  • CACHE变量:持久化存储(写入CMakeCache.txt),可跨多次配置
  • 普通变量:临时存在,作用域结束后销毁

Q2:如何强制刷新缓存变量?

  • 删除build目录或使用cmake -U <变量名>清除
  • 在脚本中用FORCE关键字

Q3:为什么我的变量在子目录中不可见?

  • 默认变量传递是向下传递的,向上修改需用PARENT_SCOPE
  • 跨目录共享变量建议用CACHE变量
相关推荐
朝新_3 小时前
【多线程初阶】阻塞队列 & 生产者消费者模型
java·开发语言·javaee
立莹Sir3 小时前
Calendar类日期设置进位问题
java·开发语言
木子.李3474 小时前
排序算法总结(C++)
c++·算法·排序算法
风逸hhh4 小时前
python打卡day46@浙大疏锦行
开发语言·python
火兮明兮4 小时前
Python训练第四十三天
开发语言·python
___波子 Pro Max.4 小时前
CMake GLOB返回路径规则及示例
cmake
freyazzr5 小时前
C++八股 | Day2 | atom/函数指针/指针函数/struct、Class/静态局部变量、局部变量、全局变量/强制类型转换
c++
ascarl20105 小时前
准确--k8s cgroup问题排查
java·开发语言
fpcc6 小时前
跟我学c++中级篇——理解类型推导和C++不同版本的支持
开发语言·c++
莱茵菜苗6 小时前
Python打卡训练营day46——2025.06.06
开发语言·python