CMake 函数和宏
CMake 函数
CMake 函数定义语法如下, 其中 name
为函数名, <arg1>
为参数名, <commands>
为函数体. 函数定义后, 可以通过 name
调用函数. 函数名允许字母数字下划线, 不区分大小写.
function(name [<arg1> ...])
<commands>
endfunction()
如下的样例定义了一个函数fun
, 不带任何参数.
function(fun)
message("Hello, World!")
endfunction()
# 调用函数
fun()
FUN()
Fun()
cmake_language(CALL fun)
# 携带了参数, 参数被函数忽略
FUN(A B C)
函数参数
CMake 将参数分为如下几种类型:
- 命名参数, 按照携带值的数量进一步分为:
option
类型, 不携带任何值, 如果存在则视为真, 不存在视为假. 比如ENABLE_TESTS
指定是否编译测试.single
类型, 携带一个值. 比如参数OUTPUT
指定一个目标文件.multi
类型, 携带多个值. 比如参数SOURCE
指定多个源文件.
- 未命名的参数.
CMake 对于每个函数都自动定义了如下三个变量:
ARGC
: 函数参数个数.ARGV
: 函数参数列表. 包含命名参数和未命名参数.ARGN
: 只包含未命名参数.
我们先看一下ARGN
的使用场景:
function(add_gtest targetName)
add_executable(${targetName} ${ARGN})
target_link_libraries(${targetName} PRIVATE GTest::gtest)
add_test(NAME ${targetName} COMMAND ${targetName})
endfunction()
# 使用方式
add_gtest(test1 test1.cpp)
add_gtest(test2 test2.cpp util.cpp)
参数解析
CMake 使用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>)
第二种方式是在 3.7 版本引入的, 并且不能再宏中使用. 二者的区别在于PARSE_ARGV
指定了参数列表的起始位置, 这在一些嵌套的函数参数传递中有用.
function(fun)
set(options ENABLE_A ENABLE_B ENABLE_C)
set(single OUTPUT_NAME)
set(multi DEPENDS SOURCES)
cmake_parse_arguments(arg "${options}" "${single}" "${multi}" ${ARGN})
foreach(opt IN LISTS options)
if(arg_${opt})
message(STATUS "${opt} is set")
endif()
endforeach()
if (arg_OUTPUT_NAME)
message(STATUS "OUTPUT_NAME=${arg_OUTPUT_NAME}")
endif()
foreach(key IN LISTS multi)
if (arg_${key})
message(STATUS "${key}=${arg_${key}}")
endif()
endforeach()
endfunction()
# 调用函数
fun(ENABLE_A
OUTPUT_NAME "output.exe"
DEPENDS "lib-a" "lib-b" "lib-c"
SOURCES s1.cpp s2.cpp s3.cpp
)
输出:
-- ENABLE_A is set
-- OUTPUT_NAME=output.exe
-- DEPENDS=lib-a;lib-b;lib-c
-- SOURCES=s1.cpp;s2.cpp;s3.cpp
设置返回值
从 CMake 3.25 开始, CMake 支持return
语句中设置返回值. 注意此时需要设置 CMake Policy CMP0140
为NEW
.
cmake_minimum_required(VERSION 3.25)
cmake_policy(SET CMP0140 NEW)
function(getVal retValName)
set(${retValName} "Hello, World!")
return (PROPAGATE ${retValName})
endfunction()
getVal(ret1)
message(STATUS "ret1=${ret1}") # 输出 -- ret1=Hello, World!
而在以前的版本中, 一般是通过set
变量存在于父级的作用域达到返回值目的.
function(getValOld retValueName)
set(${retValueName} "Glad to see you" PARENT_SCOPE)
endfunction()
getValOld(ret2)
message(STATUS "ret2=${ret2}")
常见错误
- 函数重复定义. 当使用
function()
或macro()
定义一个新命令时, 如果已经存在同名的命令, CMake 有一个未记录的行为: 旧命令会以原名称加下划线的形式继续可用. 无论旧名称是内置命令, 还是自定义函数或宏, 都是如此. 了解这一行为的开发者有时会试图利用它来创建现有命令的包装器,
function(fun)
message("call 1")
endfunction()
function(fun)
message("call 2")
_fun()
endfunction()
function(fun)
message("call 3")
_fun()
endfunction()
fun()
这个函数将会无限循环, 并最终导致栈溢出.
- 第二次定义的时候,
_fun
指向第一个定义的函数, 此时还是可以正常工作的. - 第三次定义的时候,
_fun
已经指向了第二个定义的函数, 而第二个定义的函数中又调用了_fun
, 因此会无限循环.
CMake 宏
CMake 宏的定义方式与函数定义方式相同, 定义语法与函数定义语法相同.
macro(name [arg1 [arg2 [...]]])
# command list...
endmacro()
宏在调用之后就是被粘贴到调用的位置, 宏不会产生一个新的作用域. 跟 C/C++中的#define
类似, 其实本质上就是做的文本替换.
专栏目录
- 快速上手
- 最佳实践
- CMake基础: 变量
- CMake基础: 控制流
- CMake基础: 函数和宏