第 4 章:变量系统
第 4 章:变量系统
4.1 变量基础
4.1.1 设置和使用变量
# 设置变量
set(MY_NAME "张三")
set(MY_AGE 25)
set(MY_PATH "/usr/local/bin")
# 使用变量(通过 ${} 语法)
message("姓名: ${MY_NAME}")
message("年龄: ${MY_AGE}")
message("路径: ${MY_PATH}")
⚠️ 注意:
set()中变量名不需要${},但引用时需要。
4.1.2 变量类型
CMake 中所有变量本质上都是字符串:
set(NAME "hello") # 字符串
set(COUNT 42) # 也是字符串 "42"
set(ENABLED ON) # 布尔值也是字符串 "ON"
set(PATH "/usr/local") # 路径字符串
4.1.3 布尔值
CMake 使用以下值表示布尔值:
| 真值(True) | 假值(False) |
|---|---|
1, ON, YES, TRUE, Y | 0, OFF, NO, FALSE, N |
on, yes, true, y | off, no, false, n |
⚠️ 注意:这些值不区分大小写,但推荐使用大写形式(
ON/OFF)。
4.1.4 变量命名约定
# CMake 内置变量通常以 CMAKE_ 开头
set(CMAKE_BUILD_TYPE "Release")
# 项目变量建议使用项目前缀
set(MYAPP_VERSION "1.0.0")
set(MYAPP_USE_SSL ON)
# 模块变量使用大写加下划线
set(FIND_OPENSSL_ROOT "/usr/local/ssl")
# 局部变量通常使用小写
set(source_files main.cpp utils.cpp)
set(link_libraries fmt::fmt Threads::Threads)
4.2 列表(Lists)
4.2.1 创建列表
# 列表用分号分隔
set(MY_LIST "a;b;c;d")
# 多参数形式(推荐,等价于上面)
set(MY_LIST a b c d)
# 混合类型
set(SOURCES main.cpp src/app.cpp src/utils.cpp)
# 空列表
set(MY_LIST "")
4.2.2 操作列表
set(MY_LIST a b c d)
# 追加元素
list(APPEND MY_LIST e f)
# MY_LIST = "a;b;c;d;e;f"
# 插入元素
list(INSERT MY_LIST 2 x)
# MY_LIST = "a;b;x;c;d;e;f"
# 删除元素
list(REMOVE_ITEM MY_LIST c)
# MY_LIST = "a;b;x;d;e;f"
# 删除重复项
list(REMOVE_DUPLICATES MY_LIST)
# 反转列表
list(REVERSE MY_LIST)
# 排序列表
list(SORT MY_LIST)
4.2.3 查询列表
set(MY_LIST a b c d e)
# 列表长度
list(LENGTH MY_LIST len)
message("长度: ${len}") # 5
# 获取元素(索引从 0 开始)
list(GET MY_LIST 1 3 elements)
message("元素: ${elements}") # "b;d"
# 查找元素
list(FIND MY_LIST c idx)
message("索引: ${idx}") # 2(未找到返回 -1)
# 判断是否包含
if("c" IN_LIST MY_LIST)
message("MY_LIST 包含 c")
endif()
4.2.4 列表作为函数参数
# CMake 函数接收到列表时,会自动展开
set(MY_LIST a b c)
# 传递列表 — 注意引号的使用
message("带引号: ${MY_LIST}") # a b c(展开)
message("带引号: \"${MY_LIST}\"") # "a;b;c"(保持为一个参数)
# 在 if 语句中
if(MY_LIST) # 如果列表非空则为真
message("列表非空")
endif()
4.3 缓存变量(Cache Variables)
4.3.1 概念
缓存变量存储在 CMakeCache.txt 中,在多次配置之间持久化:
# 设置缓存变量
set(MY_OPTION ON CACHE BOOL "启用自定义选项")
set(MY_PATH "/usr/local" CACHE PATH "安装路径")
set(MY_STRING "hello" CACHE STRING "描述性字符串")
set(MY_FILE "config.h" CACHE FILEPATH "配置文件路径")
4.3.2 缓存变量类型
| 类型 | 说明 | GUI 显示 |
|---|---|---|
BOOL | 布尔值 | 复选框 |
FILEPATH | 文件路径 | 文件选择器 |
PATH | 目录路径 | 目录选择器 |
STRING | 字符串 | 文本输入框 |
INTERNAL | 内部变量 | 不在 GUI 中显示 |
4.3.3 缓存变量 vs 普通变量
# 普通变量:每次 cmake 都重新设置
set(CMAKE_BUILD_TYPE "Debug")
# 缓存变量:仅在第一次设置时生效
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "构建类型")
# 之后用户修改 CMakeCache.txt 不会被覆盖
# 强制覆盖缓存变量
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "构建类型" FORCE)
4.3.4 命令行设置缓存变量
# 使用 -D 设置
cmake -S . -B build -DMY_OPTION=ON
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/myapp
# 删除缓存变量
cmake -S . -B build -UMY_OPTION
# 或删除所有 MY_* 变量
cmake -S . -B build -UMY_*
# 删除缓存重新配置
rm -rf build
cmake -S . -B build
4.3.5 option 命令
# 定义布尔选项(简化缓存变量的设置)
option(BUILD_TESTS "构建测试" ON)
option(BUILD_DOCS "构建文档" OFF)
option(ENABLE_LOGGING "启用日志" ON)
option(USE_SYSTEM_FMT "使用系统 fmt 库" OFF)
# 使用
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
# 命令行使用
# cmake -S . -B build -DBUILD_TESTS=ON -DENABLE_LOGGING=OFF
4.4 环境变量
4.4.1 读取环境变量
# 读取环境变量
set(HOME_DIR $ENV{HOME})
message("HOME: ${HOME_DIR}")
# 检查环境变量是否存在
if(DEFINED ENV{MY_CUSTOM_PATH})
message("MY_CUSTOM_PATH 已设置: $ENV{MY_CUSTOM_PATH}")
endif()
# 使用环境变量设置默认值
if(NOT DEFINED CMAKE_BUILD_TYPE)
if(DEFINED ENV{CMAKE_BUILD_TYPE})
set(CMAKE_BUILD_TYPE $ENV{CMAKE_BUILD_TYPE})
else()
set(CMAKE_BUILD_TYPE "Release")
endif()
endif()
4.4.2 设置环境变量
# 仅在当前 CMake 进程中有效
set(ENV{MY_VAR} "my_value")
# 用于 subprocess
execute_process(
COMMAND ${CMAKE_COMMAND} -E env
MY_PATH=/opt/lib
MY_FLAG=1
some_command
)
4.4.3 常用环境变量
| 环境变量 | 说明 |
|---|---|
CC | C 编译器路径 |
CXX | C++ 编译器路径 |
CFLAGS | C 编译器标志 |
CXXFLAGS | C++ 编译器标志 |
LDFLAGS | 链接器标志 |
CMAKE_PREFIX_PATH | 依赖包搜索路径 |
CMAKE_TOOLCHAIN_FILE | 工具链文件路径 |
PATH | 可执行文件搜索路径 |
PKG_CONFIG_PATH | pkg-config 搜索路径 |
4.5 字符串操作
4.5.1 string 命令概览
set(MY_STR "Hello, World!")
4.5.2 常用字符串操作
set(TEXT "Hello, CMake World!")
# 长度
string(LENGTH "${TEXT}" len) # len = 20
# 子串
string(SUBSTRING "${TEXT}" 0 5 sub) # sub = "Hello"
# 查找
string(FIND "${TEXT}" "CMake" pos) # pos = 7(未找到返回 -1)
# 替换
string(REPLACE "CMake" "World" out "${TEXT}") # out = "Hello, World World!"
# 正则匹配
string(REGEX MATCH "[A-Z]+" result "${TEXT}") # result = "H"
string(REGEX MATCHALL "[A-Z]+" result "${TEXT}") # result = "H;C;W"
string(REGEX REPLACE "World" "Universe" result "${TEXT}")
# result = "Hello, CMake Universe!"
4.5.3 字符串转换
set(NAME "hello_world")
# 大小写转换
string(TOUPPER "${NAME}" upper) # upper = "HELLO_WORLD"
string(TOLOWER "${NAME}" lower) # lower = "hello_world"
# 首字母大写
string(MAKE_C_IDENTIFIER "${NAME}" ident) # 转换为合法 C 标识符
string(MAKE_C_IDENTIFIER "hello-world" ident) # ident = "hello_world"
# 哈希
string(MD5 hash "Hello") # hash = 8b1a9953c4611296a827abf8c47804d7
string(SHA256 hash "Hello") # SHA256 哈希
# UUID
string(UUID result NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8
NAME "www.example.com" TYPE SHA1)
4.5.4 JSON 操作(CMake 3.19+)
set(JSON_STR [[
{
"name": "MyApp",
"version": "1.0",
"dependencies": ["fmt", "spdlog"],
"config": {
"debug": true,
"port": 8080
}
}
]])
# 获取值
string(JSON name GET "${JSON_STR}" "name") # name = "MyApp"
string(JSON version GET "${JSON_STR}" "version") # version = "1.0"
string(JSON debug GET "${JSON_STR}" "config" "debug") # debug = true
string(JSON deps_len LENGTH "${JSON_STR}" "dependencies") # deps_len = 2
string(JSON dep GET "${JSON_STR}" "dependencies" 1) # dep = "spdlog"
# 修改值
string(JSON JSON_STR SET "${JSON_STR}" "version" "2.0")
4.6 数学运算
# 基本运算
math(EXPR result "2 + 3") # result = 5
math(EXPR result "10 * 2 + 1") # result = 21
math(EXPR result "100 / 3") # result = 33(整数除法)
math(EXPR result "100 % 3") # result = 1(取模)
# 十六进制
math(EXPR result "0xFF") # result = 255
math(EXPR hex "255" OUTPUT_FORMAT HEXADECIMAL) # hex = ff
# 递增计数器
math(EXPR COUNT "${COUNT} + 1")
4.7 作用域(Scope)
4.7.1 作用域规则
全局作用域
├── 目录作用域(每个 CMakeLists.txt)
│ ├── 子目录作用域(add_subdirectory 创建)
│ │ └── ...
│ └── ...
└── ...
# 父级 CMakeLists.txt
set(PARENT_VAR "I am parent")
add_subdirectory(subdir)
# 在 subdir/CMakeLists.txt 中:
message("${PARENT_VAR}") # 可以访问父级变量
set(CHILD_VAR "I am child")
# CHILD_VAR 在父级中不可见
4.7.2 PARENT_SCOPE
# 子目录中修改父级变量
# subdir/CMakeLists.txt
set(MY_VAR "new value" PARENT_SCOPE)
# 修改父级列表
set(MY_LIST a b c PARENT_SCOPE)
4.7.3 函数作用域
function(my_func)
# 函数有自己的作用域
set(LOCAL_VAR "inside function")
message("函数内: ${LOCAL_VAR}")
endfunction()
my_func()
message("函数外: ${LOCAL_VAR}") # 空!函数内变量不可见
# 在函数中修改调用者的变量
function(my_func)
set(RESULT "computed value" PARENT_SCOPE)
endfunction()
my_func()
message("${RESULT}") # "computed value"
4.7.4 全局作用域
# 设置全局属性
set_property(GLOBAL PROPERTY MY_GLOBAL_PROP "value")
get_property(result GLOBAL PROPERTY MY_GLOBAL_PROP)
# 追加到全局属性
set_property(GLOBAL APPEND PROPERTY MY_LIST item1)
set_property(GLOBAL APPEND PROPERTY MY_LIST item2)
4.8 特殊变量
4.8.1 CMake 内置变量
| 变量 | 说明 |
|---|---|
CMAKE_SOURCE_DIR | 顶层源码目录 |
CMAKE_BINARY_DIR | 顶层构建目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前源码目录 |
CMAKE_CURRENT_BINARY_DIR | 当前构建目录 |
CMAKE_CURRENT_LIST_DIR | 当前 CMakeLists.txt 所在目录 |
CMAKE_CURRENT_LIST_FILE | 当前正在处理的文件路径 |
CMAKE_CURRENT_LIST_LINE | 当前行号 |
CMAKE_PROJECT_NAME | 顶层 project 名称 |
CMAKE_PROJECT_VERSION | 顶层 project 版本 |
CMAKE_BUILD_TYPE | 构建类型 |
CMAKE_CXX_COMPILER | C++ 编译器路径 |
CMAKE_CXX_STANDARD | C++ 标准版本 |
CMAKE_INSTALL_PREFIX | 安装前缀 |
CMAKE_MODULE_PATH | 模块搜索路径 |
4.8.2 项目相关变量
project(MyApp VERSION 2.1.0)
# 由 project() 自动设置
message("${PROJECT_NAME}") # MyApp
message("${PROJECT_VERSION}") # 2.1.0
message("${PROJECT_SOURCE_DIR}") # 源码根目录
message("${PROJECT_BINARY_DIR}") # 构建根目录
# 子项目
project(SubProject VERSION 1.0.0)
message("${PROJECT_NAME}") # SubProject(子项目的值)
4.8.3 目录 vs 源码 vs 构建路径
project/
├── CMakeLists.txt ← CMAKE_SOURCE_DIR = /project
├── src/
│ ├── CMakeLists.txt ← CMAKE_CURRENT_SOURCE_DIR = /project/src
│ └── ...
└── build/ ← CMAKE_BINARY_DIR = /project/build
└── src/
└── ... ← CMAKE_CURRENT_BINARY_DIR = /project/build/src
4.9 高级变量技巧
4.9.1 间接引用(变量变量)
set(PREFIX "MY")
set(MY_NAME "hello")
# 间接引用
set(var_name "${PREFIX}_NAME")
message("${${var_name}}") # hello
# 使用 cmake_language 实现间接设置
cmake_language(CALL cmake_language SET "${var_name}" "world")
4.9.2 三元表达式风格
# CMake 没有三元运算符,但可以用生成器表达式
set(CONFIG_TYPE $<IF:$<CONFIG:Debug>,debug,release>)
# 或用 if-else
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CONFIG_TYPE "debug")
else()
set(CONFIG_TYPE "release")
endif()
4.9.3 条件设置变量
# 如果变量未定义,设置默认值
if(NOT DEFINED MY_VAR)
set(MY_VAR "default_value")
endif()
# 或使用 if 缩写
set(MY_VAR "default_value") # 先设默认值
if(DEFINED USER_MY_VAR)
set(MY_VAR "${USER_MY_VAR}") # 用户有值则覆盖
endif()
4.10 业务场景
场景:配置管理
# config.cmake — 项目配置文件
set(MYAPP_NAME "MyApplication")
set(MYAPP_VERSION "2.0.0")
set(MYAPP_AUTHOR "开发团队")
set(MYAPP_FEATURES
"logging"
"networking"
"database"
)
# CMakeLists.txt
include(config.cmake)
configure_file(
${PROJECT_SOURCE_DIR}/config.h.in
${PROJECT_BINARY_DIR}/config.h
)
config.h.in 模板:
#pragma once
#define APP_NAME "@MYAPP_NAME@"
#define APP_VERSION "@MYAPP_VERSION@"
#define APP_AUTHOR "@MYAPP_AUTHOR@"
#cmakedefine FEATURE_LOGGING
#cmakedefine FEATURE_NETWORKING
4.11 注意事项
| 问题 | 说明 |
|---|---|
${VAR} 未定义时为空字符串 | 不会报错,需检查 DEFINED |
| 缓存变量不会被 set() 覆盖 | 需要 FORCE 标志 |
| 列表展开问题 | 传递列表时注意引号使用 |
| 作用域隔离 | 函数和子目录有独立作用域 |
| 环境变量不持久化 | 仅当前 CMake 进程有效 |
4.12 扩展阅读
上一章:第 3 章 — 基础入门 | 下一章:第 5 章 — 目标与属性 →