# CMake
本文档为入门教程,后续在使用过程中会继续添加内容,更多内容请查阅最新的官方文档:
https://cmake.org/cmake/help/latest/index.html
## 安装
cmake:https://cmake.org/download/
cmake documentation:https://cmake.org/cmake/help/latest/
MinGW:https://sourceforge.net/projects/mingw-w64/files/mingw-w64/
PS: 编译器并不一定要使用MinGW,看你的实际开发的操作系统以及环境(MSVC, Clangd\).
## 构建和运行
- 新建构建目录和源代码目录(如 ./doc,README.md, run.sh以及其他文件)
>便于管理工程:在工程文件中创建了 src 目录以及 build 目录
```bash
mkdir build
mkdir src
```
- 进入该目录并配置项目
```bash
cd build
cmake ../src
```
如果不是使用默认的Generator,应当添加`-G`选项:
```bash
cmake -G "MinGW Makefiles" ../Step1
```
- 构建
- 内部构建:在工程文件目录下构建,生成的临时文件多,不好整理
- 外部构建:在工程文件目录下创建名为 "build" 的目录,在其中进行 cmake 命令的执行
> 以下是外部构建使用的指令,注意 cmake --build 会生成二进制目标文件
> 可以在vscode的配置文件中设置 "cwd" 变量到当前工作目录下
```bash
# 已在工程文件中创建了 src 目录以及 build 目录
# 使用cmake构建
~>cmake --build .
# 或者make构建
~>cmake -G "MinGW Makefiles" ..
~>make
```
- 运行
## 说明(CMake中的语法基本原则)
- cmake命令不区分大小写,但是参数、变量区分大小写
- 参数用空格` `或分号`;`隔开
- 使用`${VAR}`引用变量
- if控制语句中可直接使用变量名
- 引号`""`可加可不加,但如果字符串中有空格` `必须加
## 概念
- 目标文件(`target`):可执行文件(`add_executable`)、库文件(`add_library`)
- 命令(cmake-command):下面要讲的函数
- 变量(cmake-variable):以`CMAKE_`开头的变量名
- 属性(cmake-properties):文件/文件夹都有各自的属性,下面链接是关于CMake中的属性详细
https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties
## 命令
### cmake_minimum_required
设置最低cmake版本。
```cmake
cmake_minimum_required(VERSION <min>)
```
```cmake
cmake_minimum_required(VERSION 3.10)
```
### project
设置项目名。
```cmake
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
# 项目名会被存储在变量 PROJECT_NAME 和 CMAKE_PROJECT_NAME 中
# PROJECT_SOURCE_DIR 等价于 <PROJECT-NAME>_SOURCE_DIR
# PROJECT_BINARY_DIR 等价于 <PROJECT-NAME>_BINARY_DIR
# 如果定义了版本号
# 版本号被保存在 PROJECT_VERSION 和 <PROJECT-NAME>_VERSION 中
# 主版本号被保存在 PROJECT_VERSION_MAJOR 和 <PROJECT-NAME>_VERSION_MAJOR 中
# 次版本号被保存在 PROJECT_VERSION_MINOR 和 <PROJECT-NAME>_VERSION_MINOR 中
```
```cmake
project(Tutorial)
project(Tutorial C CXX)
project(Tutorial VERSION 2.3 LANGUAGES CXX)
```
### add_executable
用指定的源文件为项目添加可执行文件。
```cmake
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
# <name>即生成可执行文件的名字(与项目名没有关系),在一个项目中必须唯一
# 如windows系统会生成<name>.exe文件
```
CMake生成可执行文件时会自动寻找 .c 和 .cpp 文件,所以能不加源文件的后缀,但是如果遇到重名文件,如:
main.c 和 main 时会报错
```cmake
add_executable(Tutorial tutorial.cxx)
```
### message
`STATUS`: 前缀为--的信息
`SEND_ERROR`: 产生错误,跳过生成过程
`FATAL_ERROR`: 产生错误,终止运行
打印信息。
```cmake
message([<mode>] "message text" ...)
# STATUS 前缀为--的信息
# SEND_ERROR 产生错误,跳过生成过程
# FATAL_ERROR 产生错误,终止运行
```
### set
将变量设置为指定值。
```cmake
set(<variable> <value>)
# <value> 为空值相当于清空变量
```
#### 设置C++标准
内置变量名:`CMAKE_CXX_STANDARD`
```cmake
set(CMAKE_CXX_STANDARD 11)
```
#### 设置输出文件位置
一些常见的内置变量名:
`CMAKE_RUNTIME_OUTPUT_DIRECTORY`: 设置运行时目标文件(exe、dll)的输出位置
`CMAKE_ARCHIVE_OUTPUT_DIRECTORY`: 设置存档目标文件(lib、a)的输出位置
`CMAKE_BINARY_DIR`: 生成的二进制文件(.a/.o/.so/.exe)所在目录( ./build ),,使用方法 ${..} ,同名`<projectname>_BINARY_DIR`
`CMAKE_SOURCE_DIR`: 源代码(.c/.cpp)所在目录,使用方法 ${..} ,同名`<projectname>_SOURCE_DIR` | `PROJECT_BINARY_DIR`
`EXECUTABLE_OUTPUT_PATH` 等价于 `${PROJECT_BINARY_DIR}/bin`
`LIBRARY_OUTPUT_PATH` 等价于 `${PROJECT_BINARY_DIR}/lib`
```cmake
# 设置运行时目标文件(exe、dll)的输出位置 ./build/bin
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 设置存档目标文件(lib、a)的输出位置 ./build/lib
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 注意,生成的可执行文件能执行的条件是:
# 动态库 .dll 或者 .so 要么和生成的 .a 或者 .exe 存储在一个目录下,要么就在 系统目录 下
# 如 Windows OS 的 系统目录就在 C:\Windows\System32> 下
```
### option
定义一个开关。
```cmake
option(<variable> "<help_text>" [value])
# value的值为 ON 或 OFF ,默认为 OFF
# 命令行 -D<variable>=ON/OFF
```
```cmake
option(VERSION_ENABLE "output version" ON)
# 定义一个变量叫VERSION_ENABLE,其值为ON
# 在cmd中
~>cmake -G "MinGW Makefiles" .. \src -DDATE_ENABLE=OFF
```
### configure_file(配置文件)
将输入文件进行替换并生成输出文件。
```cmake
configure_file(<input> <output>)
# 输入文件中形如 @VAR@ 或 ${VAR} 的字符串会被替换为这些变量的当前值,如果未定义则被替换为空字符串
# 其他规则见下
```
```c
#cmakedefine VAR ...
// 会被替换为以下两行之一,取决于VAR是否被设置
#define VAR ...
/* #undef VAR */
```
例:本例使用的文件名后缀为in
```c
// config.h.in文件:
#cmakedefine FOO_ENABLE
#cmakedefine FOO_STRING "@FOO_STRING@"
#define FOO_ENABLE @FOO_STRING@
```
```cmake
# CMakeLists.txt文件
option(FOO_ENABLE "Enable Foo" ON)
if(FOO_ENABLE)
set(FOO_STRING "foo")
endif()
configure_file(config.h.in config.h)
```
```c
// 如果FOO_ENABLE为ON,则生成以下内容的.h文件
#define FOO_ENABLE
#define FOO_STRING "foo"
#define FOO_STRING "foo"
// 如果FOO_ENABLE为OFF,则生成以下内容的.h文件
/* #undef FOO_ENABLE */
/* #undef FOO_STRING */
#define FOO_ENABLE
// 没做实验并不确定 OPTION 为 OFF 第三点结论的正确性,或许是报错;
// 从逻辑上分析,上述结果不会发生,CMakeLists.txt 中的 FOO_ENABLE 变量不存在,
// 但是#define 定义了 FOO_ENABLE ,并使用了 CMakeLists 中该变量的值,
// 应该是默认为空,所以只是定义了这样一个空值的宏
```
### include_directories
指定所有目标的头文件路径。
```cmake
include_directories(dir1 [dir2 ...])
# 可添加多个目录
# 目录会被添加到当前文件的 INCLUDE_DIRECTORIES 属性中
# 当前文件的每一个目标文件的 INCLUDE_DIRECTORIES 属性也会添加该目录
```
### target_include_directories
指定目标的头文件路径。
```cmake
target_include_directories(<target> [SYSTERM] [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
# <target> 必须是已经通过 add_excutable() 或 add_library() 函数创建的目标,而且不是 IMPORTED 的(外源的)
# 如果指定了 SYSTERM 属性,则路径是一个系统路径
# target_include_directories 命令的参数可以使用”生成表达式“,语法是$<...>,见例
# 目标文件有 INCLUDE_DIRECTORIES 和 INTERFACE_INCLUDE_DIRECTORIES 两个属性
# INCLUDE_DIRECTORIES 对内头文件目录,限于工程内的目标文件使用,非工程内的目标文件不使用
# INTERFACE_INCLUDE_DIRECTORIES 对外头文件目录,提供给非工程内的目标文件使用,工程内的目标文件不使用
```
| | INCLUDE_DIRECTORIES | INTERFACE_INCLUDE_DIRECTORIES |
| --------- | ------------------- | ----------------------------- |
| PRIVATE | √ | |
| INTERFACE | | √ |
| PUBLIC | √ | √ |
参考:https://zhuanlan.zhihu.com/p/82244559
例:target_include_directories 命令使用“生成表达式”
```cmake
target_include_directories(mylib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/mylib>
$<INSTALL_INTERFACE:include/mylib> # <prefix>/include/mylib
)
# 包含路径的使用要求对于build树和instal树通常是不太一样的
# BUILD_INTERFACE 和 INSTALL_INTERFACE 生成表达式可以用来描述不同的使用要求
```
### add_subdirectory
添加子文件目录,子文件目录下也需要 `CMakeList.txt`
增加 `CMakeList.txt` 后,在 ./ 或者 ./build 下执行 cmake 命令时会转移到子目录下的 `CMakeList.txt` 进行调用;
子目录中的 `CMakeList.txt` 也需要规定,项目名,版本,输出的位置,链接的路径,头文件包含路径等等属性。
```cmake
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# 子目录下也要建立 CMakeLists.txt
# binary_dir 指定编译结果存放的位置
```
### add_library
用指定的源文件 **生成库**(.lib, .a; .dll, .so)。
也可以 **导入** **第三方库** (IMPORTED) 。
也可以指定部分文件不被编译
```cmake
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
# STATIC 静态库,编译时链接
# SHARED 动态库,运行时加载
# MODULE 模组,是一些插件,运行时使用dlopen-like的功能进行动态加载
# EXCLUDE_FROM_ALL 会在目标文件上设置相应的属性(执行默认make的时候,这个目标文件会被排除在外,不被编译)
# 生成的库文件名为 lib<name>.xxx
```
```cmake
add_library(<name> [STATIC | SHARED | MODULE | UNKNOWN] IMPORTED
[GLOBAL])
# IMPORTED 代表一个工程外部的的库文件,不需要添加 [<source>...] 文件,因为当前工程下没有命令来编译它,并且该库的IMPORT属性为true
# 没有设置 GLOBAL 的时候,这个目标名称的作用域只在创建它的目录以及子目录中;设置后,全局可见
# Imported Target 通常代表一个工程的一个依赖,一般只能作为诸如 target_link_libraries() 这样的右值,不能修改
```
```cmake
add_library(<name> OBJECT <src>...)
# 创建一个特殊的 object library 目标,这种库只编译源文件生成的目标文件,但是不会把这些目标文件打包进一个lib\
# 当其他库或者目标文件需要使用这些目标文件时,会以这样的形式来添加,objlib是这个库的名字
add_library(... $<TARGET_OBJECTS:objlib> ...)
add_executable(... $<TARGET_OBJECTS:objlib> ...)
```
```cmake
add_library(<name> INTERFACE [IMPORTED [GLOBAL]])
# 创建一个 Interface 库,这个库不会直接创建编译目标文件,即使这个库可以设置一些属性并能被 installed 和 imported
# 通常来说,使用
# set_property(), target_link_libraries(INTERFACE),
# target_include_directories(INTERFACE),
# target_compile_options(INTERFACE), target_compile_definitions(INTERFACE)
# 这些函数来设置 INTERFACE_* 属性,然后这个库就可以像其他库一样作为target_link_libraries()命令的参数
# 话可以通过 IMPORTED 关键字来生成一个Interface imported target,表示这个库文件在工程外
```
### target_link_libraries
为目标文件链接库。
```cmake
target_link_libraries(<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
# item 可以是target名、绝对路径(必须保证文件存在)
# [<PRIVATE|PUBLIC|INTERFACE> <item>...] 是一组 关键字 与 参数
```
### set_properties
通用的设置属性命令,这些属性会影响源文件,目标文件,目录,缓存(Cache)和测试使用的附件文件(ATTACHED_FILES)。
参见 https://cmake.org/cmake/help/latest/index.html
### set_target_properties
可设置目标文件的的属性,这些属性会在其创建过程中影响目标文件。
```cmake
set_target_properties([<target1> <target2> ...]
PROPERTIES [<prop1> <value1>]
[<prop2> <value2>] ...)
# 列出所有你想改变的目标文件 <target>, 然后提供你接下来想设置的属性。
# 使用 [<prop> <value>] 对(pair)来提供你想要改变的值,
# 设置后可用 get_property 或 get_target_property 命令获取你设置的 [属性 值] 对。
```
例:
```cmake
# 可以用于构建同名的静态库与动态库(后缀不同仍然会CMake报错)
# 定义源文件列表
aux_source_directory(. SRC_LIST)
# 指定最终生成的可执行文件的路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# 指定生成目标 {SRC_LIST}代表前面定义的源文件列表变量 动态库名 math
add_library(math MODULE ${SRC_LIST})
# 指定静态库名 math_static
add_library(math_static STATIC ${SRC_LIST})
# 改变最终生成的静态库的名字
set_target_properties(math_static PROPERTIES OUTPUT_NAME math)
# 获取指定构建目标的指定属性的值;这里获取 math_static 的属性 OUTPUT_NAME 值赋值给 OUTPUT_VALUE 变量
# 并使用 message 命令打印到终端
get_target_property(OUTPUT_VALUE math_static OUTPUT_NAME)
message(STATUS "This is the math_static OUTPUT_NAME:" ${OUTPUT_VALUE})
```
下列文件的结构为
```shell
~$tree
./sample6
|
+--- CMakeLists.txt
|
+--- src/
+--- CMakeLists.txt
|
+--- Math.h
|
+--- Math.cpp
```
```cmake
# 导入第三方库时用于设置导入的路径
set_target_properties(
<Thirdlib>
PROPERTIES IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/jniLibs/libThirdlib.so
)
# CMAKE_CURRENT_SOURCE_DIR 是内置变量,表示正在编辑的CMakeLists.txt的绝对路径
```
```cmake
# 添加 .dll(.so) 文件版本号
set_target_properties(math PROPERTIES VERSION 1.2 SOVERSION 1)
# VERSION 指代动态库版本
# SOVERSION 指代API版本
```
### 区分
```cmake
# 头文件目录 .cpp 文件中的包含 .h 的搜索路径
include_directories()
target_include_directories()
# 链接时库目录 链接时编译器搜索的目录路径,不推荐使用这组命令
link_directories()
target_link_directories()
# 链接库 链接时搜索的 .dll(.so) 和 .a 文件的路径
link_libraries()
target_link_libraries()
# 都推荐使用以target_开头的函数
```
### list
```cmake
# Reading
list(LENGTH <list> <out-var>)
list(GET <list> <element index> [<index> ...] <out-var>)
list(JOIN <list> <glue> <out-var>)
list(SUBLIST <list> <begin> <length> <out-var>)
# Search
list(FIND <list> <value> <out-var>)
# Modification
list(APPEND <list> [<element>...]) # 追加
list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>) #
list(INSERT <list> <index> [<element>...]) #
list(POP_BACK <list> [<out-var>...]) #
list(POP_FRONT <list> [<out-var>...]) #
list(PREPEND <list> [<element>...]) #
list(REMOVE_ITEM <list> <value>...) #
list(REMOVE_AT <list> <index>...) #
list(REMOVE_DUPLICATES <list>) #
list(TRANSFORM <list> <ACTION> [...]) #
# Ordering
list(REVERSE <list>)
list(SORT <list> [...])
```
The list subcommands `APPEND`, `INSERT`, `FILTER`, `PREPEND`, `POP_BACK`, `POP_FRONT`, `REMOVE_AT`, `REMOVE_ITEM`, `REMOVE_DUPLICATES`, `REVERSE` and `SORT` may create new values for the list within the current CMake variable scope. Similar to the [`set()`](https://cmake.org/cmake/help/latest/command/set.html#command:set) command, the LIST command creates new variable values in the current scope, even if the list itself is actually defined in a parent scope. To propagate the results of these operations upwards, use [`set()`](https://cmake.org/cmake/help/latest/command/set.html#command:set) with `PARENT_SCOPE`, [`set()`](https://cmake.org/cmake/help/latest/command/set.html#command:set) with `CACHE INTERNAL`, or some other means of value propagation.
- A list in cmake is a `;` separated group of strings. To create a list the set command can be used. For example, `set(var a b c d e)` creates a list with `a;b;c;d;e`, and `set(var "a b c d e")` creates a string or a list with one item in it. (Note macro arguments are not variables, and therefore cannot be used in LIST commands.)
- When specifying index values, if `<element index>` is 0 or greater, it is indexed from the beginning of the list, with 0 representing the first list element. If `<element index>` is -1 or lesser, it is indexed from the end of the list, with -1 representing the last list element. Be careful when counting with negative indices: they do not start from 0. -0 is equivalent to 0, the first list element.
### find_library
使用后返回一个句柄, 可以通过 `message` 来打开调用句柄显示: https://cmake.org/cmake/help/latest/command/find_library.html
简明介绍
```cmake
find_library (<VAR> name1 [path1 path2 ...])
```
详细介绍
```cmake
find_library (
<VAR>
name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
[HINTS [path | ENV var]... ]
[PATHS [path | ENV var]... ]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[DOC "cache documentation string"]
[NO_CACHE]
[REQUIRED]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_INSTALL_PREFIX]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
)
```
## 安装
将可执行文件,库文件,头文件等其他文件安装至一个指定目录
### cmake代码
在对应目录的`CMakeLists.txt`中使用。
```cmake
install(TARGETS <target> DESTINATION <dir>) # 目标文件
install(FILES <file> DESTINATION <dir>) # README, <doc/documentation_file>, COPYRIGHT
install(PROGRAMS <非目标文件的可执行程序> DESTINATION <dir>) # 如脚本,包括Python3,Bash,Fish等
install(DIRECTORY <dir_s> DESTINATION <dir_t>) # 安装目录
# 注意目录必须以 / 结尾,不然会把 <dir_s> 目录下的文件安装到 <dir_t> 目标下\
# 加 / 后安装的则为整个文件夹, 结果将是这种正确的结构 <dir_t>/<dir_s>
```
```
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
install(DIRECTORY doc/ DESTINATION doc)
```
### 命令行
```bash
cmake --install . # 安装到默认目录 CMAKE_INSTALL_PREFIX
cmake --install . --prefix <dir> # 安装到指定目录
```
### 相关内置变量
`CMAKE_INSTALL_PREFIX`: 在Linux OS中一般默认为 /user/local/
`DESTINATION`: 等价于${CMAKE_INSTALL_PREFIX}/<该变量定义的path>
## 脚本语言
### if 条件语句
简介
```cmake
if(<condition>)
<commands>
elseif(<condition>) # optional block, can be repeated
<commands>
else() # optional block
<commands>
endif()
```
基本表达式
```cmake
if(<constant>)
if(<variable>)
if(<string>)
```
逻辑运算符
```cmake
if(NOT <condition>)
if(<cond1> AND <cond2>)
if(<cond1> OR <cond2>)
if((condition) AND (condition OR (condition)))
# 括号内条件先被评估,如果有嵌套的括号,最里面的小括号优先
# 短路(存疑)
```
存在性检查
```cmake
if(COMMAND command-name) # 如果给定的名称是一个可以调用的命令、宏或函数,则为真
if(POLICY policy-id) # 如果给定的名称是一个现有的POLICY(形式为CMP<NNNN>),则为真
if(TARGET target-name) # 如果给定的名称是由调用 add_executable()、add_library()或add_custom_target() 命令创建的
# 现有逻辑目标名称,并且已经被调用(在任何目录下),则为真
if(TEST test-name) #如果给定的名称是由add_test()命令创建的现有测试名称,则为真
if(DEFINED <name>|CACHE{<name>}|ENV{<name>})
# 如果定义了具有给定<name>的变量、缓存变量(cache name)或环境变量(environment name),则为真.
# 该变量的值并不重要。请注意以下注意事项:
# 宏参数不是变量
# 不可能直接测试<name>是否是一个非缓存变量。
# 表达式if(DEFINED someName)将在缓存或非缓存变量someName存在时评估为真。
# 相比之下,表达式if(DEFINED CACHE{someName}只有在缓存变量someName存在的情况下才会被评估为真。
# 如果需要知道非缓存变量是否存在,则需要测试这两个表达式:
if(DEFINED someName AND NOT DEFINED CACHE{someName})
if(<variable|string> IN_LIST <variable>) # 如果给定的元素包含在命名的列表变量中,则为真
```
文件操作
```cmake
if(EXISTS path-to-file-or-directory) # 判断给定文件或路径参数是否存在
if(file1 IS_NEWER_THAN file2) # 判断给定文件file1是否比file2新
if(IS_DIRECTORY path-to-directory) # 给定参数path-to-directory是否为目录
if(IS_SYMLINK file-name) # 给定参数file-name是否为符号链接
if(IS_ABSOLUTE path) # 给定参数path是否为绝对路径
```
比较操作
```cmake
if(<variable|string> MATCHES regex) # 匹配正则表达式
if(<variable|string> <LESS| # 值
GREATER|
EQUAL|
LESS_EQUAL|
GREATER_EQUAL|
STRLESS| # 字数上的
STRGREATER|
STREQUAL|
STRLESS_EQUAL|
STRGREATER_EQUAL>
<variable|string>)
```
### while 循环语句
```cmake
# while语句的 <condition> 支持和在 if语句中所描述的 <condition> 相同的语法和逻辑运算操作
## endwhile() 命令允许一个可选的<condition>参数,但必须是逐字逐句地重复开头的 while() 命令的参数
while(<condition>)
<commands>
endwhile()
# 同时提供了逃避正常控制流的方法 break() 和 continue() 命令.
break()
continue()
```
### function 函数
```cmake
fucntion(<function_name> [<arg1>...])
<commands>
endfunction()
# 在函数体中使用参数宏:
# ARGC 参数数量
# ARG0,ARG1,ARG2,ARG3,... 为参数的引用
# ARGV 所有参数的引用
# ARGN 最后一个参数的引用
```