1. 引言

CMake是一个开源、跨平台的构建自动化工具,它使用配置文件(CMakeLists.txt)来生成标准的构建文件(如Makefile或Visual Studio项目)。CMake被广泛用于C++项目的构建系统管理,但也可以用于其他语言的构建。

在CMake中,列表(list)是一种基本而重要的数据结构。虽然CMake没有传统编程语言中的”数组”概念,但列表功能在大多数情况下可以充当数组的角色。列表是一系列由分号(;)分隔的字符串,CMake提供了丰富的命令来操作这些列表。

理解如何有效操作列表对于编写高效、灵活的CMake脚本至关重要。无论是处理源文件列表、管理编译选项,还是处理依赖项,列表操作都是必不可少的技能。

2. 列表的基础知识

2.1 列表的概念与表示

在CMake中,列表本质上是一个由分号(;)分隔的字符串。例如,”a;b;c”是一个包含三个元素的列表。CMake在内部处理列表时,会自动处理这种分隔。

列表可以显式或隐式地创建:

# 显式创建列表 set(my_list "a" "b" "c") # 隐式创建列表 set(my_other_list "a;b;c") # 这与上面的列表等价 

2.2 创建列表

有几种常见的方法可以创建列表:

# 方法1:使用set命令 set(simple_list "item1" "item2" "item3") # 方法2:通过分号分隔的字符串创建 set(another_list "item1;item2;item3") # 方法3:通过变量扩展创建 set(base_list "item1" "item2") set(extended_list ${base_list} "item3") # 结果为 "item1;item2;item3" 

2.3 访问列表元素

CMake提供了list命令来访问和操作列表。要获取列表的长度或特定元素:

set(my_list "apple" "banana" "cherry") # 获取列表长度 list(LENGTH my_list list_length) message(STATUS "List length: ${list_length}") # 输出: List length: 3 # 获取特定索引的元素(索引从0开始) list(GET my_list 1 first_item) message(STATUS "Item at index 1: ${first_item}") # 输出: Item at index 1: banana # 获取子列表 list(SUBLIST my_list 1 2 sublist) message(STATUS "Sublist: ${sublist}") # 输出: Sublist: banana;cherry 

2.4 列表的字符串表示

当需要将列表作为字符串处理时,可以使用JOIN操作:

set(my_list "apple" "banana" "cherry") # 用逗号和空格连接列表元素 list(JOIN my_list ", " joined_string) message(STATUS "Joined string: ${joined_string}") # 输出: Joined string: apple, banana, cherry 

3. 列表的基本操作

3.1 添加元素

向列表添加元素是常见的操作:

set(my_list "item1" "item2") # 在末尾添加元素 list(APPEND my_list "item3") message(STATUS "After append: ${my_list}") # 输出: After append: item1;item2;item3 # 在开头添加元素 list(PREPEND my_list "item0") message(STATUS "After prepend: ${my_list}") # 输出: After prepend: item0;item1;item2;item3 

3.2 插入元素

在指定位置插入元素:

set(my_list "item1" "item2" "item3") # 在索引1处插入新元素 list(INSERT my_list 1 "new_item") message(STATUS "After insert: ${my_list}") # 输出: After insert: item1;new_item;item2;item3 

3.3 删除元素

从列表中删除元素:

set(my_list "item1" "item2" "item3" "item2") # 按索引删除元素 list(REMOVE_AT my_list 1) message(STATUS "After remove at: ${my_list}") # 输出: After remove at: item1;item3;item2 # 按值删除元素(删除所有匹配项) list(REMOVE_ITEM my_list "item2") message(STATUS "After remove item: ${my_list}") # 输出: After remove item: item1;item3 # 删除重复元素 list(REMOVE_DUPLICATES my_list) message(STATUS "After remove duplicates: ${my_list}") # 输出: After remove duplicates: item1;item3 

3.4 修改元素

修改列表中的特定元素:

set(my_list "item1" "item2" "item3") # 修改特定索引的元素 list(REMOVE_AT my_list 1) list(INSERT my_list 1 "new_item2") message(STATUS "After modification: ${my_list}") # 输出: After modification: item1;new_item2;item3 

4. 列表的高级操作

4.1 搜索元素

在列表中搜索特定元素:

set(my_list "apple" "banana" "cherry" "date") # 查找元素的索引 list(FIND my_list "cherry" index) message(STATUS "Index of 'cherry': ${index}") # 输出: Index of 'cherry': 2 # 检查元素是否存在 if(index GREATER -1) message(STATUS "'cherry' found in the list") else() message(STATUS "'cherry' not found in the list") endif() 

4.2 排序列表

对列表进行排序:

set(my_list "banana" "apple" "date" "cherry") # 按字母顺序升序排序 list(SORT my_list) message(STATUS "Sorted list: ${my_list}") # 输出: Sorted list: apple;banana;cherry;date # 按字母顺序降序排序 list(SORT my_list COMPARE STRING ORDER DESCENDING) message(STATUS "Reverse sorted list: ${my_list}") # 输出: Reverse sorted list: date;cherry;banana;apple # 按自然顺序排序(考虑数字) set(numeric_list "item10" "item2" "item1") list(SORT numeric_list COMPARE NATURAL) message(STATUS "Naturally sorted list: ${numeric_list}") # 输出: Naturally sorted list: item1;item2;item10 

4.3 列表转换

将列表转换为其他格式或从其他格式创建列表:

# 将字符串转换为列表 set(string "a b c") string(REPLACE " " ";" list_from_string "${string}") message(STATUS "List from string: ${list_from_string}") # 输出: List from string: a;b;c # 将列表转换为字符串 set(my_list "a" "b" "c") string(REPLACE ";" " " string_from_list "${my_list}") message(STATUS "String from list: ${string_from_list}") # 输出: String from list: a b c 

4.4 列表过滤

根据特定条件过滤列表:

set(source_files "main.cpp" "utils.cpp" "test_main.cpp" "utils.h" "test_utils.h") # 过滤出所有.cpp文件 set(cpp_files "") foreach(file ${source_files}) if(file MATCHES "\.cpp$") list(APPEND cpp_files ${file}) endif() endforeach() message(STATUS "C++ files: ${cpp_files}") # 输出: C++ files: main.cpp;utils.cpp;test_main.cpp # 使用filter命令(CMake 3.6+) list(FILTER source_files INCLUDE REGEX "\.(cpp|h)$") message(STATUS "Filtered source files: ${source_files}") # 输出: Filtered source files: main.cpp;utils.cpp;test_main.cpp;utils.h;test_utils.h 

5. 列表在CMake脚本中的实际应用

5.1 管理源文件

列表最常见的用途之一是管理项目中的源文件:

# 收集所有源文件 set(SOURCES src/main.cpp src/utils.cpp src/graphics/renderer.cpp src/graphics/shader.cpp ) # 收集所有头文件 set(HEADERS include/utils.h include/graphics/renderer.h include/graphics/shader.h ) # 创建可执行文件 add_executable(my_app ${SOURCES} ${HEADERS}) # 条件性地添加源文件 if(ENABLE_TESTS) list(APPEND SOURCES test/test_main.cpp test/test_utils.cpp ) add_executable(my_app_test ${SOURCES} ${HEADERS}) endif() 

5.2 管理编译选项

使用列表管理编译选项和定义:

# 基本编译选项 set(COMMON_FLAGS -Wall -Wextra -O2 ) # 调试模式下的额外选项 if(CMAKE_BUILD_TYPE STREQUAL "Debug") list(APPEND COMMON_FLAGS -g -DDEBUG ) endif() # 应用编译选项 target_compile_options(my_app PRIVATE ${COMMON_FLAGS}) # 管理预处理器定义 set(DEFINITIONS PLATFORM_DESKTOP VERSION_MAJOR=1 VERSION_MINOR=0 ) if(ENABLE_FEATURE_X) list(APPEND DEFINITIONS FEATURE_X) endif() target_compile_definitions(my_app PRIVATE ${DEFINITIONS}) 

5.3 管理链接库

使用列表管理项目依赖的库:

# 系统库 set(SYSTEM_LIBS pthread dl ) # 第三方库 find_package(Boost REQUIRED COMPONENTS filesystem system) set(THIRD_PARTY_LIBS Boost::filesystem Boost::system ) # 项目内部库 add_subdirectory(libs/utils) add_subdirectory(libs/graphics) set(INTERNAL_LIBS utils graphics ) # 链接所有库 target_link_libraries(my_app PRIVATE ${SYSTEM_LIBS} ${THIRD_PARTY_LIBS} ${INTERNAL_LIBS} ) 

5.4 处理平台特定代码

使用列表处理不同平台的特定代码:

# 通用源文件 set(COMMON_SOURCES src/main.cpp src/utils.cpp ) # 平台特定源文件 if(WIN32) list(APPEND COMMON_SOURCES src/platform/windows.cpp ) elseif(UNIX AND NOT APPLE) list(APPEND COMMON_SOURCES src/platform/linux.cpp ) elseif(APPLE) list(APPEND COMMON_SOURCES src/platform/macos.cpp ) endif() # 创建可执行文件 add_executable(my_app ${COMMON_SOURCES}) 

6. 列表操作的最佳实践和常见陷阱

6.1 最佳实践

6.1.1 使用列表变量而不是直接操作字符串

# 好的做法 set(my_list "item1" "item2" "item3") list(APPEND my_list "item4") # 不好的做法 set(my_string "item1;item2;item3") set(my_string "${my_string};item4") # 容易出错,特别是当元素包含分号时 

6.1.2 使用引号处理包含空格的元素

# 好的做法 set(my_list "item with spaces" "another item") # 不好的做法 set(my_list item with spaces another item) # 这会创建一个包含4个元素的列表 

6.1.3 使用列表命令而不是手动操作

# 好的做法 set(my_list "item1" "item2" "item3") list(REMOVE_ITEM my_list "item2") # 不好的做法 set(new_list "") foreach(item ${my_list}) if(NOT item STREQUAL "item2") list(APPEND new_list ${item}) endif() endforeach() set(my_list ${new_list}) 

6.2 常见陷阱

6.2.1 忽略列表中的空元素

set(my_list "item1" "" "item3") list(LENGTH my_list len) message(STATUS "Length: ${len}") # 输出: Length: 3,空元素也被计算在内 

6.2.2 错误地处理包含分号的元素

# 问题:元素本身包含分号 set(problematic_list "item1" "item;with;semicolons" "item3") list(LENGTH problematic_list len) message(STATUS "Length: ${len}") # 输出: Length: 5,而不是预期的3 # 解决方案:使用引号 set(correct_list "item1" "item;with;semicolons" "item3") list(LENGTH correct_list len) message(STATUS "Length: ${len}") # 输出: Length: 3 

6.2.3 在循环中修改列表

set(my_list "item1" "item2" "item3") # 危险:在迭代过程中修改列表 foreach(item ${my_list}) if(item STREQUAL "item2") list(REMOVE_ITEM my_list ${item}) # 这可能导致意外的行为 endif() endforeach() # 更安全的方法:创建一个新列表 set(my_list "item1" "item2" "item3") set(new_list "") foreach(item ${my_list}) if(NOT item STREQUAL "item2") list(APPEND new_list ${item}) endif() endforeach() set(my_list ${new_list}) 

7. 高级技巧和实用示例

7.1 列表转换和映射

将一个列表转换为另一个列表,应用某种转换:

# 将源文件列表转换为目标文件列表 set(source_files "src/main.cpp" "src/utils.cpp" "src/graphics/renderer.cpp") set(object_files "") foreach(src ${source_files}) # 替换扩展名和路径 string(REPLACE ".cpp" ".o" obj "${src}") string(REPLACE "src/" "build/obj/" obj "${obj}") list(APPEND object_files ${obj}) endforeach() message(STATUS "Object files: ${object_files}") # 输出: Object files: build/obj/main.o;build/obj/utils.o;build/obj/graphics/renderer.o 

7.2 列表去重和交集

实现列表去重和计算交集:

# 列表去重 set(list_with_duplicates "a" "b" "a" "c" "b" "d") set(unique_list "") foreach(item ${list_with_duplicates}) list(FIND unique_list ${item} index) if(index EQUAL -1) list(APPEND unique_list ${item}) endif() endforeach() message(STATUS "Unique list: ${unique_list}") # 输出: Unique list: a;b;c;d # 计算两个列表的交集 set(list1 "a" "b" "c" "d") set(list2 "b" "c" "e" "f") set(intersection "") foreach(item ${list1}) list(FIND list2 ${item} index) if(NOT index EQUAL -1) list(APPEND intersection ${item}) endif() endforeach() message(STATUS "Intersection: ${intersection}") # 输出: Intersection: b;c 

7.3 多维列表模拟

虽然CMake没有真正的多维列表,但可以通过特定模式模拟:

# 创建一个"2D列表"(实际上是列表的列表) set(matrix "1;2;3" "4;5;6" "7;8;9" ) # 访问"2D列表"的元素 set(row 1) # 第二行(索引从0开始) set(col 2) # 第三列(索引从0开始) list(GET matrix ${row} row_content) string(REPLACE ";" " " row_elements "${row_content}") message(STATUS "Row ${row}: ${row_elements}") # 将行内容转换为真正的列表 string(REPLACE ";" ";" row_list "${row_content}") list(GET row_list ${col} element) message(STATUS "Element at (${row},${col}): ${element}") # 输出: Element at (1,2): 6 

7.4 列表操作函数封装

创建自定义函数来封装常见的列表操作:

# 自定义函数:从列表中移除匹配正则表达式的元素 function(remove_regex_matches list_name regex) set(input_list ${${list_name}}) set(result_list "") foreach(item ${input_list}) if(NOT item MATCHES "${regex}") list(APPEND result_list ${item}) endif() endforeach() set(${list_name} ${result_list} PARENT_SCOPE) endfunction() # 使用自定义函数 set(test_list "file1.cpp" "file2.h" "test_file1.cpp" "test_file2.h") remove_regex_matches(test_list "^test_") message(STATUS "Filtered list: ${test_list}") # 输出: Filtered list: file1.cpp;file2.h # 自定义函数:将列表转换为键值对列表 function(create_key_value_pairs list_name separator) set(input_list ${${list_name}}) set(result_list "") foreach(item ${input_list}) string(REGEX REPLACE "${separator}";";" pair "${item}") list(APPEND result_list ${pair}) endforeach() set(${list_name} ${result_list} PARENT_SCOPE) endfunction() # 使用自定义函数 set(config_list "DEBUG:TRUE" "LOG_LEVEL:INFO" "MAX_CONNECTIONS:10") create_key_value_pairs(config_list ":") message(STATUS "Key-value pairs: ${config_list}") # 输出: Key-value pairs: DEBUG;TRUE;LOG_LEVEL;INFO;MAX_CONNECTIONS;10 

7.5 处理命令行参数

使用列表处理传递给CMake脚本的命令行参数:

# 假设CMake通过以下方式调用: # cmake -DARGS="-O2;-Wall;-DDEBUG" -P process_args.cmake if(DEFINED ARGS) # 将字符串转换为列表 string(REPLACE ";" " " args_string "${ARGS}") message(STATUS "Arguments string: ${args_string}") # 直接使用列表 set(args_list ${ARGS}) message(STATUS "Arguments list: ${args_list}") # 处理每个参数 foreach(arg ${args_list}) if(arg MATCHES "^-D(.+)=(.+)$") set(var_name ${CMAKE_MATCH_1}) set(var_value ${CMAKE_MATCH_2}) message(STATUS "Define: ${var_name} = ${var_value}") elseif(arg MATCHES "^-") message(STATUS "Flag: ${arg}") else() message(STATUS "Other argument: ${arg}") endif() endforeach() endif() 

8. 总结

在CMake中,列表是一种强大而灵活的数据结构,虽然它本质上是由分号分隔的字符串,但CMake提供了丰富的命令来操作这些列表。通过本文,我们深入探讨了CMake中列表操作的各个方面,从基础的创建、访问和修改,到高级的搜索、排序和转换。

我们了解了列表在CMake脚本中的实际应用,包括管理源文件、编译选项、链接库以及处理平台特定代码。同时,我们也讨论了列表操作的最佳实践和常见陷阱,帮助读者避免一些常见的错误。

最后,我们探索了一些高级技巧和实用示例,如列表转换和映射、列表去重和交集、模拟多维列表、封装自定义函数以及处理命令行参数。这些技巧可以帮助读者更有效地利用CMake中的列表功能,编写更加灵活和强大的构建脚本。

掌握CMake中的列表操作是成为CMake专家的重要一步。希望本文能够帮助读者深入理解CMake中的列表与数组操作,从基础到高级应用,为他们的项目构建提供有力支持。