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

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, 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 章 — 目标与属性 →