【CMake 语法】(12) CMake 宏和函数

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 为可选关键词,因为仅传入 LIBRARYLIBRARY 被设置为 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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值