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_AOUTPUT_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基礎: 函數和宏