CMake介绍和简要说明
编译系统的对比和说明1
手写MakeFile: 适合于小型项目编译。当项目中模块众多并且之间依赖关系复杂时,维护Makefile非常困难。
Modern CMake: 开源社区非常流行的build system generator,通过执行CMakeLists生成makefile/ninja等,构建C++项目。尤其是2013年以来Modern CMake的发展,理念是:一切皆为target or target property。Modern CMake通过target之间的依赖关系,实现target属性(头文件查找路径、编译选项、链接依赖)内部隐藏或者自动传递,可以非常方便地处理大型C++项目的复杂依赖关系。
相比于makefile,Modern CMake大大解放了C/C++研发人员的生产力。
Bazel (and Blade): Bazel是Google开源的编译构建工具,以Monolithic Repository为理念。与makefile & CMake不同,Bazel另起炉灶,采用client/server运行模式,为云编译而生。Bazel工具将编译过程分三个阶段:Load Phase/Analysis Phase/Execution phase。研发人员实现workspace/build/.bzl三种文件,Bazel执行这些文件生成action graph,执行action来构建项目。
2022 CMake版本选择2
What minimum to choose - OS support:
- 3.4: The bare minimum. Never set less.
- 3.7: Debian old-stable.
- 3.10: Ubuntu 18.04.
- 3.11: CentOS 8 (use EPEL or AppSteams, though)
- 3.13: Debian stable.
- 3.16: Ubuntu 20.04.
- 3.19: First to support Apple Silicon.
- latest: pip/conda-forge/homebew/chocolaty, etc.
What minimum to choose - Features:
- 3.8: C++ meta features, CUDA, lots more
- 3.11:
IMPORTED INTERFACE
setting, faster, FetchContent,COMPILE_LANGUAGE
in IDEs - 3.12: C++20,
cmake --build build -j N
,SHELL:
, FindPython - 3.14/3.15: CLI, FindPython updates
- 3.16: Unity builds / precompiled headers, CUDA meta features
- 3.17/3.18: Lots more CUDA, metaprogramming
CMake文件编写基础
Minimum Version
1 | cmake_minimum_required(VERSION 3.1) |
这条命令几乎是所有CMakeLists.txt
的第一行命令,它设置项目需要的最小
cmake 版本,因为较新的版本会有老版本没有的命令,如果 cmake
版本号小于该命令指定的版本,cmake
会报错。另外在后面的视频里会提到版本 2.8.12 之后的 cmake 为
modern cmake,更加注重 modular
design,因此项目使用的版本不应低于 2.8.12。
Tips: 在CMake
3.12版本以后,这个命令支持VERSION 3.1...3.15
这样的表达,相关细节内容可以参考3。
Setting a Project
1 | project(MyProject VERSION 1.0 |
在这里project
名称必须是第一个变量。后面的VERSION
,
DESCRIPTION
, LANGUAGES
等等属性都是可选的选项4。
Making an executable
1 | add_executable(one two.cpp three.h) |
one
既是生成的可执行文件的名称,也是创建的
CMake
目标(target)的名称。
紧接着的是源文件的列表,你想列多少个都可以。CMake 很聪明 ,它根据拓展名只编译源文件。在大多数情况下,头文件将会被忽略;列出他们的唯一原因是为了让他们在 IDE 中被展示出来,目标文件在许多 IDE 中被显示为文件夹。
Making a Library
1 | add_library(one STATIC two.cpp three.h) |
通过add_library
命令你可以制作一个库,同时你可以选择库的类型,可以是
STATIC
,SHARED
,
或者MODULE
.如果你不选择它,CMake 将会通过
BUILD_SHARED_LIBS
的值来选择构建 STATIC 还是 SHARED
类型的库。
在下面的章节中你将会看到,你经常需要生成一个虚构的目标,也就是说,一个不需要编译的目标。例如,只有一个头文件的库。这被叫做
INTERFACE
库,这是另一种选择,和上面唯一的区别是后面不能有文件名。
Definition of Targets
1 | target_include_directories(one PUBLIC include) |
target_include_directories
为目标添加了一个目录。 PUBLIC
对于一个二进制目标没有什么含义;但对于库来说,它让 CMake
知道,任何链接到这个目标的目标也必须包含这个目录。其他选项还有
PRIVATE
(只影响当前目标,不影响依赖),以及
INTERFACE
(只影响依赖)。
我们可以使用下面的命令,将another
库和上面的one
库链接起来:
1 | add_library(another STATIC another.cpp another.h) |
target_link_libraries
可能是 CMake 中最有用也最令人迷惑的命令。
- 如果
one
已经被定义过,那么target_link_libraries
这个命令将会把one
的依赖添加到another
上。 - 如果
one
没有被定义过,那么target_link_libraries
这个命令将会从你的path
中找到one
这个library。 - 或者你可以在这里给定
one
的绝对路径。
虽然经典的CMake允许缺省关键字PUBLIC
,
但是缺省与非缺省混用,CMake将会报错。所以推荐全局使用targets
和关键字。
综合示例
1 | # cmake版本要求定义 |
如何调试CMakeLists文件
如果只是需要打印单一的变量,那么可以参考如下两种方法
使用message命令
1
message(STATUS "MY_VARIABLE=${MY_VARIABLE}")
使用CMake内置的模组CMakePrintHelpers
1
2include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)
如果需要打印某些目标的变量,那么使用CMakePrintHelpers的方法就是比较方便的选择了
1 | cmake_print_properties(TARGETS foo bar PROPERTIES |
上面的命令将会打印foo
,
bar
的LOCATION
和INTERFACE_INCLUDE_DIRS
属性。
CMake学习参考资料
相关的学习网站
An Introduction to Modern CMake · Modern CMake:这是一份非常不错的CMake教程,很多基础的概念可以从这个说明里面建立起来。
CLIUtils / Modern CMake · GitLab:上面网站的Gitlab维护工程.
Introduction · Modern CMake: 上面网站的中文翻译版本
Introduction – More Modern CMake:这也是非常不错的CMake教程,上面的教程有参考其中的内容。
Effective Modern CMake:倡议的CMake编写规范。
CMake Tutorial — CMake 3.25.0-rc1 Documentation: CMake官方教程和Demo程序5
CMake/Help/guide/tutorial at master · Kitware/CMake (github.com):Demo程序源码
cmake-examples-Chinese: 一个相当不错的CMake学习文档仓库,其源头cmake-examples
相关的学习视频
- Daniel Pfeifer “Effective CMake”
- Using Modern CMake Patterns to Enforce a Good Modular Design
- Git, CMake, Conan - How to ship and reuse our C++ projects