1. 函数
函数: function
, endfunction
,支持语法如下:
# function
function(<name> [<arg1> ...])
# endfunction
endfunction([<name>])
概要
function(<name> [<arg1> ...])
<commands>
endfunction()
定义个名为 <name>
的函数,该函数接收名为 <arg1>
… 的参数,<commands>
表示函数定义的功能,在调用函数之前不会被执行。
一个函数打开一个新的作用域,请参阅 set(var PARENT_SCOPE)
。
https://cmake.org/cmake/help/v3.19/command/set.html
有关函数内部策略的行为,请参阅 cmake_policy()
。
https://cmake.org/cmake/help/v3.19/command/cmake_policy.html
调用(Invocation)
函数调用的函数名不区分大小写。
案例,定义一个名为 “foo” 的函数,根据不同大小写的函数名调用。
# 函数定义
function(foo)
<commands>
endfunction()
# 函数调用
foo()
Foo()
FOO()
# cmake_language 函数调用方式
cmake_language(CALL foo)
建议函数名全部使用小写。
cmake_language(CALL ...)
既可以调用函数,也可以调用宏。该命令不会引入新的变量或策略范围。
参数(Arguments)
1. 用 形参 引用参数
当函数被调用时,首先通过实参替换形参 ${arg1}, …
,然后作为普通命令调用。
案例,定义一个对变量求和的函数。
function(foo x1 x2 x3)
math(EXPR value "${x1} + ${x2} + ${x3}")
message("x1:${x1}, x2:${x2}, x3:${x3}, sum:${value}")
endfunction()
foo(10 20 30)
注意:似乎是通过 set(x1 10)
命令将实参值 10
设置到形参变量 x1
中,然后在函数内通过 ${x1}
形式引用参数值。
2. 用 ARGC
变量 和 ARGVn
变量 引用参数
除了引用形参之外,你还可以引用 ARGC
变量 和 ARGVn
变量 来引用参数,ARGC
表示参数数量,以及 ARGV0, ARGV1, ARGV2, …
将具有传入参数的值。这有助于创建带有可选参数的函数。
案例,定义一个函数,输出参数数量和传入的每个参数值
function(foo x1 x2 x3)
message("参数数量:${ARGC}, ARGV0:${ARGV0}, ARGV1:${ARGV1}, ARGV2:${ARGV2}")
endfunction()
foo(10 20 30)
注意: 用 ARGVn
变量引用实参值,与定义的形参 x1, x2, x3
无关,只与传入的实参有关,就算函数定义是 function(main)
,该函数也照样可以使用,或者说这样还更好。
ARGV
变量保存函数的所有参数的列表。
案例,用 ARGV
变量 引用所有参数
function(foo)
message("ARGV:${ARGV}")
endfunction()
foo(10 20 30 10 10)
3. 用 ARGN
变量引用预期之外的参数
此外,ARGN
变量保存超过形参列表之后的参数。如果实参数量大于形参数量,用 ARGN
变量引用预期之外的参数。
案例,用 ARGN
变量引用预期之外的参数
function(foo x1 x2 x3)
message("ARGN:${ARGN}")
endfunction()
foo(10 20 30 10 10)
4. 形参为变量
之前案例中形参都为值,那么变量取值的语法 ${VAR}
获取的是值。如果形参为变量的话,那么获取的值为变量,要想获取值的话使用 ${${VAR}}
。
案例,如果形参为变量的话,参数引用的就是变量
set(a1 10)
set(a2 20)
set(a3 30)
function(foo x1 x2 x3)
message("x1:${x1}, x2:${x2}, x3:${x3}")
endfunction()
foo(a1 a2 a3)
输出结果
x1:a1, x2:a2, x3:a3
2. 宏
宏: macro
, endmacro
,支持语法如下:
# macro
macro(<name> [<arg1> ...])
# endmacro
endmacro([<name>])
概要
macro(<name> [<arg1> ...])
<commands>
endmacro()
定义个名为 <name>
的宏,该宏接收名为 <arg1>
… 的参数,<commands>
表示宏定义的功能,在调用宏之前不会被执行。
有关宏内部策略的行为,请参阅 cmake_policy()
。
https://cmake.org/cmake/help/v3.19/command/cmake_policy.html
调用(Invocation)
宏调用名也不区分大小写。
与函数的案例一样,定义一个名为 “foo” 的宏,调用时也可以用 “foo”, “Foo”, “FOO”,也可以使用 cmake_language(CALL ...)
子命令调用宏。
宏也建议,宏名全部使用小写。
参数(Arguments)
调用宏时的参数定义和使用,也与函数一样。
- 用 形参 引用参数
- 用
ARGC
变量 和ARGVn
变量 引用参数,用ARGV
变量 引用所有参数 - 用
ARGN
变量引用预期之外的参数
3. 函数与宏的区别
macro()
与 function()
非常相似。尽管如此,还有一些重要的区别。
在函数中,ARGN
, ARGC
, ARGV
and ARGV0, ARGV1, …
是 CMake 意义上的变量。在宏中,它们不是,它们是字符串替换,就像 C 预处理器
对 宏
所做的一样。这会产生许多后果,如下面的 “参数警告” 部分所述。
宏和函数之间的另一个区别是控制流。函数的执行 通过将控制权从调用语句转移到函数体。宏的执行 就像将宏主题粘贴到调用语句的位置一样。这导致宏体中的 return()
不仅终止宏的执行;相反,控制权是从宏调用的范围内返回的。为了避免混淆,建议在宏中完全避免 return()
。
案例,在函数仅仅用 return
将控制权返回给函数的调用者
function(select option)
if(${option} EQUAL 1)
message("选择选项1")
return()
elseif(${option} EQUAL 2)
message("选择选项2")
return()
elseif(${option} EQUAL 3)
message("选择选项3")
return()
else()
message("未知选项")
endif()
message("继续执行")
endfunction()
select(1)
select(2)
select(0)
案例,如果函数返回返回值,并不是使用 return
,而是通过参数
function(foo x1 x2 x3 out_var)
math(EXPR value "${x1} + ${x2} + ${x3}")
message("x1:${x1}, x2:${x2}, x3:${x3}")
set(${out_var} ${value} PARENT_SCOPE)
endfunction()
foo(10 20 30 RET)
message("sum:${RET}")
注意,形参原本的作用域在函数内,只能在函数内使用,但是通过
set(... PARENT_SCOPE)
将变量的作用域设置为当前作用域之外,你可以使用父作用域,也就是作用于函数之外。
注意,如果在函数内部通过
PARENT_SCOPE
设置变量,该变量可以在函数之外使用。
例如,在函数内部设置变量 out_var
的值为 100
,在函数外部输出也为 100
。
function(foo)
set(out_var 100 PARENT_SCOPE)
endfunction()
foo()
message("out_var:${out_var}")
与函数不同,CMAKE_CURRENT_FUNCTION
, CMAKE_CURRENT_FUNCTION_LIST_DIR
, CMAKE_CURRENT_FUNCTION_LIST_FILE
, CMAKE_CURRENT_FUNCTION_LIST_LINE
变量,不是为宏设置的。
4. 参数警告(Argument Caveats)
宏与函数在调用时,参数处理方面的不同。
由于 ARGN
, ARGC
, ARGV
, ARGV0
等,不是变量,因此你无法使用,诸如以下语句:
if(ARGV1) # ARGV1 is not a variable
if(DEFINED ARGV2) # ARGV2 is not a variable
if(ARGC GREATER 2) # ARGC is not a variable
foreach(loop_var IN LISTS ARGN) # ARGN is not a variable
在第一种情况下,你可以使用 if(${ARGV1})
。
在第二种和第三种情况下,检查是否将可选变量传递给宏的正确方法是使用 if(${ARGC} GREATER 2)
。
在最后一种情况下,你可以使用 foreach(loop_var ${ARGN})
如果需要包含它们,可以使用
set(list_var "${ARGN}")
foreach(loop_var IN LISTS list_var)
注意:如果在调用宏的范围内有一个同名的变量,则使用未引用的名称,将使用现有变量而不是参数。例如:
macro(bar)
foreach(arg IN LISTS ARGN)
<commands>
endforeach()
endmacro()
function(foo)
bar(x y z)
endfunction()
foo(a b c)
将遍历 a;b;c
,而不是预期那样遍历 x;y;z
。原因是,bar
会在 foo
中展开,所以 ARGN 称为 foo
中的变量。
5. 用 cmake_parse_arguments
解析参数
我们一般用 cmake_parse_arguments
命令来解析函数或宏的参数。用 cmake_parse_arguments
命令,可以解析复杂的参数形式。
cmake_parse_arguments
有两种形式,语法如下:
cmake_parse_arguments(<prefix> <options> <one_value_keywords>
<multi_value_keywords> <args>...)
cmake_parse_arguments(PARSE_ARGV <N> <prefix> <options>
<one_value_keywords> <multi_value_keywords>)
我们以 cmake_parse_arguments
的第一种形式为例。
在设计时,有三种参数,
第一种参数是 <options>
表示可选关键词列表,如果传入参数包含此变量名,则为 TRUE
,反之为 FALSE
。
第二种参数是 <one_value_keywords>
表示单值关键词列表,每个关键词仅对应一个值。
第三种参数是 <multi_value_keywords>
表示多值关键词列表,每个关键词可对应多个值。
要解析的参数 <args>...
,我们一般传入为 ${ARGN}
即可,一般定义的函数或宏是无参的,除非第一个参数不是关键词,那么有多少非关键词变量,定义多少形参。
我们将参数 ${ARGN}
根据 <options>
, <one_value_keywords>
, <multi_value_keywords>
规则进行解析,解析出来的新变量名根据 <prefix>
前缀,按照 prefix_参数名
的形式。
可选参数,我们用 if 语句
来实现。
案例1,我们想定义一个类似 install
命令,用于安装目标到指定的目录。目前案例仅仅是分析传参,下一个案例是功能的实现框架。
要实现的命令形式,如下:
my_install(TARGETS <target>...
[LIBRARY|RUNTIME|OBJECTS]
[CONFIGURATIONS [Debug|Release|...]]
DESTINATION <dir>
[COMPONENT <component>]
[RENAME <name>]
[OPTIONAL]
)
我们知道 TARGETS
为多值关键词,LIBRARY
,RUNTIME
,OBJECTS
为可选关键词,CONFIGURATIONS
,DESTINATION
,COMPONENT
,RENAME
为单值关键词,OPTIONAL
为可选关键词。
代码如下:
macro(my_install)
set(options LIBRARY RUNTIME OBJECTS OPTIONAL)
set(oneValueArgs DESTINATION COMPONENT RENAME)
set(multiValueArgs TARGETS CONFIGURATIONS)
cmake_parse_arguments(MY_INSTALL "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 多值关键词
message("MY_INSTALL_TARGETS: ${MY_INSTALL_TARGETS}")
# 可选关键词
message("MY_INSTALL_LIBRARY: ${MY_INSTALL_LIBRARY}")
message("MY_INSTALL_RUNTIME: ${MY_INSTALL_RUNTIME}")
message("MY_INSTALL_OBJECTS: ${MY_INSTALL_OBJECTS}")
# 多值关键词
message("MY_INSTALL_CONFIGURATIONS: ${MY_INSTALL_CONFIGURATIONS}")
# 单值关键词
message("MY_INSTALL_DESTINATION: ${MY_INSTALL_DESTINATION}")
message("MY_INSTALL_COMPONENT: ${MY_INSTALL_COMPONENT}")
message("MY_INSTALL_RENAME: ${MY_INSTALL_RENAME}")
# 可选关键词
message("MY_INSTALL_OPTIONAL: ${MY_INSTALL_OPTIONAL}")
# 特殊变量
# 未解析的参数
message("MY_INSTALL_UNPARSED_ARGUMENTS: ${MY_INSTALL_UNPARSED_ARGUMENTS}")
# 关键字缺失值的参数
message("MY_INSTALL_KEYWORDS_MISSING_VALUES: ${MY_INSTALL_KEYWORDS_MISSING_VALUES}")
endmacro()
my_install()
被调用:
my_install(TARGETS foo bar LIBRARY CONFIGURATIONS DESTINATION bin OPTIONAL blub)
在 cmake_parse_arguments
调用之后,将设置以下变量
MY_INSTALL_TARGETS: foo;bar
MY_INSTALL_LIBRARY: TRUE
MY_INSTALL_RUNTIME: FALSE
MY_INSTALL_OBJECTS: FALSE
MY_INSTALL_CONFIGURATIONS:
MY_INSTALL_DESTINATION: bin
MY_INSTALL_COMPONENT:
MY_INSTALL_RENAME:
MY_INSTALL_OPTIONAL: TRUE
MY_INSTALL_UNPARSED_ARGUMENTS: blub
MY_INSTALL_KEYWORDS_MISSING_VALUES: CONFIGURATIONS
TARGETS
为多值关键词,传入值为 foo;bar
。
LIBRARY
,RUNTIME
,OBJECTS
为可选关键词,因为仅传入 LIBRARY
,LIBRARY
被设置为 TRUE
,其他都设置为 FALSE
。
CONFIGURATIONS
仅传入关键字,并未传入值,所以值也为空。
COMPONENT
,RENAME
为单值关键词,未被传入,所以值为空。
DESTINATION
为单值关键词,传入值为 bin
。
OPTIONAL
为可选关键词,被设置为 TRUE
。
有两种特殊的变量,
第一个 <prefix>_UNPARSED_ARGUMENTS
表示未解析的参数,表示传入的参数并不符合 cmake_parse_arguments
命令解析规则,所以有 blub
。
第二个 <prefix>_KEYWORDS_MISSING_VALUES
表示关键字缺失值的参数,表示单值关键词或多值关键词,仅传入关键字,并未传入值,所以有 CONFIGURATIONS
。
案例2,对案例1,增加功能的实现框架。
实现 [LIBRARY|RUNTIME|OBJECTS]
,可选值,或者三个选一个。
macro(my_install)
...
# 实现 [LIBRARY|RUNTIME|OBJECTS],可选值,或者三个选一个。
set(nBool 0)
if(MY_INSTALL_LIBRARY)
math(EXPR nBool "${nBool} + 1")
endif()
if(MY_INSTALL_RUNTIME)
math(EXPR nBool "${nBool} + 1")
endif()
if(MY_INSTALL_OBJECTS)
math(EXPR nBool "${nBool} + 1")
endif()
if(${nBool} EQUAL 1)
if(${MY_INSTALL_LIBRARY})
message("TODO LIBRARY...")
elseif(${MY_INSTALL_RUNTIME})
message("TODO RUNTIME...")
elseif(${MY_INSTALL_OBJECTS})
message("TODO OBJECTS...")
endif()
elseif(nBool EQUAL 0)
message("[LIBRARY|RUNTIME|OBJECTS] NOTHING...")
else()
message("参数错误: [LIBRARY|RUNTIME|OBJECTS]")
endif()
message("MY_INSTALL_LIBRARY: ${MY_INSTALL_LIBRARY}")
message("MY_INSTALL_RUNTIME: ${MY_INSTALL_RUNTIME}")
message("MY_INSTALL_OBJECTS: ${MY_INSTALL_OBJECTS}")
endmacro()
调用形式
my_install()
my_install(LIBRARY)
my_install(OBJECTS)
my_install(LIBRARY RUNTIME)
my_install(OBJECTS RUNTIME)
my_install(LIBRARY OBJECTS RUNTIME)
实现 [CONFIGURATIONS [Debug|Release|MinSizeRel|RelWithDebInfo]]
参数为多值关键词,但是值为某些固定选项。
macro(my_install)
...
# 实现 `[CONFIGURATIONS [Debug|Release|MinSizeRel|RelWithDebInfo]]` 参数为多值关键词,但是值为某些固定选项。
message("MY_INSTALL_CONFIGURATIONS: ${MY_INSTALL_CONFIGURATIONS}")
if(MY_INSTALL_CONFIGURATIONS)
foreach(loop_var IN LISTS MY_INSTALL_CONFIGURATIONS)
string(STRIP ${loop_var} loop_var)
string(TOUPPER ${loop_var} loop_var)
#message("loop_var: ${loop_var}")
if(${loop_var} STREQUAL "DEBUG")
message("TODO DEBUG...")
elseif(${loop_var} STREQUAL "RELEASE")
message("TODO RELEASE...")
elseif(${loop_var} STREQUAL "MINSIZEREL")
message("TODO MINSIZEREL...")
elseif(${loop_var} STREQUAL "RELWITHDEBINFO")
message("TODO RELWITHDEBINFO...")
else()
message("参数错误: [CONFIGURATIONS [Debug|Release|MinSizeRel|RelWithDebInfo]]")
endif()
endforeach()
endif()
endmacro()
调用形式
my_install()
my_install(CONFIGURATIONS Debug)
my_install(CONFIGURATIONS Release)
my_install(CONFIGURATIONS Debug Release)
my_install(CONFIGURATIONS Debug Release RelWithDebInfo)
my_install(CONFIGURATIONS Debug Release MinSizeRel RelWithDebInfo SizeRel)
我们有时会用 foreach 语句
来处理参数。
function(foo STATUS)
message("foo(STATUS <message>...)")
set(_message "STATUS:${STATUS}")
list(LENGTH ARGN _message_len)
if(${_message_len} EQUAL 1)
string(APPEND _message ", <message>:${message}")
else(${_message_len} GREATER 2)
set(i 0)
foreach(loop_var IN LISTS ARGN)
string(APPEND _message ", <message_${i}>:${loop_var}")
math(EXPR i "${i} + 1")
endforeach()
endif()
message("${_message}")
endfunction()
调用形式
foo(STATUS "Hello World")
foo(STATUS "Hello" " World" "!!!")
cmake_parse_arguments
的第二种形式
cmake_parse_arguments
的第二种形式,语法如下:
cmake_parse_arguments(PARSE_ARGV <N> <prefix> <options>
<one_value_keywords> <multi_value_keywords>)
PARSE_ARGV
仅用于函数。在这种情况下,解析的参数来自调用函数的 ARGVn
。解析从第 <N>
个参数开始。
定义一个类似 add_executable
命令,要实现的命令形式,如下:
my_add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...]
)
代码如下:
function(my_add_executable name)
set(options WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL)
set(oneValueArgs)
set(multiValueArgs)
cmake_parse_arguments(PARSE_ARGV 1 MY "${options}" "${oneValueArgs}" "${multiValueArgs}")
message("name: ${name}")
message("MY_WIN32: ${MY_WIN32}")
message("MY_MACOSX_BUNDLE: ${MY_MACOSX_BUNDLE}")
message("MY_EXCLUDE_FROM_ALL: ${MY_EXCLUDE_FROM_ALL}")
# 未解析的参数
message("MY_UNPARSED_ARGUMENTS: ${MY_UNPARSED_ARGUMENTS}")
# 关键字缺失值的参数
message("MY_KEYWORDS_MISSING_VALUES: ${MY_KEYWORDS_MISSING_VALUES}")
endfunction()
调用形式
my_add_executable(myprog WIN32 main1.c main2.c main3.c)
在 cmake_parse_arguments
调用之后,将设置以下变量
name: myprog
MY_WIN32: TRUE
MY_MACOSX_BUNDLE: FALSE
MY_EXCLUDE_FROM_ALL: FALSE
MY_UNPARSED_ARGUMENTS: main1.c;main2.c;main3.c
MY_KEYWORDS_MISSING_VALUES:
注意,函数第一个参数,可以通过 形参获取 或者通过 ARGV0
引用。最后的不定参数,可以通过特殊变量 MY_UNPARSED_ARGUMENTS
获取,并通过 foreach 语句
解析。
6. 通过特殊变量,查看函数执行情况
我们可以在函数定义中,使用特殊变量,用于查看函数执行的情况。CMAKE_CURRENT_FUNCTION
表示函数名,CMAKE_CURRENT_FUNCTION_LIST_DIR
表示函数所在 CMakeLists.txt
文件目录,CMAKE_CURRENT_FUNCTION_LIST_FILE
表示函数所在 CMakeLists.txt
文件路径CMAKE_CURRENT_FUNCTION_LIST_LINE
表示函数执行所在行数。
Project/src/CMakeLists.txt
cmake_minimum_required(VERSION 3.9.0)
project(proj)
add_subdirectory(bar)
function(foo)
message("CMAKE_CURRENT_FUNCTION ${CMAKE_CURRENT_FUNCTION}")
message("CMAKE_CURRENT_FUNCTION_LIST_DIR ${CMAKE_CURRENT_FUNCTION_LIST_DIR}")
message("CMAKE_CURRENT_FUNCTION_LIST_FILE ${CMAKE_CURRENT_FUNCTION_LIST_FILE}")
message("CMAKE_CURRENT_FUNCTION_LIST_LINE ${CMAKE_CURRENT_FUNCTION_LIST_LINE}")
endfunction()
foo()
bar()
Project/src/bar/CMakeLists.txt
function(bar)
message("CMAKE_CURRENT_FUNCTION ${CMAKE_CURRENT_FUNCTION}")
message("CMAKE_CURRENT_FUNCTION_LIST_DIR ${CMAKE_CURRENT_FUNCTION_LIST_DIR}")
message("CMAKE_CURRENT_FUNCTION_LIST_FILE ${CMAKE_CURRENT_FUNCTION_LIST_FILE}")
message("CMAKE_CURRENT_FUNCTION_LIST_LINE ${CMAKE_CURRENT_FUNCTION_LIST_LINE}")
endfunction()
输出结果为
CMAKE_CURRENT_FUNCTION foo
CMAKE_CURRENT_FUNCTION_LIST_DIR Project/src
CMAKE_CURRENT_FUNCTION_LIST_FILE Project/src/CMakeLists.txt
CMAKE_CURRENT_FUNCTION_LIST_LINE 7
CMAKE_CURRENT_FUNCTION bar
CMAKE_CURRENT_FUNCTION_LIST_DIR Project/src/bar
CMAKE_CURRENT_FUNCTION_LIST_FILE Project/src/bar/CMakeLists.txt
CMAKE_CURRENT_FUNCTION_LIST_LINE 1