最近常常使用cmake构建c++项目有感,从创建项目到打包发布总结一下需要注意的事情.
项目组织方式
具体的项目组织方式因人而异,这里推荐一种,在src目录中创建模块目录,再在include目录中常见对应的同名目录包含头文件,可执行程序的源代码或者最终生成库的源代码可以放在app目录中.
比如我看的一个项目组织如图
- 在src目录中包括demo,view,assignment三个项目,对应include目录相同,或者在生成程序的目录中包含头文件而不另外放include中.
- 此外也有src目录中放所有的源代码文件,include目录分别放每个模块对应的头文件,相对来说更方便.
针对第一种组织方式,cmake会在src目录添加模块1
2
3add_subdirectory(view)
add_subdirectory(demo)
add_subdirectory(assignments)
每个模块再单独写cmake,甚至可以单独写project,这样方便模块化,可以看到下面利用不同的${PROJECT_NAME}
设置库生成位置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25project(demo_hello_world)
file(GLOB source
"${CMAKE_CURRENT_SOURCE_DIR}/demo_hello_world.cpp"
)
add_executable(${PROJECT_NAME} ${source})
set_target_properties(${PROJECT_NAME} PROPERTIES
DEBUG_POSTFIX "_d"
RUNTIME_OUTPUT_DIRECTORY "${BINARY_DIR}"
LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_DIR}"
ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_DIR}")
target_link_libraries(${PROJECT_NAME} PUBLIC view)
project(demo)
file(GLOB source
"${CMAKE_CURRENT_SOURCE_DIR}/demo.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/window_demo.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/window_demo.h"
)
add_executable(${PROJECT_NAME} ${source})
set_target_properties(${PROJECT_NAME} PROPERTIES
DEBUG_POSTFIX "_d"
RUNTIME_OUTPUT_DIRECTORY "${BINARY_DIR}"
LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_DIR}"
ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_DIR}")
target_link_libraries(${PROJECT_NAME} PUBLIC view)
比较来看,如果给src目录中放所有的cpp源文件,那不好给模块分离,因为一个项目中一般包括一个生成可执行程序或最终库的源代码,一起一堆供这个目标依赖的模块,这些模块如果能单独提出来更好,也就是说这些模板的cpp代码如果放在分别的模块目录下虽然更麻烦但更好. 此外将main程序放在app目录中也更加清晰.
依赖图与文档生成
查看目标的依赖
下载graphviz1
cd build && cmake .. --graphviz=graph.dot && dot -Tpng graph.dot -o graphImage.png
使用Doxygen生成文档
需要按照规定格式撰写注释,根据注释生成文档Doxygen: Documenting the code.
doxygen支持许多格式注释,下面列举三种1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* ... text ...
*/
/** Brief description which ends at this dot. Details follow
* here.
*/
/*!
* ... text ...
*/
/*! \brief Brief description.
* Brief description continued.
*
* Detailed description starts here.
*/
/*!
... text ...
*/
注释 | 介绍 |
---|---|
@file | 文件说明 |
@author | 作者的信息 |
@brief | 用于class 或function的批注中,后面为class 或function的简易说明 |
@param | 参数介绍 |
@return | 函数传回值的说明 |
Doxygen 还需要一个 Doxyfile,其包含文档生成的所有参数,比如输出格式、排除的文件模式、 项目名称等。因为配置参数太多,开始配置 Doxygen 可能会让人望而生畏,但 CMake 可以自动生 成 Doxyfile。
1 | doxygen -g # 生成doxyfile |
配置doxygenfile然后运行doxygen
生成.
当然更好的方式是结合cmake,首先找到doxygen程序,然后设置需要的选项Doxygen: Configuration,最后生成文旦. 可以使用add_custom_target
或者doxygen_add_docs(推荐)
FindDoxygen — CMake 3.30.3 Documentation1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24find_package(Doxygen)
if (DOXYGEN_FOUND)
set(DOXYGEN_OUTPUT_DIRECTORY"${CMAKE_CURRENT_BINARY_DIR}/docs")
set(DOXYGEN_GENERATE_HTML YES)
set(DOXYGEN_GENERATE_MAN YES)
set(DOXYGEN_MARKDOWN_SUPPORT YES)
set(DOXYGEN_AUTOLINK_SUPPORT YES)
set(DOXYGEN_HAVE_DOT YES)
set(DOXYGEN_COLLABORATION_GRAPH YES)
set(DOXYGEN_CLASS_GRAPH YES)
set(DOXYGEN_UML_LOOK YES)
set(DOXYGEN_DOT_UML_DETAILS YES)
set(DOXYGEN_DOT_WRAP_THRESHOLD 100)
set(DOXYGEN_CALL_GRAPH YES)
set(DOXYGEN_QUIET YES)
#add_custom_target(docs ${DOXYGEN_EXECUTABLE} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/docs))
doxygen_add_docs(
docs
"${CMAKE_CURRENT_LIST_DIR}"
ALL
COMMENT "Generating documentation for myproject"
)
endif()1
2
3
4
5
6
7doxygen_add_docs(targetName
[filesOrDirs...]
[ALL]
[USE_STAMP_FILE]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[CONFIG_FILE filename])
第一个参数 targetName 是文档目标的名称,该函数将生成一个名为 targetName 的自定义目 标。这个目标将触发 Doxygen,并在构建时使用代码创建文档。
filesOrDirs包含想要从文档生成的代码的文件或目录的列表。
ALL 参数用于使 CMake 的 ALL 元目标依赖于 doxygen_add_docs(…) 创建的文档目标,因此在构建 ALL 元目标时自动生成文档。
WORKING_DIRECTORY默认是 CMAKE_CURRENT_SOURCE_DIR
代码检查和格式化工具
这部分工作其实完全可以交由IDE提供,不需要在cmake build时使用的,但为了保持兼容,这里简略写一点
可以考虑使用clang-tidy
和clang-format
工具,在cmake文件中1
2
3
4
5
6
7
8
9
10
11
12cmake_minimum_required(VERSION 3.28)
project(my-project)
add_executable(my-app main.c)
file(GLOB_RECURSE ALL_SOURCE_FILES
*.c *.h *.cpp *.hpp *.cxx *.hxx *.cc *.hh *.cppm *.ipp *.ixx)
add_custom_target(format
COMMAND clang-format
-i
${ALL_SOURCE_FILES}
)
对于clang-tidy
完全可以在.clang-tidy
文件中设置并通过clangd进行检查Enabling clang-tidy checks in clangd - Clang Frontend / clangd - LLVM Discussion Forums
Configuration (llvm.org)1
2
3
4
5# .clangd
Diagnostics:
ClangTidy:
CheckOptions:
readability-identifier-naming.VariableCase: CamelCase
此外还可以设置编译器编译链接选项检查内存和初始化等错误1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function (add_sanitier target)
message(STATUS "Adding sanitizer to target ${target}")
if (CMAKE_CXX_COMPILER_ID MATCHES "CLANG" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options("-fno-omit-frame-pointer")
add_link_options("-fno-omit-frame-pointer")
target_compile_options(${target} PRIVATE -fsanitize=address)
target_link_libraries(${target} PRIVATE -fsanitize=address)
target_compile_options(${target} PRIVATE -fsanitize=undefined)
target_link_libraries(${target} PRIVATE -fsanitize=undefined)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_definitions(${target} PRIVATE /fsanitize=address)
else()
message(WARNING "Sanitier is not supported for ${CMAKE_CXX_COMPILER_ID}")
endif()
endfunction()
除此之外,可以使用valgrind
等工具动态debug查找内存问题.
进行测试
CTest
Testing With CMake and CTest — Mastering CMake1
2
3
4
5include(CTest)
add_executable(TestInstantiator TestInstantiator.cxx)
target_link_libraries(TestInstantiator vtkCommon)
add_test(NAME TestInstantiator
COMMAND TestInstantiator)1
make test
CTest 模块通常应该只包含在项目的顶层 CMakeLists.txt 中。自从 CMake 版本 3.21 以 来,PROJECT_IS_TOP_LEVEL 可以用来测试当前的 CMakeLists.txt 是否为顶层文件。
对于项目的顶层目录和使用 ExternalProject 添加的项目顶层目录,此变量为 True。对于使用 add_subdirectory 或 FetchContent 添加的目录,该值为 False1
2
3
4
5project(CMakeBestPractice)
...
if(PROJECT_IS_TOP_LEVEL)
include(CTest)
endif()1
2
3
4add_test(NAME <name> COMMAND <command>
[COMMAND_EXPAND_LISTS])1
2
3ctest --test-dir <build_dir>
cmake --build <build_dir> --target test # 注意这里目标就是test,而不是add_test中添加的NAME
ctest --build-and-test <source_dir> <build_dir>
可以设置ctest多个lable,然后通过过滤查看对应结果1
2
3
4
5
6add_test(NAME labeled_test_1 COMMAND someTest)
set_tests_properties(labeled_test PROPERTIES LABELS "example")
add_test(NAME labeled_test_2 COMMAND anotherTest)
set_tests_properties(labeled_test_2 PROPERTIES LABELS "will_fail" )
add_test(NAME labeled_test_3 COMMAND YetAnotherText)
set_tests_properties(labeled_test_3 PROPERTIES LABELS "example;will_fail")1
ctest -L "example|will_fail"
-L
进行过滤1
ctest -I [Start,End,Stride,test#,test#,...|Test file]
通过 Start、End 和 Stride,可以指定要执行的测试的范围。这三个数字是与显式测试数字 test# 相结合的范围,或传递包含参数的文件
处理大量测试
1 | create_test_sourcelist (SourceListName |
注意ctest并不提供方便测试的方法,可以使用第三方库提供的REQUIRE等方法
使用include(Ctest)
和add_test
可使得可以方便使用ctest
命令进行测试
比如1
2cmake --build build
cd build && ctest
使用Catch2
创建tests目录,编写cmake文件1
2
3
4
5
6
7
8
9
10if(ENABLE_TESTING)
set(TEST_MAIN "unit_tests")
set(TEST_SOURCES main.cpp)
set(TEST_INCLUDES "./")
add_executable(${TEST_MAIN} ${TEST_SOURCES})
target_include_directories(${TEST_MAIN} PUBLIC ${TEST_INCLUDES})
target_link_libraries(${TEST_MAIN} PUBLIC ${LIBRARY_NAME} Catch2::Catch2WithMain)
endif()
自动发现测试1
2
3
4
5
6
7
8
9cmake_minimum_required(VERSION 3.5)
project(baz LANGUAGES CXX VERSION 0.0.1)
find_package(Catch2 REQUIRED)
add_executable(tests test.cpp)
target_link_libraries(tests PRIVATE Catch2::Catch2)
# list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) # use FetchContent
include(CTest)
include(Catch)
catch_discover_tests(tests)
使用GoogleTest
1 | cmake_minimum_required(VERSION 3.14) |
代码覆盖检查
检查测试了哪些代码并生成覆盖率报告,使用Gcov生成覆盖率信息,使用覆盖分析程序,如 Gcovr 或 LCOVn分析覆盖文件并生成报告
小结
CTest+Catch2即可
第三方库管理
使用FetchContent下载库
1 | include(FetchContent) |
需要项目是cmake项目
使用vcpkg等包管理工具下载库
包管理器与xmake介绍1
2
3
4
5
6
7
8
9
10{
"version": 6,
"configurePresets": [
{
"name": "my-preset",
"binaryDir": "${sourceDir}/build",
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
]
}1
2
3
4
5
6cmake_minimum_required(VERSION 3.28)
project(my-project)
find_package(ftxui REQUIRED)
add_executable(my-app main.cpp)
target_compile_features(my-app PRIVATE cxx_std_20)
target_link_libraries(my-app PRIVATE ftxui::dom ftxui::screen ftxui::component)1
2
3
4vcpkg install
cmake --preset my-preset
cmake --build build
./build/my-app
使用Conan
简单介绍一下使用流程
首先定义conanfile.txt和conan profile1
2
3
4
5
6[requires]
zlib/1.2.11
[generators]
CMakeDeps
CMakeToolchain1
conan profile detect --force
然后执行conan insatll
会生成conan_toolchain.cmake
1
conan install . --output-folder=build --build=missing
再在cmake中使用1
2cd build
cmake .. -G "Visual Studio 15 2017" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake"
借助git submodule下载库
Git - git-submodule Documentation
Git submodule | Atlassian1
2
3
4git submodule add https://bitbucket.org/jaredw/awesomelibrary # 添加子模块(当前新版本git也会下载对应模块)
git submodule init //初始化子模块 (根据.gitmodules更新信息)
git submodule update //更新子模块
git submodule update --init --recursive # 更新映射关系并递归下载模块
使用git submodule add
之后会创建.gitmodules文件并写入相关信息,包括子模块path和url,其中path是安装路径,因此我们可以借助修改path,使得git update --init
安装子模块时安装到3rd_party或vendor目录便于管理,比如1
2
3[submodule "glfw"]
path = third_party/glfw
url = https://github.com/glfw/glfw.git
修改.gitmodules 文件中对应模块的名字或者path,然后使用git submodule sync
进行更新.1
2git submodule sync --recursive
git submodule update --init --recursive
此外还会在.git/config
和.git/modules
中添加子模块信息
update
的作用是根据项目的配置信息,拉取更新子模块中的代码,也可以使用git clone --recurse-submodules
直接下载子模块
卸载子模块1
2git submodule deinit project-sub # 在.gitmodules中对应的模块名
git rm project-sub # 删除模块目录与.git/config,.git/modules信息
总结来说,可以使用第三方管理工具,下载链接非常方便. 对于自己写的一些库或者没有cmake的项目可以使用vendor/3rd_party方式,放在一个单独目录,如果是源代码,添加源文件和头文件,生成库,cmake如下1
2
3
4
5
6
7
8
9
10# glad
set(glad_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/glad" CACHE STRING "")
file(GLOB source
"${glad_SOURCE_DIR}/src/*.c"
)
add_library(glad ${source})
target_include_directories(glad
PUBLIC "${glad_SOURCE_DIR}/include"
)
set_target_properties(glad PROPERTIES FOLDER "third_party")
如果是已经编译好的库,使用add_library(xxx SHARED IMPORTED)
并设置库文件位置1
2
3
4
5add_library(glad SHARED IMPORTED)
set_target_properties(glad PROPERTIES IMPORTED_LOCATION "/path/to/glad/library")
set_target_properties(glad PROPERTIES IMPORTED_IMPLIB "/path/to/glad/library") # 针对windows
set_target_properties(glad PROPERTIES FOLDER "third_party")
IMPORTED_IMPLIB:用于指定导入库的导入库文件(import library file)。在 Windows 上,通常用于 .lib 文件。这个属性通常用于静态链接的导入库
小结
vcpkg,conan的逻辑是使用一个文件声明项目信息和依赖,然后在cmake中添加toolchainfile用于下载对应的包,而CPM和FetchContent直接在cmake中声明需要添加的包.
优先使用第三方包管理工具,因为相比FetchContent
提供更多功能,如果第三方库不是cmake项目,使用git submodule
方式,下载到某个文件夹编译源代码、链接库
项目打包、安装与分发
install
安装target
具体来说install(TARGETS …)会安装生成的的东西,不会安装头文件或者项目中的json、txt等读取文件.1
install(TARGETS <target>... [...])
这里最需要注意的就是动态库不包括windows上的dll.
默认安装路径如下,安装目录在Unix上usr/local,Windows是C:/program files,前缀通过cmake --prefix
或CMAKE_INSTALL_PREFIX
指定
install(TARGETS…) 如果 包 含 EXPORT 参 数, 用 于 从 给 定 的 install(…) 目 标 创 建 一 个 导 出 名 称,可以使用此导出名称导出这些目标
安装文件
安装的东西并不总是目标输出构件的一部分。它们可能是目标的运行时依赖项,例如图片、源文件、脚本和配置文件
1 | install ( |
install(FILES…) 指令接受一个或多个文件作为参数,TYPE 和DESTINATION 用于确定指定文件的目标目录。TYPE 用于指示哪些文件将使用该文件类型的默认 路径作为安装目录
1
2
3
4
5install(FILES "${CMAKE_CURRENT_LIST_DIR}/greeter_content"
DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(PROGRAMS "${CMAKE_CURRENT_LIST_DIR}/greeter.py"
DESTINATION "${CMAKE_INSTALL_BINDIR}" RENAME chapter4_greeter)
安装目录
1 | install(DIRECTORY dir1 dir2 dir3 TYPE LOCALSTATE) |
可以指定匹配文件和排除文件模式.
config-file
当别人安装了你的库,也要方便使用.为了让其他用户使用find_package
找到我们的包,需要config-file.
包配置文件Config.cmake
设置如下1
2
3include(GNUInstallDirs) # 便于获取安装路径变量
set(FOO_INCLUDE_DIRS ${PREFIX}/include/foo-1.2)
set(FOO_LIBRARIES ${PREFIX}/lib/foo-1.2/libfoo.a)
搜索包时,find_package(…) 会查找 /cmake 目录,所以包配置文件放在/cmake中.1
2
3
4# top level cmake
include(GNUInstallDirs)
set(project_INSTALL_CMAKEDIR cmake CACHE PATH
"Installation directory for config-file package cmake files") # 于设置 config-file 打包配置文件的安装目录1
2
3
4target_include_directories(ch4_ex05_lib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
target_compile_features(ch4_ex05_lib PUBLIC cxx_std_11)
使用$ 使用 要实现对 find_package(…) 的完全支持,还需要获取xxxConfig-version.cmake 文件 安装包并使用 Packaging With CPack — Mastering CMake cpack包含多种生成器生成包 常用cpack变量,用于设置项目打包时的信息 CPACKPACKAGE_NAME 和 CPACK_PACKAGE_VERSION* 默认从顶层项目名称和版本中获取 项目配置后,生成CpackConfig.cmake 和 CpackConfigSource.cmake文件到build/CPack*中,使用 注意设置generator时,其中每个都需要符合条件. 比如nsis需要安装对应的软件,否则设置generator包括它时会直接报错 add_custom_target 的核心是通过 COMMAND 选项传递的命令列表。虽然第一个命令可 以不带这个选项,但最好在 add_custom_target 中添加 COMMAND 选项。 默认情况下,定制目标只在显式请求时执行,除非指定了 ALL 选项。 自定义目标总认为是过时的,因此总是运行指 定的命令,而不管是否会反复产生相同的结果。 使用 DEPENDS 关键字,可以使定制目标依赖于使用 add_custom_command 或其他目标定义的定制命令的文件和输出。 要使自定义目标依赖于另 一个目标,可以使用 add_dependencies。若使用自定义目标创建文件,可以在 BYPRODUCTS 选项下列出这些文件。 列出的文件都将使用 GENERATED 属性标记,CMake 使用该属性来确定构建是否过期,并找出需要清理的文件,但使用 add_custom_command 创建文件的任务可能更适 合。 通常,命令在当前二进制目录中执行,该目录在 CMAKE_CURRENT_BINARY_DIRECTORY 缓存变量中。若需要修改,这可以通过 WORKING_DIRECTORY 选项来更改。该选项可以是绝对 路径,也可以是相对路径 (当前二进制目录的相对路径)。 可以在以下时段将命令连接到构建中: • PRE_BUILD: 在 Visual Studio 中,此命令在执行其他构建步骤之前执行。当使用其他生成器 时,会在 PRE_LINK 命令之前运行。 • PRE_LINK: 此命令将在编译源代码之后运行,在可执行文件或存档工具链接到静态库之前运行。 • POS_BUILD: 这将在执行所有其他构建规则后运行该命令。 执行自定义步骤最常见的方法是使用 POST_BUILD; 其他两个选项很少使用,要么是因为支持 有限,要么是因为它们既不能影响链接,也不能影响构建。 cmake_parse_arguments — CMake 3.30.3 Documentation 书籍推荐CMake Best Practices (豆瓣) (douban.com)和Professional CMake (豆瓣) (douban.com) 欢迎关注我的其它发布渠道1
2
3
4
5
6
7
8install(TARGETS ex05_lib
EXPORT cex05_lib_export
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install (
DIRECTORY ${PROJECT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)install(EXPORT)
得到xxConfig.cmake文件1
2
3
4
5install(EXPORT ex05_lib_export
FILE ex05_lib-config.cmake
NAMESPACE ex05_lib::
DESTINATION ${project_INSTALL_CMAKEDIR}
)1
2
3
4
5
6
7
8
9
10
11
12include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"ex05_lib-config-version.cmake"
# Package compatibility strategy. SameMajorVersion is
essentially 'semantic versioning'.
COMPATIBILITY SameMajorVersion # 与主版本号相同即可
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/ex05_lib-config-version.
cmake"
DESTINATION "${project_INSTALL_CMAKEDIR}"
)1
2
3cmake –S . -B ./build
cmake --build ./build
cmake --install ./build --prefix /3rdpartyfind_package
使用包1
2
3
4
5
6
7
8
9
10if(NOT PROJECT_IS_TOP_LEVEL)
message(FATAL_ERROR "The chapter-4, ex05_consumer project is
intended to be a standalone, top-level project. Do not
include this directory.")
endif()
find_package(ex05_lib 1 CONFIG REQUIRED)
add_executable(ex05_consumer src/main.cpp)
target_compile_features(ex05_consumer PRIVATE cxx_std_11)
target_link_libraries(ex05_consumer ex05_lib::ch4_ex05_
lib)CPack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18cmake_minimum_required(VERSION 3.21)
project(
ch4_ex06_pack
VERSION 1.0
DESCRIPTION "Chapter 4 Example 06, Packaging with CPack"
LANGUAGES CXX)
if(NOT PROJECT_IS_TOP_LEVEL)
message(FATAL_ERROR "The chapter-4, ex06_pack project is
intended to be a standalone, top-level project.
Do not include this directory.")
endif()
add_subdirectory(executable)
add_subdirectory(library)
set(CPACK_PACKAGE_VENDOR "CTT Authors") # 作者
set(CPACK_GENERATOR "DEB;RPM;TBZ2") # 包管理器
set(CPACK_THREADS 0)
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "CTT Authors")
include(CPack)1
cmake –S . -B build/
cpack
得到最终包1
2cmake --build build/
cpack --config build/CPackConfig.cmake -B build/经常遗忘的指令
1
2
3
4
5
6
7
8
9
10add_custom_target(Name [ALL] [command1 [args1...]]
[COMMAND command2 [args2...] ...]
[DEPENDS depend depend depend ... ]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[JOB_POOL job_pool]
[VERBATIM] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS]
[SOURCES src1 [src2...]])1
2
3
4
5
6
7
8
9add_custom_command(TARGET <target>
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS])1
2
3
4
5cmake_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>)Great resoureces for learning