CMake进阶:解析自定义函数 / 宏的可变参数(ARGN)的指令cmake_parse_arguments
目录
1.简介
2.核心作用
3.常见使用场景
4.实战示例:自定义可执行文件函数
5.实战示例:nuttx_add_subdirectory
5.1.nuttx_add_subdirectory简介
5.2.背景:NuttX 的模块化构建体系
5.3.nuttx_add_subdirectory 的核心作用
5.4.实战示例:使用 nuttx_add_subdirectory 集成模块
5.5.关键区别:nuttx_add_subdirectory vs 标准 add_subdirectory
6.注意事项
7.总结
相关链接
1.简介
在 CMake 中,cmake_parse_arguments
是一个用于解析自定义函数 / 宏的可变参数(ARGN
) 的核心命令,专门解决 “手动处理复杂参数列表繁琐且易出错” 的问题。它能自动将参数分类为开关参数(无值)、单值参数(关键字 + 1 个值)、多值参数(关键字 + 多个值),并通过前缀统一管理解析后的变量,避免全局变量污染。大大提高了 CMake 代码的可读性和可维护性。
对于之前 “VS 工程转 CMake” 的场景(如自定义封装 add_executable
、处理第三方库配置),cmake_parse_arguments
可大幅简化参数传递逻辑(例如支持自定义输出目录、依赖库、编译开关等)。
CMake实践: VS2022工程转为CMake工程过程与经验分享
cmake_parse_arguments
的语法格式固定,需指定 “参数分类规则” 和 “待解析的参数列表”:
cmake_parse_arguments(<PREFIX> # 1. 变量前缀(解析后变量名的统一前缀,避免冲突)<OPTIONS> # 2. 开关参数列表(仅需关键字,无值,如 "WITH_DEBUG WITH_TEST")<ONE_VALUE_ARGS> # 3. 单值参数列表(关键字后必须跟1个值,如 "OUTPUT_DIR VERSION")<MULTI_VALUE_ARGS># 4. 多值参数列表(关键字后可跟多个值,如 "SOURCES DEPENDS")${ARGN} # 5. 待解析的可变参数(通常是函数/宏的 ARGN 参数)
)
参数详解
参数类别 | 作用说明 | 示例 |
---|---|---|
<PREFIX> | 解析后所有变量的前缀(如 MY_EXE ),最终变量名格式为 PREFIX_<参数名> 。 | 若前缀为 MY_EXE ,开关参数 WITH_DEBUG 会生成 MY_EXE_WITH_DEBUG 。 |
<OPTIONS> | 开关参数(布尔型):仅需指定关键字,存在则为 TRUE ,不存在为 FALSE 。 | "WITH_DEBUG WITH_TEST" |
<ONE_VALUE_ARGS> | 单值参数:关键字后必须跟 1 个值(若未跟值,CMake 会报错)。 | "OUTPUT_DIR VERSION" |
<MULTI_VALUE_ARGS> | 多值参数:关键字后可跟 多个值(值会被收集为列表)。 | "SOURCES DEPENDS" |
${ARGN} | 待解析的可变参数(自定义函数 / 宏的 ARGN 变量,包含所有传入的额外参数)。 | 函数调用时传入的 SOURCES main.cpp util.cpp 等。 |
示例1:基本用法
function(my_function)set(options VERBOSE FORCE)set(oneValueArgs NAME OUTPUT)set(multiValueArgs SOURCES INCLUDES)cmake_parse_arguments(MY "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})message("VERBOSE: ${MY_VERBOSE}")message("FORCE: ${MY_FORCE}")message("NAME: ${MY_NAME}")message("OUTPUT: ${MY_OUTPUT}")message("SOURCES: ${MY_SOURCES}")message("INCLUDES: ${MY_INCLUDES}")message("未解析的参数: ${MY_UNPARSED_ARGUMENTS}")
endfunction()# 调用示例
my_function(VERBOSENAME "my_project"SOURCES main.cpp util.cppINCLUDES /usr/include /opt/local/includeFORCEOUTPUT "build"EXTRA_ARG # 这个会被识别为未解析参数
)
输出结果:
VERBOSE: TRUE
FORCE: TRUE
NAME: my_project
OUTPUT: build
SOURCES: main.cpp;util.cpp
INCLUDES: /usr/include;/opt/local/include
未解析的参数: EXTRA_ARG
示例 2:更复杂的函数
function(create_library)set(options STATIC SHARED INTERFACE)set(oneValueArgs TARGET NAME VERSION)set(multiValueArgs SOURCES DEPENDENCIES COMPILE_OPTIONS)cmake_parse_arguments(LIB"${options}""${oneValueArgs}""${multiValueArgs}"${ARGN})# 验证参数if(NOT LIB_TARGET)message(FATAL_ERROR "TARGET argument is required")endif()# 确定库类型if(LIB_STATIC)set(lib_type STATIC)elseif(LIB_SHARED)set(lib_type SHARED)elseif(LIB_INTERFACE)set(lib_type INTERFACE)else()set(lib_type STATIC) # 默认类型endif()# 创建库if(lib_type STREQUAL "INTERFACE")add_library(${LIB_TARGET} INTERFACE)else()add_library(${LIB_TARGET} ${lib_type} ${LIB_SOURCES})endif()# 设置属性if(LIB_NAME)set_target_properties(${LIB_TARGET} PROPERTIES OUTPUT_NAME ${LIB_NAME})endif()if(LIB_DEPENDENCIES)target_link_libraries(${LIB_TARGET} PRIVATE ${LIB_DEPENDENCIES})endif()if(LIB_COMPILE_OPTIONS)target_compile_options(${LIB_TARGET} PRIVATE ${LIB_COMPILE_OPTIONS})endif()
endfunction()# 使用示例
create_library(TARGET mylibSTATICNAME "mylibrary"SOURCES src1.cpp src2.cppDEPENDENCIES Threads::Threads MathLibCOMPILE_OPTIONS -Wall -Wextra
)
示例 3:使用 PARSE_ARGV 处理复杂值
macro(complex_example)# 使用 PARSE_ARGV 版本,从第 0 个参数开始cmake_parse_arguments(PARSE_ARGV 0COMPLEX"ENABLE_A;ENABLE_B""CONFIG_FILE""FEATURES;OPTIONS")message(STATUS "ENABLE_A: ${COMPLEX_ENABLE_A}")message(STATUS "ENABLE_B: ${COMPLEX_ENABLE_B}")message(STATUS "CONFIG_FILE: ${COMPLEX_CONFIG_FILE}")message(STATUS "FEATURES: ${COMPLEX_FEATURES}")message(STATUS "OPTIONS: ${COMPLEX_OPTIONS}")
endmacro()# 调用示例
complex_example(ENABLE_ACONFIG_FILE "/path/to/config"FEATURES "feature1;feature2" "feature3"OPTIONS "option1=value1" "option2=value2"
)
2.核心作用
当你编写自定义 CMake 函数 / 宏时,若需要支持灵活的参数格式(如可选参数、多值参数),cmake_parse_arguments
能帮你:
- 自动区分 开关参数(如
WITH_DEBUG
,仅需指定关键字,无后续值); - 自动提取 单值参数(如
OUTPUT_DIR "bin"
,关键字后紧跟 1 个值); - 自动收集 多值参数(如
SOURCES main.cpp util.cpp
,关键字后紧跟多个值); - 生成统一前缀的变量(如
PREFIX_WITH_DEBUG
、PREFIX_OUTPUT_DIR
),避免变量冲突; - 捕获未解析的参数(便于排查错误)。
3.常见使用场景
1.参数验证
function(validated_function)set(options "")set(oneValueArgs REQUIRED_ARG)set(multiValueArgs "")cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})# 验证必需参数if(NOT FUNC_REQUIRED_ARG)message(FATAL_ERROR "REQUIRED_ARG is required")endif()
endfunction()
2.提供默认值
function(function_with_defaults)set(options "ENABLE_FEATURE")set(oneValueArgs "TIMEOUT")set(multiValueArgs "TAGS")cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})# 设置默认值if(NOT DEFINED FUNC_TIMEOUT)set(FUNC_TIMEOUT 60)endif()if(NOT DEFINED FUNC_TAGS)set(FUNC_TAGS "default")endif()
endfunction()
3.处理互斥参数
function(exclusive_options)set(options "OPTION_A;OPTION_B")set(oneValueArgs "")set(multiValueArgs "")cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})# 检查互斥选项if(FUNC_OPTION_A AND FUNC_OPTION_B)message(FATAL_ERROR "OPTION_A and OPTION_B are mutually exclusive")endif()
endfunction()
4.实战示例:自定义可执行文件函数
以 “封装 add_executable
,支持自定义输出目录、调试开关、依赖库” 为例,演示 cmake_parse_arguments
的完整用法(贴合之前的 prjXiAn
项目场景)。
1.定义自定义函数 add_my_executable
# 自定义函数:封装 add_executable,支持灵活参数
function(add_my_executable TARGET_NAME)# 步骤1:用 cmake_parse_arguments 解析参数cmake_parse_arguments(MY_EXE # PREFIX:解析后变量的前缀(如 MY_EXE_WITH_DEBUG)"WITH_DEBUG" # OPTIONS:开关参数(是否启用调试模式)"OUTPUT_DIR" # ONE_VALUE_ARGS:单值参数(输出目录)"SOURCES;DEPENDS"# MULTI_VALUE_ARGS:多值参数(源文件、依赖库)${ARGN} # 待解析的可变参数(函数调用时传入的额外参数))# 步骤2:处理解析后的参数(生成可执行文件)# 2.1 检查必填参数:SOURCES 必须存在if(NOT MY_EXE_SOURCES)message(FATAL_ERROR "add_my_executable 必须指定 SOURCES 参数(如 SOURCES main.cpp)")endif()# 2.2 创建可执行目标(核心调用 add_executable)add_executable(${TARGET_NAME} ${MY_EXE_SOURCES})# 2.3 处理输出目录(单值参数:若指定 OUTPUT_DIR,设置目标输出路径)if(MY_EXE_OUTPUT_DIR)# 用 cmake_path 处理跨平台路径(Windows 自动转 \)cmake_path(JOIN "${CMAKE_CURRENT_BINARY_DIR}" "${MY_EXE_OUTPUT_DIR}" OUTPUT_PATH)set_target_properties(${TARGET_NAME} PROPERTIESRUNTIME_OUTPUT_DIRECTORY "${OUTPUT_PATH}" # 可执行文件输出目录)endif()# 2.4 处理调试开关(开关参数:若 WITH_DEBUG 为 TRUE,添加 DEBUG 宏)if(MY_EXE_WITH_DEBUG)target_compile_definitions(${TARGET_NAME} PRIVATE DEBUG)message(STATUS "[${TARGET_NAME}] 已启用调试模式(添加 DEBUG 宏)")endif()# 2.5 处理依赖库(多值参数:若指定 DEPENDS,链接依赖库)if(MY_EXE_DEPENDS)target_link_libraries(${TARGET_NAME} PRIVATE ${MY_EXE_DEPENDS})message(STATUS "[${TARGET_NAME}] 依赖库:${MY_EXE_DEPENDS}")endif()# 步骤3:(可选)打印未解析的参数(排查错误)if(MY_EXE_UNPARSED_ARGUMENTS)message(WARNING "[${TARGET_NAME}] 存在未解析的参数:${MY_EXE_UNPARSED_ARGUMENTS}")endif()
endfunction()
2.调用自定义函数 add_my_executable
在 CMakeLists.txt
中调用该函数,传递不同类型的参数(开关、单值、多值):
# 调用示例1:基础用法(指定源文件、输出目录)
add_my_executable(prjXiAnSOURCES src/main.cpp src/deviceManage.cpp UI/QComboBoxEx.cppOUTPUT_DIR "bin/Debug" # 单值参数:输出目录(Windows 下自动转为 bin\Debug)
)# 调用示例2:完整用法(含调试开关、依赖库)
add_my_executable(prjXiAn_TestWITH_DEBUG # 开关参数:启用调试模式SOURCES test/test_main.cpp test/test_device.cppOUTPUT_DIR "bin/Test"DEPENDS Qt5::Widgets ws2_32.lib # 多值参数:依赖库(Qt Widgets + Windows 系统库)
)
3.解析结果与效果
- 开关参数
WITH_DEBUG
:调用时指定WITH_DEBUG
→MY_EXE_WITH_DEBUG = TRUE
→ 自动添加DEBUG
宏。 - 单值参数
OUTPUT_DIR
:传入OUTPUT_DIR "bin/Debug"
→MY_EXE_OUTPUT_DIR = "bin/Debug"
→ 用cmake_path
转为跨平台路径(Windows 下为build\bin\Debug
)。 - 多值参数
SOURCES
/DEPENDS
:SOURCES
传入多个.cpp
文件 →MY_EXE_SOURCES
是包含这些文件的列表;DEPENDS
传入多个库 →MY_EXE_DEPENDS
是库列表,通过target_link_libraries
链接。 - 未解析参数:若误传
UNKNOWN_ARG "value"
→MY_EXE_UNPARSED_ARGUMENTS = "UNKNOWN_ARG;value"
→ 打印警告,帮助排查错误。
5.实战示例:nuttx_add_subdirectory
5.1.nuttx_add_subdirectory简介
nuttx_add_subdirectory
并非 CMake 标准命令,而是 NuttX RTOS(嵌入式实时操作系统) 项目中自定义的 CMake 函数,专门用于适配 NuttX 的模块化构建体系。其核心作用是:在 NuttX 构建流程中添加子目录(如驱动、应用、内核组件),并自动处理 NuttX 特有的配置依赖(Kconfig)、编译规则(嵌入式交叉编译)和模块间依赖,确保子目录模块与 NuttX 系统无缝集成。
nuttx_add_subdirectory
的具体实现位于 NuttX 源码的 nuttx/cmake/NuttXFunctions.cmake
文件中,可通过查看该文件了解细节(如参数解析逻辑、配置依赖判断代码)。例如,核心逻辑片段(简化):
# nuttx/cmake/NuttXFunctions.cmakefunction(nuttx_add_subdirectory subdir_path)# 解析参数(MODULE、DEPENDS、CONFIG)cmake_parse_arguments(ARG"""MODULE;CONFIG""DEPENDS"${ARGN})# 检查 CONFIG 宏:未定义则跳过子目录if(ARG_CONFIG AND NOT DEFINED ${ARG_CONFIG})message(STATUS "Skip subdirectory ${subdir_path} (${ARG_CONFIG} not enabled)")return()endif()# 处理依赖模块:确保依赖先编译if(ARG_DEPENDS)foreach(dep ${ARG_DEPENDS})if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dep}/CMakeLists.txt)message(FATAL_ERROR "Dependency ${dep} not found for ${subdir_path}")endif()nuttx_add_subdirectory(${dep}) # 递归集成依赖模块endforeach()endif()# 执行标准 add_subdirectory(核心:加载子目录 CMakeLists.txt)add_subdirectory(${subdir_path})# 打印日志(含模块名称)set(module_name ${ARG_MODULE})if(NOT module_name)get_filename_component(module_name ${subdir_path} NAME)endif()message(STATUS "Added NuttX module: ${module_name} (path: ${subdir_path})")
endfunction()
nuttx_add_subdirectory
的语法由 NuttX 定义(不同版本略有差异,以 NuttX 11.0+ 为例),核心参数格式如下:
nuttx_add_subdirectory(<subdir_path> # 1. 必选:子目录路径(相对 NuttX 根目录或当前 CMakeLists.txt 路径)[MODULE <module_name>] # 2. 可选:模块名称(用于日志和依赖标识,默认用子目录名)[DEPENDS <deps...>] # 3. 可选:依赖的其他模块(如 "drivers/bus" "kernel/sched")[CONFIG <config_key>] # 4. 可选:Kconfig 开关宏(如 "CONFIG_MY_DRIVER",未定义则不编译该子目录)
)
参数详解:
参数 | 作用说明 | 示例 |
---|---|---|
<subdir_path> | 子目录的路径(如 drivers/uart 、apps/my_app ),需包含 CMakeLists.txt 。 | nuttx_add_subdirectory(drivers/uart) |
MODULE <name> | 给模块命名(便于日志区分),默认用子目录名(如 uart )。 | MODULE uart_driver |
DEPENDS <deps> | 该模块依赖的其他 NuttX 模块(确保依赖模块先编译)。 | DEPENDS drivers/bus kernel/irq |
CONFIG <key> | 控制模块是否启用的 Kconfig 宏(如 CONFIG_UART ),若 /.config 中该宏未定义(或为 n ),则跳过子目录。 | CONFIG CONFIG_UART |
5.2.背景:NuttX 的模块化构建体系
NuttX 采用 “配置 - 构建” 分离的模块化设计,核心特点包括:
1.Kconfig 配置系统:通过 Kconfig
文件定义模块的可配置选项(如 “是否启用 UART 驱动”“是否支持 FAT 文件系统”),用户通过 menuconfig
选择后生成 /.config
配置文件。
2.CMake 构建系统:基于 CMake 实现跨平台交叉编译,自动根据 /.config
生成的宏(如 CONFIG_UART=y
)决定是否编译某模块、链接哪些依赖。
3.子目录模块化:功能模块(驱动、应用、内核组件)按目录拆分(如 drivers/uart
、apps/system
、kernel/sched
),每个模块需通过特定函数集成到整体构建中 ——nuttx_add_subdirectory
就是为此设计的 “模块集成入口”。
5.3.nuttx_add_subdirectory
的核心作用
相比 CMake 标准命令 add_subdirectory
(仅执行子目录的 CMakeLists.txt
),nuttx_add_subdirectory
额外处理了 NuttX 特有的构建逻辑,核心功能包括:
1.Kconfig 配置依赖检查:根据 /.config
中的宏(如 CONFIG_MY_MODULE
)判断是否启用该子目录模块,未启用则跳过编译。
2.自动传递 NuttX 编译环境:向子目录传递 NuttX 的交叉编译工具链、内核头文件路径、硬件平台宏(如 CONFIG_ARCH_ARM
、CONFIG_BOARD_STM32F4DISCO
)。
3.模块依赖管理:自动处理子目录模块对 NuttX 内核 API(如线程、信号量)或其他模块(如驱动依赖总线框架)的依赖,确保编译顺序和链接正确性。
4.嵌入式编译规则适配:默认添加 NuttX 嵌入式编译选项(如 -ffunction-sections
、-fdata-sections
减小固件体积),兼容 NuttX 的链接脚本和内存布局。
5.4.实战示例:使用 nuttx_add_subdirectory
集成模块
以 “在 NuttX 中添加自定义 UART 驱动子目录” 和 “添加应用程序子目录” 为例,演示具体用法。
示例 1:添加自定义驱动子目录(drivers/my_uart
)
1.目录结构准备
首先在 NuttX 源码中创建自定义驱动目录,需包含 3 个核心文件:
nuttx/
└── drivers/└── my_uart/ # 自定义 UART 驱动子目录├── CMakeLists.txt # 驱动的构建配置├── Kconfig # 驱动的配置选项(供 menuconfig 使用)├── my_uart.c # 驱动源码└── my_uart.h # 驱动头文件
2.父目录(drivers/CMakeLists.txt
)调用 nuttx_add_subdirectory
在 NuttX 驱动根目录的 CMakeLists.txt
中,通过 nuttx_add_subdirectory
集成自定义驱动:
# nuttx/drivers/CMakeLists.txt# 集成标准 UART 驱动(NuttX 原生)
nuttx_add_subdirectory(drivers/uart MODULE uart CONFIG CONFIG_UART)# 集成自定义 UART 驱动(新增)
nuttx_add_subdirectory(my_uart # 子目录路径(相对当前 drivers/ 目录)MODULE my_uart_driver # 模块名称DEPENDS drivers/bus kernel/irq # 依赖:总线驱动、中断模块CONFIG CONFIG_MY_UART # Kconfig 开关宏(需在 my_uart/Kconfig 中定义)
)
3.子目录(my_uart/CMakeLists.txt
)编写
子目录的 CMakeLists.txt
需遵循 NuttX 驱动的构建规则(使用 NuttX 提供的编译函数):
# nuttx/drivers/my_uart/CMakeLists.txt# 1. 收集驱动源码
set(SRCS my_uart.c)# 2. 生成驱动库(NuttX 驱动通常编译为静态库,再链接到内核)
nuttx_add_library(my_uart_lib STATIC ${SRCS})# 3. 链接依赖(如内核 API、总线驱动)
target_link_libraries(my_uart_lib PRIVATE nuttx_kernel nuttx_bus)# 4. 添加内核头文件路径(NuttX 内核头文件在 include/ 目录)
target_include_directories(my_uart_lib PRIVATE ${CMAKE_SOURCE_DIR}/include)# 5. 将驱动库添加到 NuttX 全局链接列表(确保最终链接到固件)
list(APPEND NUTTX_DRIVERS_LIBS my_uart_lib)
set(NUTTX_DRIVERS_LIBS ${NUTTX_DRIVERS_LIBS} PARENT_SCOPE)
4.Kconfig 配置(my_uart/Kconfig
)
定义驱动的开关选项(供 menuconfig
配置),确保 CONFIG_MY_UART
宏能被 nuttx_add_subdirectory
识别:
# nuttx/drivers/my_uart/Kconfigconfig MY_UARTbool "Enable Custom UART Driver"default ndepends on ARCH_HAS_UART # 依赖硬件平台支持 UARThelpEnable support for custom UART driver (for STM32F4xx).
示例 2:添加应用程序子目录(apps/my_app
)
NuttX 的 apps/
目录存放用户应用,集成方式与驱动类似:
# nuttx/apps/CMakeLists.txt# 集成自定义应用(my_app)
nuttx_add_subdirectory(my_app # 应用子目录路径MODULE my_application # 模块名称DEPENDS system/stdio # 依赖:标准输入输出模块CONFIG CONFIG_MY_APP # Kconfig 开关宏
)
子目录 apps/my_app/CMakeLists.txt
编写(应用通常编译为可执行文件或链接到 apps
库):
# nuttx/apps/my_app/CMakeLists.txtset(SRCS my_app_main.c)# 编译应用为可执行文件(NuttX 应用支持静态链接到内核或动态加载)
nuttx_add_executable(my_app ${SRCS})# 链接依赖(如标准库、NuttX 应用框架)
target_link_libraries(my_app PRIVATE nuttx_apps nuttx_stdio)# 添加头文件路径
target_include_directories(my_app PRIVATE ${CMAKE_SOURCE_DIR}/include)
5.5.关键区别:nuttx_add_subdirectory
vs 标准 add_subdirectory
特性 | nuttx_add_subdirectory (NuttX 自定义) | add_subdirectory (CMake 标准) |
---|---|---|
配置依赖处理 | 支持 Kconfig 开关宏(CONFIG <key> ),未启用则跳过子目录 | 无配置依赖,强制执行子目录 CMakeLists.txt |
编译环境传递 | 自动传递 NuttX 交叉编译工具链、内核宏、硬件平台选项 | 仅继承父目录的 CMake 环境,需手动配置嵌入式编译选项 |
模块依赖管理 | 支持 DEPENDS 参数,确保依赖模块先编译 | 无依赖管理,需手动通过 add_dependencies 控制编译顺序 |
嵌入式规则适配 | 默认添加 NuttX 嵌入式编译选项(如 -ffunction-sections )、链接脚本支持 | 无嵌入式适配,需手动编写交叉编译规则 |
适用场景 | NuttX 内核模块、驱动、应用的集成 | 通用 CMake 项目的子目录管理(非嵌入式 / 非 NuttX 场景) |
6.注意事项
1.PREFIX
必须唯一,避免变量污染
不同函数的 PREFIX
不能重复(如两个函数都用 PARSE
作为前缀,会导致变量覆盖)。推荐用函数名相关的前缀(如 add_my_executable
用 MY_EXE
)。
2.参数顺序不强制,但推荐 “位置参数在前,关键字参数在后”
CMake 允许关键字参数穿插在位置参数中(如 add_my_executable(prjXiAn SOURCES main.cpp OUTPUT_DIR bin)
),但为了可读性,建议按 “位置参数 → 开关参数 → 单值参数 → 多值参数” 的顺序传递。
3.单值参数必须跟值,多值参数可跟多个值
- 单值参数(如
OUTPUT_DIR
)若未跟值,CMake 会报错:cmake_parse_arguments called with incorrect number of arguments for ONE_VALUE_ARGS
。 - 多值参数(如
DEPENDS
)若未跟值,MY_EXE_DEPENDS
会是空列表(不会报错)。
4.大小写不敏感,但推荐统一风格
CMake 对关键字参数不区分大小写(如 with_debug
和 WITH_DEBUG
等效),但为了代码规范,建议统一使用大写(如 WITH_DEBUG
、OUTPUT_DIR
)。
5.捕获未解析参数,排查错误
cmake_parse_arguments
会自动生成 PREFIX_UNPARSED_ARGUMENTS
变量,存储未匹配到任何规则的参数。建议在函数中检查该变量并打印警告(如示例中的 if(MY_EXE_UNPARSED_ARGUMENTS)
),避免因误传参数导致隐藏问题。
6.CMake 版本兼容性
cmake_parse_arguments
从 CMake 3.5 版本开始支持,若项目需兼容更低版本(如 3.4 及以下),需改用手动解析 ARGN
的方式(通过循环判断参数类型,代码繁琐,不推荐)。
7.总结
cmake_parse_arguments
是编写复杂自定义函数 / 宏的必备工具,尤其适合以下场景:
1.封装 add_executable
/add_library
,支持自定义输出目录、编译开关、依赖库(如示例中的 add_my_executable
);
2.处理第三方库的配置函数(如 find_package
后的自定义初始化函数,支持多参数配置);
3.编写通用工具函数(如文件复制、批量编译,需要灵活的参数输入)。
通过它,你可以摆脱手动循环 ARGN
解析参数的繁琐工作,让自定义函数的参数逻辑更清晰、更易维护,同时兼顾跨平台兼容性(如配合 cmake_path
处理路径)。
相关链接
- CMake 官网 https://cmake.org/
- CMake 官方文档:https://cmake.org/cmake/help/latest/guide/tutorial/index.html
- CMake 源码:https://github.com/Kitware/CMake
- CMake 源码:https://gitlab.kitware.com/cmake/cmakeku
- 中文版基础介绍: https://www.hahack.com/codes/cmake/
- wiki: https://gitlab.kitware.com/cmake/community/-/wikis/Home
- Modern CMake 简体中文版: https://modern-cmake-cn.github.io/Modern-CMake-zh_CN/