CMake 构建工具介绍
概述
CMake 是跨平台的编译、构建工具,它主要被设计为各种Makefile方言的生成器。CMake 可以生成各种编译系统,例如 Ninja,也可以生成各种 IDE 的工程文件,例如 Visual Studio 和 Xcode。
Cmake 被广泛用于 C/C++ 语言,但也可以构建使用其他语言的源代码。
参考 CMake - Documentation 文档。
参考 Mastering CMake: A Cross-Platform Build System By Ken Martin & Bill Hoffman
为什么使用 CMake
MakeFile 的问题
- 对于比较复杂庞大的软件系统,手写 MakeFile 过于复杂繁琐,维护困难。
- 与平台环境耦合性强,跨平台性不足
为什么不使用 Autoconf
为什么不使用 JAM,qmake,SCons 或者 ANT
为什么不使用脚本
CMake 的特性
使用 CMake 与直接编写 Makefile 相比到底有哪些优势呢?
CMake 是 Makefile 生成器,它包含了大部分关于 Makefile 的知识,而直接掌握这些知识需要一段时间(可能很长)经验的积累。
- 跨平台性:同一个源代码工程可以构建到不同的平台,从 Android,iOS 到高性能计算平台(包括构建、打包、安装和测试)。
- 高效率:CMake 可以描述复杂工程各子模块的精确依赖关系(支持并行构建),并进行最小化构建。
- 外部构建(out-of-source build):在单独的构建目录中存放临时文件,不影响源代码目录。
- 以目标(target)为中心的方法论:
- 支持多种后端(生成器):CMake 支持生成多种构建工具脚本,包括 Visual Studio,Xcode,ninja,make 和 VSCode。
- 自定义命令:可以自定义命令去生成文件,或者完成用户自定义的任务。
- 可复用性强: CMake可以从构建中导出和导入目标,以允许重用软件。
- 组件化/模块化:
- 支持多种构建模式
CMake 构建系统
基于 CMake 的构建系统由一系列高级逻辑目标(target)构成的,这些目标对应于可执行目标文件、库文件或者包含自定义命令的自定义目标(custom target)。各个目标间的依赖关系会在构建系统中指明,并依此来决定构建顺序和重新构建的规则。
二进制目标
| 类型 | 命令 | 示例 |
|---|---|---|
| 库 | add_library() | add_library(mylib mylib.cxx) |
| 可执行文件 | add_executable() | add_executable(demo2 demo2.c) |
| 自定义目标 | add_custom_command() | add_custom_command(OUTPUT demo2.h COMMAND demo2 demo2.h DEPENDS demo2) |
注意:
- add_library() 支持生成多种类型的“库”,如
SHARED、STATIC、MODULE、OBJECT。具体用法参考官方文档。
将库链接到可执行文件:target_link_libraries(),例如:
1 | target_link_libraries(demo2 PRIVATE mylib) |
构建规范和使用要求
以库目标为例,其构建时需要指定一些规范:头文件路径、编译选项和宏定义等;当其他目标(consumers)使用(如链接)该库时,则必须满足使用要求,这些要求可能是必须包含某个头文件或者必须打开某个编译选项。
相关命令表:
| 命令 | 描述 | 示例 |
|---|---|---|
| target_include_directories() | 可认为等价于GCC的 -I 选项 |
None |
| target_compile_definitions() | 可认为等价于GCC的 -D 选项 |
None |
| set_property() | None | None |
命令模式:
PUBLIC:PRIVATE:INTERFACE:
1 | add_library(archive SHARED archive.cpp) |
使用要求的传播(Transitive Usage Requirements):
target_link_libraries()命令的不同模式可以控制某个库的使用要求是否可以沿依赖链传递。
使用生成器表达式(Generator Expressions): 有些构建规范只能在generate-time获得或者需要有条件地获得。
1 | add_library(lib1Version2 SHARED lib1_v2.cpp) |
使用 find_package 命令是非常便利的,它会生成对应依赖包的编译选项,例如头文件路径。经常使用的变量如下:
<package>_<lang>_INCLUDE_DIRS: 头文件路径<package>_<lang>_LIBRARIES: 共享库路径<package>_<lang>_VERSION:
可以参考 FindMPI - Cmake 作为一个例子。
其他命令
| 名字 | 描述 |
|---|---|
| set | |
| option (ccmake) | 如果需要在源代码文件中引用该选项,a: 在 CMakeLists.txt 中 使用 configure_file;b: 使用 target_compile_options。 |
| list | |
| target_compile_options() | |
| set_target_properties | |
| configure_file | |
| add_subdirectory | |
| install |
条件语句
自定义函数
输出编译信息
make VERBOSE=1
测试
集成 CTest
enable_testing
add_test
set_tests_properties
打包
集成 CPack
常用构建变量
| Variable | 描述 |
|---|---|
| CMAKE_PREFIX_PATH | 依赖包路径 |
| CMAKE_MODULE_PATH | CMake扩展模块所在路径 |
| CMAKE_INSTALL_PREFIX | 当执行install 目标时的安装路径 |
| CMAKE_BUILD_TYPE | 构建类型,例如Debug或Release |
| BUILD_SHARED_LIBS | 指定add_library()生成静态库还是动态库 |
上述变量可以通过option()或者 ccmake工具设置,或者通过命令行设置:
1 | cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Debug |
命令行参数
- 打印变量列表
1
cmake --help-variable-list
- 打印命令列表
1
cmake --help-command-list
模板
1 | cmake_minimum_required(VERSION 3.5.1) |