cmake笔记

记录一个CMake速查表。

运行 cmake

1
2
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j8

常用函数

函数名 例子 用途
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
2
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

设置构建的类型

有四种构建的类型:

  • Debug 调试模式,完全不优化,生成调试信息,方便调试程序
  • Release 发布模式,优化程度最高,性能最佳,但是编译比 Debug
  • MinSizeRel 最小体积发布,生成的文件比 Release 更小,不完全优化,减少二进制体积
  • RelWithDebInfo 带调试信息发布,生成的文件比 Release 更大,因为带有调试的符号信息

可以检查是否设置了构建类型,没有就可以设置成默认的:

1
2
3
4
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

在代码中可以使用NDEBUG也就是"NO DEBUG"来判断是否是非调试的构建类型,比如用

1
2
3
#ifndef NDEBUG
std::cerr << phyDevice.getProperties().deviceName << std::endl;
#endif
就可以仅在构建类型为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中,这个语句会影响对应目标的属性页的附加包含目录。 1678123205595

链接子库目录

1
target_link_libraries(VulkanToy PUBLIC "${PROJECT_BINARY_DIR}")

在vs中,这个语句会影响对应目标的属性页的附加依赖项。 1678123306803

作用域关键字

当存在多个共享库而且存在依赖关系时,作用域关键字可以用于指定依赖关系。

链接的依赖关系

这里以两个库 foobar,目标 app为例,两个库有对应名字的头文件和源文件。假设 appCMakeLists.txt中有以下语句:

1
2
target_link_libraries(app bar)
target_link_libraries(bar PUBLIC foo)

这里的 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_directoriestarget_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
2
#define VulkanToy_VERSION_MAJOR @VulkanToy_VERSION_MAJOR@
#define VulkanToy_VERSION_MINOR @VulkanToy_VERSION_MINOR@

CMakeLists.txt中加入下面这行:

1
configure_file(VulkanToyConfig.h.in VulkanToyConfig.h)

这行代码会将第一个参数复制到第二个参数这个文件中,这个文件 VulkanToyConfig.h不存在时,cmake会在运行的所在目录生成一个,比如我是在 Build目录下。他可以被包含到当前路径下的源文件里,这样就可以使用定义的宏等。

选项

一个例子是可以决定是否包含某些子目录,为真时加入该子目录(作为一个库,该目录下要有自己的 CMakeLists.txt,会被独立构建)。这个选项会在生成项目时出现,如图:

1678040567107
1
2
3
4
5
option(USE_VULKAN_UTILS "use custom vulkan utilities" ON)
if(USE_VULKAN_UTILS)
add_subdirectory(VulkanUtils)
list(APPEND EXTRA_LIBS VulkanUtils)
endif()

list可以修改变量中的内容,在这里用来保存需要额外链接的库

在配置文件 .h.in(本文例子中是 VulkanToyConfig.h.in)加入下面的内容。

1
#cmakedefine USE_VULKAN_UTILS

安装

在文件尾添加:

1
2
install(TARGETS VulkanToy DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/VulkanToyConfig.h" DESTINATION include)

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
2
3
enable_testing()
add_test(NAME VulkanToyRuns COMMAND VulkanToy)
set_tests_properties(VulkanToyRuns PROPERTIES PASS_REGULAR_EXPRESSION ".*")

Build目录运行命令 ctest,如果出现 Test not available without configuration. (Missing "-C <config>"?)这样的错误,就可以用 -C指定配置:

1
2
cmake ..
ctest -C DEBUG

自定义命令

add_custom_command有两种,一种用于生成输出文件,一种在生成目标文件时自动执行。

参考

【公开课】现代CMake高级教程(持续更新中) 非常推荐观看。 cmake:添加自定义操作 CMake应用:生成器表达式

作者

Gehan Zheng

发布于

2023-03-04

更新于

2023-12-20

许可协议

评论