CMake cmake_parse_arguments
这段CMake代码定义了一个自定义函数 nuttx_add_subdirectory
,用于自动添加当前目录下的所有子目录,但可以排除指定的目录。
代码功能解析
1. 函数定义和参数解析
function(nuttx_add_subdirectory)set(options)set(oneValueArgs)set(multiValueArgs EXCLUDE)cmake_parse_arguments(NUTTX "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
-
参数解析设置:
options
: 空,表示没有开关选项oneValueArgs
: 空,表示没有单值参数multiValueArgs EXCLUDE
: 定义了一个多值参数EXCLUDE
,用于指定要排除的目录列表
-
cmake_parse_arguments
:- 解析传递给函数的参数
- 解析后的变量会加上
NUTTX_
前缀 - 例如:如果调用时指定
EXCLUDE dir1 dir2
,则会产生NUTTX_EXCLUDE
变量包含dir1;dir2
2. 查找子目录
file(GLOB subdirLIST_DIRECTORIES falseRELATIVE ${CMAKE_CURRENT_LIST_DIR}${CMAKE_CURRENT_LIST_DIR}/*/CMakeLists.txt)
- 使用
file(GLOB)
查找当前目录下所有包含CMakeLists.txt
的子目录 LIST_DIRECTORIES false
: 只返回文件,不返回目录RELATIVE
: 返回相对路径- 结果示例:
["subdir1/CMakeLists.txt", "subdir2/CMakeLists.txt"]
3. 处理每个子目录
foreach(dir ${subdir})get_filename_component(dir ${dir} DIRECTORY)list(FIND NUTTX_EXCLUDE ${dir} _skip_index)if(_skip_index GREATER -1)message(STATUS "nuttx_add_subdirectory: Skipping ${dir}")continue()endif()add_subdirectory(${dir})
endforeach()
- 遍历找到的每个
CMakeLists.txt
文件 get_filename_component(dir ${dir} DIRECTORY)
: 提取目录名(去掉文件名)- 检查目录是否在排除列表中,如果是则跳过
- 否则使用
add_subdirectory()
添加该目录
使用示例
# 添加所有子目录
nuttx_add_subdirectory()# 排除特定目录
nuttx_add_subdirectory(EXCLUDE tests examples)# 排除多个目录
nuttx_add_subdirectory(EXCLUDE dir1 dir2 dir3)
参数解析用法总结
cmake_parse_arguments
的基本语法:
cmake_parse_arguments(<prefix> <options> <oneValueArgs> <multiValueArgs> <args>)
<prefix>
: 解析后变量的前缀<options>
: 布尔选项列表(无值参数)<oneValueArgs>
: 单值参数列表<multiValueArgs>
: 多值参数列表<args>
: 实际传入的参数(通常用${ARGN}
)
这个函数提供了一种结构化的方式来处理CMake函数和宏的参数,比手动解析 ${ARGN}
更加清晰和健壮。
我来详细讲解 cmake_parse_arguments
的用法,并举几个具体例子。
基本语法
cmake_parse_arguments(<prefix> <options> <oneValueArgs> <multiValueArgs> <args>)
参数类型详解
1. 选项参数 (options)
- 布尔值,出现即为
TRUE
,不出现为FALSE
- 不需要额外值
2. 单值参数 (oneValueArgs)
- 每个参数后面必须跟一个值
- 如果重复出现,后面的会覆盖前面的
3. 多值参数 (multiValueArgs)
- 每个参数后面可以跟多个值
- 会收集所有值到一个列表中
详细示例
示例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(configure_target)set(options SHARED STATIC EXCLUDE_FROM_ALL)set(oneValueArgs TARGET VERSION)set(multiValueArgs SOURCES LIBRARIES DEFINITIONS)cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})# 检查必需参数if(NOT ARG_TARGET)message(FATAL_ERROR "TARGET 参数是必需的")endif()message("配置目标: ${ARG_TARGET}")message("类型: ${ARG_SHARED} (SHARED), ${ARG_STATIC} (STATIC)")message("版本: ${ARG_VERSION}")message("源文件: ${ARG_SOURCES}")message("库: ${ARG_LIBRARIES}")message("定义: ${ARG_DEFINITIONS}")message("排除从全部: ${ARG_EXCLUDE_FROM_ALL}")
endfunction()# 各种调用方式
configure_target(TARGET "my_lib"STATICVERSION "1.0.0"SOURCES src1.cpp src2.cpp src3.cppLIBRARIES pthread dlDEFINITIONS USE_FEATURE_A USE_FEATURE_B
)configure_target(TARGET "my_shared_lib"SHAREDEXCLUDE_FROM_ALLSOURCES shared.cpp
)
示例3:处理默认值和验证
function(create_executable)set(options DEBUG RELEASE)set(oneValueArgs NAME COMPILER)set(multiValueArgs FILES LINK_LIBS)cmake_parse_arguments(EXE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})# 设置默认值if(NOT EXE_COMPILER)set(EXE_COMPILER "gcc")endif()# 互斥选项检查if(EXE_DEBUG AND EXE_RELEASE)message(FATAL_ERROR "DEBUG 和 RELEASE 选项不能同时使用")endif()# 必需参数检查if(NOT EXE_NAME OR NOT EXE_FILES)message(FATAL_ERROR "NAME 和 FILES 参数是必需的")endif()# 构建类型if(EXE_DEBUG)set(BUILD_TYPE "Debug")elseif(EXE_RELEASE)set(BUILD_TYPE "Release")else()set(BUILD_TYPE "Default")endif()message("创建可执行文件: ${EXE_NAME}")message("编译器: ${EXE_COMPILER}")message("构建类型: ${BUILD_TYPE}")message("文件: ${EXE_FILES}")message("链接库: ${EXE_LINK_LIBS}")
endfunction()# 调用
create_executable(NAME "my_app"FILES main.c helper.cLINK_LIBS m pthreadDEBUG
)create_executable(NAME "another_app"FILES app.cppCOMPILER "clang++"RELEASE
)
示例4:与自定义宏结合使用
macro(my_advanced_function)set(options OPTION_A OPTION_B OPTION_C)set(oneValueArgs SINGLE_VALUE)set(multiValueArgs MULTI_VALUES)cmake_parse_arguments(ADV "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})# 在宏中,变量会影响到调用者的作用域set(PARSED_OPTION_A ${ADV_OPTION_A})set(PARSED_SINGLE_VALUE ${ADV_SINGLE_VALUE})set(PARSED_MULTI_VALUES ${ADV_MULTI_VALUES})
endmacro()# 使用
my_advanced_function(OPTION_AOPTION_CSINGLE_VALUE "hello"MULTI_VALUES item1 item2 item3
)message("OPTION_A: ${PARSED_OPTION_A}") # 输出: OPTION_A: TRUE
message("SINGLE_VALUE: ${PARSED_SINGLE_VALUE}") # 输出: SINGLE_VALUE: hello
message("MULTI_VALUES: ${PARSED_MULTI_VALUES}") # 输出: MULTI_VALUES: item1;item2;item3
重要注意事项
-
变量前缀:所有解析后的变量都会加上前缀,如
MY_VERBOSE
、MY_SOURCES
-
未解析参数:不在定义范围内的参数会保存在
<prefix>_UNPARSED_ARGUMENTS
中 -
缺失值的参数:期望有值但没有提供的参数会保存在
<prefix>_KEYWORDS_MISSING_VALUES
中 -
作用域:在函数中使用时,变量只在函数内有效;在宏中使用时,会影响调用者的作用域
-
参数顺序:调用时参数的顺序可以是任意的
这种参数解析方式让CMake函数和宏的使用更加清晰和灵活,类似于其他编程语言中的关键字参数。
三个列表的固定规则
是的,这三个列表的标识是固定死的,你必须使用这三个特定的变量名:
1. options
- 开关选项列表
用途:存放不需要值的布尔参数(标志)
规则:参数出现即为 TRUE
,不出现为 FALSE
set(options VERBOSE FORCE DEBUG) # 固定使用变量名 options
2. oneValueArgs
- 单值参数列表
用途:存放每个后面必须跟一个值的参数
规则:参数后面必须跟且只能跟一个值
set(oneValueArgs NAME OUTPUT TYPE) # 固定使用变量名 oneValueArgs
3. multiValueArgs
- 多值参数列表
用途:存放每个后面可以跟多个值的参数
规则:参数后面可以跟任意数量的值,直到遇到下一个已知参数
set(multiValueArgs SOURCES LIBRARIES INCLUDES) # 固定使用变量名 multiValueArgs
完整语法结构
function(my_function)# 1. 必须使用这三个固定变量名定义参数类型set(options <开关参数列表>)set(oneValueArgs <单值参数列表>) set(multiValueArgs <多值参数列表>)# 2. 解析参数(参数顺序也是固定的)cmake_parse_arguments(PREFIX # 自定义前缀"${options}" # 必须引用 options 变量"${oneValueArgs}" # 必须引用 oneValueArgs 变量 "${multiValueArgs}" # 必须引用 multiValueArgs 变量${ARGN} # 必须传入实际参数)
endfunction()
实际示例
function(build_target)# 固定规则:必须用这三个变量名set(options VERBOSE STATIC SHARED)set(oneValueArgs TARGET OUTPUT) set(multiValueArgs SOURCES INCLUDES LIBS)# 固定规则:参数顺序必须是这样cmake_parse_arguments(ARG"${options}""${oneValueArgs}" "${multiValueArgs}"${ARGN})# 使用解析结果if(ARG_VERBOSE)message("详细模式开启")endif()message("目标: ${ARG_TARGET}")message("输出: ${ARG_OUTPUT}")message("源文件: ${ARG_SOURCES}")message("包含路径: ${ARG_INCLUDES}")message("库文件: ${ARG_LIBS}")
endfunction()# 调用示例
build_target(VERBOSETARGET "my_app"SOURCES main.cpp util.cpp helper.cppINCLUDES /usr/include /opt/local/includeLIBS pthread m dlOUTPUT "build/bin"
)
为什么是固定的?
这是 CMake 语言的语法规定,就像其他编程语言的关键字一样:
if()...endif()
是固定的foreach()...endforeach()
是固定的set(options ...)
中的options
也是固定的
你不能随意改成其他名字,比如:
# 错误!CMake 无法识别
set(flags VERBOSE) # 应该用 options
set(singleArgs NAME) # 应该用 oneValueArgs
set(multiArgs SOURCES) # 应该用 multiValueArgs
cmake_parse_arguments(ARG "${flags}" "${singleArgs}" "${multiArgs}" ${ARGN})
总结规则
- 变量名固定:必须使用
options
、oneValueArgs
、multiValueArgs
- 参数顺序固定:
cmake_parse_arguments(PREFIX "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
- 内容自定义:列表中的具体参数名可以按需定义
- 前缀自定义:
PREFIX
部分可以自定义
这样设计是为了保持 CMake 语法的统一性和可预测性。
这个用法比较特殊,让我来详细解析:
cmake_parse_arguments(SOURCE "" "" "CFLAGS;DEPS" ${ARGN})
参数解析
根据 cmake_parse_arguments
的语法:
cmake_parse_arguments(<前缀> <options列表> <单值参数列表> <多值参数列表> ${ARGN})
在这个例子中:
SOURCE
- 前缀""
- 空的options
列表(没有开关参数)""
- 空的oneValueArgs
列表(没有单值参数)"CFLAGS;DEPS"
- 多值参数列表(只有CFLAGS
和DEPS
两个多值参数)
这意味着
- 没有开关参数:不能使用像
VERBOSE
、FORCE
这样的标志 - 没有单值参数:不能使用像
NAME "value"
这样后面跟一个值的参数 - 只有两个多值参数:
CFLAGS
- 可以接受多个编译标志DEPS
- 可以接受多个依赖项
使用示例
function(my_function)cmake_parse_arguments(SOURCE "" "" "CFLAGS;DEPS" ${ARGN})message("CFLAGS: ${SOURCE_CFLAGS}")message("DEPS: ${SOURCE_DEPS}")
endfunction()# 调用方式
my_function(CFLAGS -O2 -Wall -WextraDEPS lib1 lib2 lib3
)my_function(DEPS dependency1CFLAGS -g -DDEBUG
)
输出结果:
CFLAGS: -O2;-Wall;-Wextra
DEPS: lib1;lib2;lib3
为什么这样设计?
这种用法适用于只需要多值参数的场景,比如:
# 编译配置函数
function(setup_compilation)cmake_parse_arguments(COMP "" "" "FLAGS;DEFINES;INCLUDES" ${ARGN})if(COMP_FLAGS)add_compile_options(${COMP_FLAGS})endif()if(COMP_DEFINES)add_compile_definitions(${COMP_DEFINES})endif()if(COMP_INCLUDES)include_directories(${COMP_INCLUDES})endif()
endfunction()# 使用
setup_compilation(FLAGS -O2 -Wall -std=c++11DEFINES USE_FEATURE_A DEBUG_MODEINCLUDES /usr/local/include /opt/include
)
总结
这种用法是 cmake_parse_arguments
的一个简化形式,当你只需要多值参数而不需要开关选项和单值参数时,可以把前两个参数设为空字符串。
语法规则:
- 第一个参数:前缀(必需)
- 第二个参数:options 列表(可以为空)
- 第三个参数:单值参数列表(可以为空)
- 第四个参数:多值参数列表(至少有一个)
- 第五个参数:
${ARGN}
(必需)