【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变量
相关推荐
东雁西飞2 分钟前
MATLAB 控制系统设计与仿真 - 26
开发语言·算法·matlab·工业机器人·智能机器人
知行电子-3 分钟前
MATLAB R2024b 安装教程
开发语言·matlab·信息可视化
Clockwiseee16 分钟前
js原型链污染
开发语言·javascript·原型模式
阿拉保23 分钟前
C++复试笔记(四)
java·c++·笔记
郭源潮127 分钟前
《 线程池项目:线程池背景知识与整体架构梳理》
c++·线程池·c++11·c++17
Biomamba生信基地35 分钟前
R语言基础| 高级数据管理
开发语言·r语言
Dream it possible!1 小时前
CCF CSP 第30次(2023.09)(1_坐标变换_C++)(先输入再计算;边输入边计算)
c++·算法·csp
BBbila1 小时前
小程序主包方法迁移到分包-调用策略
开发语言·javascript·微信小程序
此刻我在家里喂猪呢1 小时前
c++介绍函数指针 十
c++
daiwoliyunshang1 小时前
类和对象:
开发语言·c++