强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

CMake 从入门到精通:完整教程 / 第 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, Y0, OFF, NO, FALSE, N
on, yes, true, yoff, 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 常用环境变量

环境变量说明
CCC 编译器路径
CXXC++ 编译器路径
CFLAGSC 编译器标志
CXXFLAGSC++ 编译器标志
LDFLAGS链接器标志
CMAKE_PREFIX_PATH依赖包搜索路径
CMAKE_TOOLCHAIN_FILE工具链文件路径
PATH可执行文件搜索路径
PKG_CONFIG_PATHpkg-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_COMPILERC++ 编译器路径
CMAKE_CXX_STANDARDC++ 标准版本
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 章 — 目标与属性 →