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()
小技巧:我个人习惯用ON和OFF来表示布尔值。为什么?因为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中各种数据类型的关系和使用场景:
4.7 总结与避坑
好了,咱们把CMake的五种数据类型都过了一遍。最后我总结几个实战中容易踩的坑:
- 引号问题:路径带空格时一定要加双引号,否则CMake会把它拆成多个参数。
- 列表遍历:
foreach遍历列表时,如果列表元素本身包含分号,会被错误拆分。用foreach(item IN LISTS mylist)可以避免。 - 缓存变量覆盖:普通变量会覆盖同名的缓存变量。如果你想让缓存变量生效,用
if(NOT DEFINED CACHE{var})判断。 - 环境变量作用域:
set(ENV{...})只影响当前CMake进程,不会影响子进程或终端。需要持久化时,用缓存变量。
我的建议:刚开始学CMake时,不用纠结数据类型。你只要记住“一切皆字符串”,然后按需使用列表和缓存变量就够了。用得多了,自然就熟练了。
公众号:蓝海资料掘金营,微信deep3321