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

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

1. CMake变量的基础操作

1.1 定义与修改变量

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

# 定义普通变量
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 删除变量

unset(MY_VAR)  # 删除当前作用域的变量

2. 变量的作用域层级

修改目录结构如下:

hello_cmake/
  ├── CMakeLists.txt
  ├── include/
  │   └── utils.h
  ├── src/
  │   ├── utils.cpp
  │   └── main.cpp
  └── build/
  └── subdir/
  │   └── CMakeLists.txt

2.1 目录作用域(Directory Scope)

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

输出结果如下:
在这里插入图片描述

作用域规则
  • ​父目录的变量在子目录中是只读副本,子目录对变量的修改不会影响父目录。
  • 每个 add_subdirectory() 都会创建一个新的作用域(类似函数调用)。
修改父目录变量的方法
  • 在子目录中使用 set(... PARENT_SCOPE) 将变量传递到父作用域。
  • 缓存变量是全局的,所有作用域共享。
# 父目录 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)​
# 父目录 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 函数/宏作用域

function(my_function)
    set(LOCAL_VAR "Local Value" PARENT_SCOPE)  # 修改上级作用域
endfunction()

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

2.3 目标作用域(Target Scope)

add_library(mylib STATIC mylib.cpp)

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

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

3.1 定义缓存变量

# 语法: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中可以看到会增加对应的可选项。
【CMake指南】第3篇:编写可维护的构建脚本(变量与作用域管理)_第1张图片

3.2 修改与覆盖

命令行覆盖

cmake -DBUILD_TESTS=ON ..

脚本强制修改

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 读取系统环境变量

# 读取PATH环境变量
message("系统PATH: $ENV{PATH}")

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

4.2 设置临时环境变量

# 仅影响当前CMake进程
set(ENV{CFLAGS} "-O2")

5. 实战技巧与避坑指南

5.1 变量优先级规则

普通变量 > 缓存变量(无FORCE时) > 环境变量

5.2 避免变量污染

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

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

5.3 类型转换技巧

# 字符串转列表
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变量

你可能感兴趣的:(CMake指南,开发语言,c++,CMake,教程)