CMake 构建工具介绍

概述

   CMake 是跨平台的编译、构建工具,它主要被设计为各种Makefile方言的生成器。CMake 可以生成各种编译系统,例如 Ninja,也可以生成各种 IDE 的工程文件,例如 Visual StudioXcode
   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() 支持生成多种类型的“库”,如 SHAREDSTATICMODULEOBJECT。具体用法参考官方文档

将库链接到可执行文件: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
2
3
4
5
6
7
8
9
10
11
add_library(archive SHARED archive.cpp)

# 只有 archive 源文件在编译时带有 -DBUILDING_WITH_LZMA
target_compile_definitions(archive PRIVATE BUILDING_WITH_LZMA)

# archive 源文件在编译时不会带有 -DUSING_ARCHIVE_LIB,而对于链接archive库的目标
#,其源文件编译时会带有 -DUSING_ARCHIVE_LIB,例如下面的 consumer 目标。
target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)

add_executable(consumer)
target_link_libraries(consumer archive)

使用要求的传播(Transitive Usage Requirements):

  • target_link_libraries()命令的不同模式可以控制某个库的使用要求是否可以沿依赖链传递。

使用生成器表达式(Generator Expressions): 有些构建规范只能在generate-time获得或者需要有条件地获得。

1
2
3
4
5
6
7
8
9
10
11
12
add_library(lib1Version2 SHARED lib1_v2.cpp)
set_property(TARGET lib1Version2 PROPERTY
INTERFACE_CONTAINER_SIZE_REQUIRED 200)
set_property(TARGET lib1Version2 APPEND PROPERTY
COMPATIBLE_INTERFACE_NUMBER_MAX CONTAINER_SIZE_REQUIRED
)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1Version2)
target_compile_definitions(exe1 PRIVATE
CONTAINER_SIZE=$<TARGET_PROPERTY:CONTAINER_SIZE_REQUIRED>
)

使用 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 构建类型,例如DebugRelease
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
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.5.1)
project(myproject VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

include_directories()
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries()