4、CMake中的数据类型:字符串、列表、布尔值、缓存变量、环境变量

说实话,刚接触CMake那会儿,我总觉得它就是个“高级点的makefile”。但用久了才发现,CMake其实是一门完整的脚本语言。既然是语言,就绕不开数据类型这个话题。今天咱们就把CMake里的几种核心数据类型掰扯清楚。

嗯,这里要注意——CMake的数据类型不像C++或Python那么严格。它本质上一切都是字符串。但为了方便使用,CMake给了我们一些“语法糖”,让字符串能扮演不同角色。

4.1 字符串:最基础的存在

字符串是CMake里最常用的数据类型。你写的每个变量值,本质上都是字符串。

set(MY_NAME "CMake实战")
set(VERSION_STR "3.28.0")
set(DESCRIPTION "这是一个用于演示的字符串")

我个人习惯用双引号包裹字符串,虽然CMake允许不加引号。但加了引号,能避免很多奇怪的解析问题。我在项目中遇到过这样一个坑:

我曾经写过一个CMakeLists.txt,里面有个路径变量没加引号:

set(SOURCE_DIR src/main)  # 没加引号,看起来没问题

后来路径变成了 src/main app(中间有空格),CMake直接把它解析成了两个参数。程序编译报错,我排查了半天才发现是引号惹的祸。从那以后,但凡路径或带空格的字符串,我必加双引号。

4.2 列表:用分号分隔的“伪数组”

CMake没有真正的数组类型。列表说白了就是用分号分隔的字符串。你想想看,这其实很巧妙——一个字符串就能表示一组数据。

set(SOURCES main.cpp utils.cpp network.cpp)
# 等价于 set(SOURCES "main.cpp;utils.cpp;network.cpp")

message("${SOURCES}")  # 输出: main.cpp;utils.cpp;network.cpp

列表操作有几个常用命令:

# 获取列表长度
list(LENGTH SOURCES len)
message("源文件数量: ${len}")

# 获取第一个元素
list(GET SOURCES 0 first_file)
message("第一个文件: ${first_file}")

# 追加元素
list(APPEND SOURCES "database.cpp")
message("追加后: ${SOURCES}")

这里有个容易混淆的地方——foreach循环遍历列表时,CMake会自动按分号拆分:

foreach(file ${SOURCES})
    message("处理文件: ${file}")
endforeach()

为什么会这样?因为CMake在解析${SOURCES}时,会把分号当成参数分隔符。说白了,列表就是CMake用来模拟数组的“障眼法”。

4.3 布尔值:真真假假

CMake里没有专门的布尔类型。真和假,是通过约定俗成的字符串值来判断的。

真值(视为ON) 假值(视为OFF)
ON, YES, TRUE, Y, 1 OFF, NO, FALSE, N, 0, IGNORE, NOTFOUND
空字符串(某些上下文) 未定义的变量
set(ENABLE_LOGGING ON)
set(USE_OPENGL TRUE)
set(DEBUG_MODE 1)

if(ENABLE_LOGGING)
    message("日志已启用")
endif()

# 判断变量是否定义
if(DEFINED USE_OPENGL)
    message("OpenGL选项已设置")
endif()

小技巧:我个人习惯用ONOFF来表示布尔值。为什么?因为CMake内部很多选项(比如BUILD_SHARED_LIBS)就是用ON/OFF。保持一致,代码更易读。

4.4 缓存变量:跨文件共享的秘密

缓存变量是CMake里一个很重要的概念。普通变量只在当前CMakeLists.txt有效,但缓存变量会写入CMakeCache.txt,在整个构建过程中持久存在。

# 定义缓存变量
set(MY_CUSTOM_PATH "/usr/local/myapp" CACHE PATH "自定义安装路径")

# 带类型的缓存变量
set(BUILD_TESTS ON CACHE BOOL "是否构建测试")
set(TARGET_ARCH "x86_64" CACHE STRING "目标架构")

# 强制缓存变量(即使已存在也会覆盖)
set(FORCE_VAR "new_value" CACHE STRING "强制更新" FORCE)

缓存变量的类型决定了CMake GUI里显示什么控件:

类型 说明 GUI控件
BOOL 布尔值 复选框
PATH 文件路径 文件选择器
STRING 任意字符串 文本框
FILEPATH 文件路径(文件必须存在) 文件选择器

重点:缓存变量是CMake配置阶段的核心机制。你运行cmake -DCMAKE_INSTALL_PREFIX=/usr时,就是在设置缓存变量。我建议所有需要用户自定义的选项,都用缓存变量暴露出来。

4.5 环境变量:与系统交互的桥梁

CMake可以读取和设置操作系统的环境变量。这在跨平台构建时特别有用。

# 读取环境变量
message("HOME目录: $ENV{HOME}")
message("PATH: $ENV{PATH}")

# 设置环境变量(仅影响当前CMake进程)
set(ENV{MY_VAR} "hello_from_cmake")
message("自定义环境变量: $ENV{MY_VAR}")

注意,set(ENV{...})只修改当前CMake进程的环境变量,不会影响父进程(你的终端)。我在项目中遇到过这样的场景:

我曾经需要在一个大型项目中引用第三方库。那个库的查找脚本依赖THIRD_PARTY_ROOT环境变量。我在CMakeLists.txt里用set(ENV{THIRD_PARTY_ROOT} "/opt/third_party")设置后,find_package就能正常工作了。但如果用户没有设置这个环境变量,构建就会失败。后来我改成了缓存变量+环境变量回退的方案:

if(NOT DEFINED CACHE{THIRD_PARTY_ROOT})
    if(DEFINED ENV{THIRD_PARTY_ROOT})
        set(THIRD_PARTY_ROOT "$ENV{THIRD_PARTY_ROOT}" CACHE PATH "第三方库路径")
    else()
        set(THIRD_PARTY_ROOT "/opt/third_party" CACHE PATH "第三方库路径" FORCE)
    endif()
endif()

这样既支持环境变量,又提供了默认值,用户还能通过-D选项覆盖。

4.6 知识体系总览

下面这张图总结了CMake中各种数据类型的关系和使用场景:

CMake数据类型体系 CMake变量 字符串 列表 布尔值 缓存变量 环境变量 最基础类型 一切皆字符串 分号分隔 模拟数组 ON/OFF约定 条件判断 持久化存储 CMakeCache.txt $ENV{...} 系统交互 典型使用场景 文件名、路径 版本号、描述 源文件列表 依赖库列表 编译选项开关 功能启用/禁用 安装路径 用户自定义选项 系统PATH 第三方库根目录

4.7 总结与避坑

好了,咱们把CMake的五种数据类型都过了一遍。最后我总结几个实战中容易踩的坑:

  1. 引号问题:路径带空格时一定要加双引号,否则CMake会把它拆成多个参数。
  2. 列表遍历foreach遍历列表时,如果列表元素本身包含分号,会被错误拆分。用foreach(item IN LISTS mylist)可以避免。
  3. 缓存变量覆盖:普通变量会覆盖同名的缓存变量。如果你想让缓存变量生效,用if(NOT DEFINED CACHE{var})判断。
  4. 环境变量作用域set(ENV{...})只影响当前CMake进程,不会影响子进程或终端。需要持久化时,用缓存变量。

我的建议:刚开始学CMake时,不用纠结数据类型。你只要记住“一切皆字符串”,然后按需使用列表和缓存变量就够了。用得多了,自然就熟练了。

公众号:蓝海资料掘金营,微信deep3321