5. 列表操作:list命令详解

CMake 里的列表,说白了就是字符串数组。你可能会想:「不就是个数组吗,有啥好讲的?」嗯,我刚开始也这么觉得。直到我在一个大型跨平台项目里,因为列表操作不当,导致链接顺序全乱套……那一次排查花了我整整一个下午。

CMake 的 list 命令,功能比你想的要强大。它支持增删改查,还能跟字符串互相转换。今天我们就把它彻底讲透。

核心要点: CMake 列表本质上是分号分隔的字符串。每个元素用分号隔开,但你可以用引号包裹包含空格或特殊字符的元素。

5.1 列表的创建与基本操作

先看怎么创建一个列表。其实很简单:

# 创建一个列表
set(MY_LIST "apple" "banana" "cherry")
# 等价于
set(MY_LIST apple banana cherry)
# 也等价于
set(MY_LIST "apple;banana;cherry")

我个人习惯用第一种写法,清晰明了。但要注意,如果你写成这样:

set(MY_LIST "apple banana cherry")

那就只有一个元素,内容是 "apple banana cherry"。这个坑我踩过,当时死活想不通为什么列表长度是1。

5.2 APPEND:追加元素

list(APPEND ...) 是最常用的操作。把新元素加到列表末尾:

set(FRUITS "apple" "banana")
list(APPEND FRUITS "cherry" "date")
# FRUITS 现在是: apple;banana;cherry;date

我在项目中经常用它来动态收集源文件:

set(SOURCES "")
list(APPEND SOURCES "src/main.cpp")
list(APPEND SOURCES "src/utils.cpp")
# 根据条件追加
if(WIN32)
    list(APPEND SOURCES "src/win_specific.cpp")
endif()

5.3 INSERT:指定位置插入

有时候你需要在特定位置插入元素,而不是追加到末尾。这时候用 list(INSERT ...)

set(COLORS "red" "green" "blue")
list(INSERT COLORS 1 "yellow")
# COLORS 现在是: red;yellow;green;blue

索引从0开始。如果你插入的位置超过了列表长度,CMake 会报错。嗯,这里要注意:索引不能是负数,也不能越界。

5.4 REMOVE_ITEM:删除指定元素

删除操作也很常见:

set(NAMES "Alice" "Bob" "Charlie" "Bob")
list(REMOVE_ITEM NAMES "Bob")
# NAMES 现在是: Alice;Charlie

注意,REMOVE_ITEM 会删除所有匹配的元素,不是只删第一个。我曾经在清理重复的库路径时,就用这个特性一次性干掉所有重复项。

5.5 LENGTH:获取列表长度

想知道列表里有多少个元素?用 list(LENGTH ...)

set(ITEMS "a" "b" "c")
list(LENGTH ITEMS ITEM_COUNT)
message("列表长度: ${ITEM_COUNT}")  # 输出: 3

这个操作在循环和条件判断里特别有用。比如:

if(ITEM_COUNT GREATER 0)
    # 列表不为空,继续处理
endif()

5.6 GET:按索引获取元素

获取指定位置的元素:

set(FRUITS "apple" "banana" "cherry")
list(GET FRUITS 1 SELECTED_FRUIT)
# SELECTED_FRUIT 现在是: banana

你也可以一次获取多个:

list(GET FRUITS 0 2 SELECTED_FRUITS)
# SELECTED_FRUITS 现在是: apple;cherry
小技巧: 如果你需要获取最后一个元素,可以先获取长度,然后用 MATH(EXPR ...) 计算索引。不过 CMake 3.15+ 支持了负索引,list(GET FRUITS -1 LAST) 就能拿到最后一个。

5.7 其他常用操作

除了上面这些,还有几个操作我经常用:

命令 作用 示例
FIND 查找元素索引 list(FIND FRUITS "banana" IDX)
REMOVE_DUPLICATES 去重 list(REMOVE_DUPLICATES FRUITS)
SORT 排序 list(SORT FRUITS)
REVERSE 反转 list(REVERSE FRUITS)
SUBLIST 取子列表 list(SUBLIST FRUITS 1 2 SUB)

举个例子,去重操作在实际项目中非常实用:

set(LIB_PATHS "/usr/lib" "/usr/local/lib" "/usr/lib")
list(REMOVE_DUPLICATES LIB_PATHS)
# 现在只有两个路径了

5.8 列表与字符串互转

这是很多人容易搞混的地方。CMake 里列表和字符串其实是一回事,只是表现形式不同。

列表 → 字符串

foreach 拼接,或者直接用 set 赋值:

set(LIST_VAR "a" "b" "c")
set(STR_VAR "${LIST_VAR}")
# STR_VAR 现在是: a;b;c

如果你想用空格或其他分隔符连接,可以用 string(JOIN ...)

string(JOIN ", " STR_VAR ${LIST_VAR})
# STR_VAR 现在是: a, b, c

字符串 → 列表

string(REPLACE ...) 把分隔符替换成分号:

set(STR "a,b,c")
string(REPLACE "," ";" LIST_VAR ${STR})
# LIST_VAR 现在是: a;b;c

或者直接用 separate_arguments 命令:

separate_arguments(LIST_VAR NATIVE_COMMAND ${STR})
# 根据平台自动解析参数
注意: 当字符串里包含分号时,直接赋值给列表会导致意外拆分。比如 set(LIST_VAR "a;b;c") 会得到三个元素。如果你想要一个包含分号的元素,必须用转义或引号包裹。

5.9 知识体系总览

下面这张图总结了 list 命令的核心操作和它们之间的关系:

CMake list 命令知识体系 CMake 列表 创建 (set) 增 (APPEND/INSERT) 删 (REMOVE_ITEM) 改 (SORT/REVERSE) 查 (LENGTH/GET/FIND) 去重 (REMOVE_DUPLICATES) 子列表 (SUBLIST) 字符串互转 列表本质:分号分隔的字符串 索引从0开始,支持负索引(CMake 3.15+) 引号包裹的元素视为一个整体

5.10 实战经验总结

最后分享几个我在项目中积累的经验:

  • 路径拼接:list(APPEND ...) 比用字符串拼接更安全,不会漏掉分号
  • 条件编译: 根据平台和编译器动态追加源文件,这是 list 最常用的场景
  • 链接库顺序:list(INSERT ...) 控制库的链接顺序,避免未定义引用
  • 调试技巧:message("${MY_LIST}") 打印列表,分号分隔一目了然
我的习惯: 每次操作列表后,我都会用 message(STATUS ...) 打印一下当前内容。尤其是在复杂的条件分支里,这能帮你快速定位问题。

列表操作看似简单,但用好了能极大提升 CMake 脚本的灵活性和可维护性。你想想看,一个 list(APPEND ...) 就能让源文件管理变得井井有条,何乐而不为呢?


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