cmake笔记
记录一个CMake速查表。
运行 cmake:
1 | cmake -B build -DCMAKE_BUILD_TYPE=Release |
常用函数
| 函数名 | 例子 | 用途 |
|---|---|---|
| set | set(CMAKE_CXX_STANDARD 11) | 为变量赋值 |
| option | option(USE_VULKAN_UTILS "use custom vulkan utilities" ON) | 选项(和set功能相同) |
| include | include(Ctest) | 包含cmake文件 |
| cmake_minimum_required | cmake_minimum_required(VERSION 3.10) | cmake版本指定 |
| project | project(VulkanToy VERSION 1.0) | 项目名称,可以包括版本 |
| add_executable | add_executable(VulkanToy main.cpp) | 添加一个要生成可执行文件的目标 |
| add_library | add_library(VulkanUtils utils.cpp) | 添加一个要生成库的目标 |
| add_subdirectory | add_subdirectory(VulkanUtils) | 添加子目录 |
| find_package | find_package(Vulkan REQUIRED) | 查找包 |
| aux_source_directory | aux_source_directory(. sources) | 自动搜集有需要的后缀名的文件放入 sources变量 |
| target_include_directories | target_include_directories(test PRIVATE VulkanUtils) | 包含子库 |
| target_link_libraries | target_link_libraries(VulkanToy utilTest) | 链接子库目录 |
| target_add_definitions | target_add_definitions(VulkanToy PUBLIC MY_MACRO=1) | 添加一个宏定义 |
| target_compile_options | target_compile_options(VulkanToy PUBLIC -fopenmp) | 添加编译器命令行选项 |
| target_sources | target_sources(VulkanToy PUBLIC test.cpp other.cpp) | 添加要编译的源文件 |
| include_directories | include_directories(/opt/cuda/include) | 添加头文件搜索目录 |
| link_directories | link_directories(/opt/cuda) | 添加库文件的搜索路径 |
| add_definitions | add_definitions(MY_MACRO=1) | 添加一个宏定义 |
| add_compile_options | add_compile_options(-fopenmp) | 添加编译器命令行选项 |
| configure_file | configure_file(VulkanToyConfig.h.in VulkanToyConfig.h) | 配置文件 |
| install | install(TARGETS VulkanToy DESTINATION bin) | 安装 |
| enable_testing | enable_testing() | 启动测试功能 |
| add_test | add_test(NAME VulkanToyRuns COMMAND VulkanToy) | 添加一个测试 |
| set_tests_properties | set_tests_properties(VulkanToyRuns PROPERTIES PASS_REGULAR_EXPRESSION ".*") | 设置测试的属性(比如结果是否满足条件 |
| add_custom_command | add_custom_command(TARGET${target_name} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/vert.spv $TARGET_FILE_DIR:${target_name}(TARGET_FILE_DIR:${target_name})) | 自定义命令 |
部分关键字
| 关键字 | 例子 | 用途 |
|---|---|---|
| GLOB | file(GLOB sources CONFIGURE_DEPENDS .cpp .h) | 将文件夹下的对应扩展名的所有文件都放入 sources中 |
| GLOB_RECURSE | file(GLOB_RECURSE sources CONFIGURE_DEPENDS .cpp .h) | 自动包含所有子文件夹下的文件 |
| CONFIGURE_DEPENDS | file(GLOB sources CONFIGURE_DEPENDS .cpp .h) | 当添加新文件时,自动更新变量 |
部分说明
指定C++标准
1 | set(CMAKE_CXX_STANDARD 11) |
设置构建的类型
有四种构建的类型:
Debug调试模式,完全不优化,生成调试信息,方便调试程序Release发布模式,优化程度最高,性能最佳,但是编译比Debug慢MinSizeRel最小体积发布,生成的文件比Release更小,不完全优化,减少二进制体积RelWithDebInfo带调试信息发布,生成的文件比Release更大,因为带有调试的符号信息
可以检查是否设置了构建类型,没有就可以设置成默认的:
1
2
3
4if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
在代码中可以使用NDEBUG也就是"NO
DEBUG"来判断是否是非调试的构建类型,比如用 1
2
3
std::cerr << phyDevice.getProperties().deviceName << std::endl;Debug的时候输出调试信息。 ###
添加子目录
子目录中包含自己的
CMakeLists.txt。添加子目录后子目录下的
CMakeLists.txt会被执行,整个项目编译时,这个子目录中的目标也会被编译(比如生成
.lib)。但是不加其他语句(target_link_libraries)的话这个目标与当前的目标没有任何联系。反过来,如果只是链接第三方的库不需要用
add_subdirectory,需要
target_link_libraries。
1 | add_subdirectory(VulkanUtils) |
包含子库
将某个(子)目录加入当前项目,使得在编译时,指定的目录下的头文件能够被访问到。
第一个参数是目标,第二个是作用域关键字,第三个是目录的标识符。如果要包含项目目录中的子目录,第三个参数可以是
"${PROJECT_SOURCE_DIR}/目录名称"。但是包含项目目录中的子目录可以有更好的做法:使用
add_library。
第三个参数可以使用相对路径,相对的当前路径时
CMakeLists.txt所在的路径。比如
CMakeLists.txt所在的文件夹中有一个子文件夹
VulkanUtils,如果 test目标想包含
VulkanUtils里的头文件,就可以直接写:
1 | target_include_directories(test PRIVATE VulkanUtils) |
在vs中,这个语句会影响对应目标的属性页的附加包含目录。 
链接子库目录
1 | target_link_libraries(VulkanToy PUBLIC "${PROJECT_BINARY_DIR}") |
在vs中,这个语句会影响对应目标的属性页的附加依赖项。 
作用域关键字
当存在多个共享库而且存在依赖关系时,作用域关键字可以用于指定依赖关系。
链接的依赖关系
这里以两个库 foo,bar,目标
app为例,两个库有对应名字的头文件和源文件。假设
app的 CMakeLists.txt中有以下语句:
1 | target_link_libraries(app bar) |
这里的 PUBLIC可以换成另外两个关键字,含义如下:
- PRIVATE: bar.h没有包含foo.h,只有bar.cpp包含了foo.h。此时,app没有包含foo.h,因此不能使用foo中定义的符号。换句话说,app只知道bar的存在,完全不知道foo的存在。
- INTERFACE: bar.h中包含了foo.h,但是bar.cpp并没有用到foo定义的符号。此时,app包含了foo.h,可以引用foo的符号。换句话说,bar只是作为一个接口、界面,把foo传递给了app。
- PUBLIC:
等于PRIVATE加INTERFACE,bar.h包含了foo.h,且bar.cpp引用了foo的符号。此时,app包含了foo.h,可以引用foo的符号。
对于
target_link_libraries(app bar),使用不同关键字并不会影响编译。
对于编译来说,关键字会影响编译时是否加入某些库(比如
.lib)。
包含的依赖关系
一般 target_include_directories和
target_link_libraries会使用相同的关键字,含义也是一样的。但是
target_link_libraries可以省略关键字,target_include_directories不可以。
总结来说,如果有 foo->bar->app这样的依赖关系,如果
bar.cpp使用了 foo.hpp里的符号,且
app.cpp里使用了 foo.hpp里的符号,那么就只能用
PUBLIC,如果只有前者,可以用
PRIVATE,如果只有后者,可以用
INTERFACE``。记住,INTERFACE意味着库的使用者需要,但库的设计者不需要。
测试中发现,如果只有后者,bar.hpp中并不能
#include "foo.hpp"(应该是因为如果可以,那么
foo.cpp也可以使用 foo.hpp的符号,就不满足
INTERFACE的要求了),只有 app.cpp可以。
配置文件
编写一个扩展名为 .h.in的文件(本文例子中是
VulkanToyConfig.h.in),里面写这样的内容,两个宏的前缀是项目名:
1 |
CMakeLists.txt中加入下面这行:
1 | configure_file(VulkanToyConfig.h.in VulkanToyConfig.h) |
这行代码会将第一个参数复制到第二个参数这个文件中,这个文件
VulkanToyConfig.h不存在时,cmake会在运行的所在目录生成一个,比如我是在
Build目录下。他可以被包含到当前路径下的源文件里,这样就可以使用定义的宏等。
选项
一个例子是可以决定是否包含某些子目录,为真时加入该子目录(作为一个库,该目录下要有自己的
CMakeLists.txt,会被独立构建)。这个选项会在生成项目时出现,如图:
1 | option(USE_VULKAN_UTILS "use custom vulkan utilities" ON) |
list可以修改变量中的内容,在这里用来保存需要额外链接的库
在配置文件 .h.in(本文例子中是
VulkanToyConfig.h.in)加入下面的内容。
1 |
安装
在文件尾添加:
1 | install(TARGETS VulkanToy DESTINATION bin) |
TARGETS指定安装的目标以及安装的形式,FILES会把项目中指定的文件拷贝到目标位置。如果有子目标的话,子目标里的
CMakeLists.txt里也需要添加类似的内容,但是
TARGETS的第三个参数要根据子目标的需求来写(换成
lib)。
加入安装语句后,使用install时默认在本机安装软件的目录下,比如
C:\Program Files (x86)。因此有时需要管理员权限。可以用
--prefix指定安装位置。
1 | cmake --install . --config Debug |
也可以用下面的命令来安装,可以指定debug或者release。但是不能用
--prefix。
1 | cmake --build . --target install --config Debug |
测试
1 | enable_testing() |
在 Build目录运行命令 ctest,如果出现
Test not available without configuration. (Missing "-C <config>"?)这样的错误,就可以用
-C指定配置:
1 | cmake .. |
自定义命令
add_custom_command有两种,一种用于生成输出文件,一种在生成目标文件时自动执行。
参考
【公开课】现代CMake高级教程(持续更新中) 非常推荐观看。 cmake:添加自定义操作 CMake应用:生成器表达式