CMake 官网: https://cmake.org/
说明: CMake 是一种描述构造过程的高层次语言,可以通过不同的生成器,转译成原生构造工具自己的语言。
- 简化构造系统,跨平台
- CMake 有几种生成器,分别运行于不同的构造机器类型
- CMake 的构造描述使用平台无关语言,保存在 CMakeLits.txt 文件中
安装 CMake: brew install cmake
1 CMake 编程语言
1.1 CMake 语言基础
命令
标准语法: command (arg1 arg2 …)
几个常用命令 | 说明 |
---|---|
project ) |
为构造系统命名,供需要项目名的原生构造系统(例如 Eclipse) 使用。 |
set |
设置变量 |
set_property |
为指定文件设置属性 |
get_property |
获取指定文件的属性名 |
1 | # |
1.2 构造可执行程序和程序库
(1)创建可执行程序和程序库
相关命令 | 说明 |
---|---|
add_executable |
创建可执行程序(生成合适的编译器命令行,把文件名加到依赖关系中) |
add_library |
编译源文件,归档为程序库 |
target_link_libraries |
链接可执行程序和程序库 |
include_directories |
指定在哪些额外路径搜索头文件 |
link_directories |
指定在哪些路径中搜索程序库 |
CMakwLists.txt: 情形1,不需要链接程序库的可执行程序
1 | # 将 add.c sub.c mult.c calc.c 编译病链接为可执行程序 |
CMakwLists.txt: 情形2,需要链接程序库的情况
1 | # 创建静态库 |
(2)设置编译标志参数
说明: CMake 不鼓励对编译器标志参数进行硬编码,而是通过构造描述来声明需要哪一种类型的输出,并由 CMake 判断采用什么编译器标志参数。
1 | # 设置编译过程为调试模式(在 UNIX 系统中,上例生成的结果是把 -g 标志参数加到 C 编译器命令行)。 |
(3)增加定制命令和标的
说明: 对于更复杂的构造需求,可以定义新的编译工具,让 CMke 把这些工具加到原生构造系统中。
add_custom_command
功能: 类似 GNU Make 的标准规则。
1 | # ----------------------------------------------- |
add_custom_target
功能: 类似 GNU Make 的伪标的。
1 | project(custom_target) |
1.3 控制流
(1)标准语法
注意: end 和 endif 语句后也需要加 ()
1 | set(my_var 1) |
(2)布尔操作
布尔操作关键字 | 说明 |
---|---|
AND |
与 |
OR |
或 |
NOT |
非 |
EQUAL |
判等 |
EXITS |
检查文件是否存在 |
IS_NEWER_THAN |
检查文件之间的新旧程度 |
MATCH |
与正则表达式进行匹配 |
1 | # NOT |
(3)宏结构
说明: 类似函数。
1 | project(macro) |
(4)foreach 循环
注意: 循环结构不会转译为原生构造工具的循环结构,而是通过向 makefile 中加入若干不容的规则,从而提供等价的功能。
1 | project(foreach) |
1.4 跨平台支持
说明: CMake 具备跨平台的能力,但并不总是如此,需要我们在编写时考虑构造机器的差异。
跨平台支持:CMake 让我们可以找到特定的工具和文件,以及确定地层编译器支持哪些特性。
(1)在构造机器上查找文件和工具的位置
说明: CMake 提供了在标准路径中搜索文件和工具的若干命令。同时也提供了用来查找流行工具和程序库的代码模块,只要引入模块,就自动完成相关库和命令的定位,并记录在相应的变量中。
搜索文件或工具的命令 | 说明 |
---|---|
find_program |
获取指定程序的路径,并记录在指定变量中 |
find_file |
获取指定文件的完整路径,并记录在指定变量中 |
find_library |
获取指定程序库的完整路径,并记录在指定变量中 |
include |
载入并运行 cmake 模块 |
CMakeLists.txt: 使用命令寻找
1 | project(finding) |
CMakeLists.txt:利用 CMakeLists 封装的模块简化查找
1 | project(find-perl) |
(2)对源代码进行测试的能力
说明: 在编译程序之前,必须判断构造机器的编译器是否提供了所有必要功能和头文件。如果不满足需要的话,就必须用自己的实现版本来进行替换,或甚至放弃构造过程。
对底层编译器进行测试的命令 | 说明 |
---|---|
try_compile |
测试构造系统能否对程序源码进行正确编译 |
try_run |
测试构造系统构造的程序能够正确运行 |
CMakeLists.txt
1 | project(try-compile) |
1.5 生成原生构造系统
说明: 使用 CMake 完成编译划分为两个阶段,第一个阶段是 CMake 设计的关键部分。
注意: CMake 在生成原生构造系统时增加了许多标准特性,例如自动化依赖关系分析等。
(1) 生成默认的构造系统
说明: 生成系统的最简单办法,是接受默认配置参数。
原理: 开发人员只需创建目标文件目录,然后在该目录调用 cmake 工具即可。
特点: 这一操作不会修改源文件目录中任何文件,从而可以根据同一源树,生成多个目标文件目录。
1 | $ cd path/to/project |
(2)生成非默认的构造系统
原理: 通过向 cmake 命令传递 -G 选项,即可覆盖默认选择值。
相关 CMake 选项 | 说明 |
---|---|
-G | 指定要生成哪种原生构造系统 |
1 | # 在 windows 系统的构造机器上输入以下命令 |
(3)对生成步骤进行定制调整
缓存变量: 决定如何生成原生构造系统的一系列关键变量,称为 缓存(cache)
变量。
说明: 通过提供相关缓存变量值覆盖其默认值,就可以对编译过程进行定制调整。具体地,可以有 3 种方式
- 在 CMakeLists.txt 中,使用 set 命令设置缓存变量(需要使用 CACHE 关键字)。
- 在 cmake 命令后面,提供相关缓存变量值。
- 使用 ccmake 命令取代 cmake,交互式地配置原生构造系统。配置的缓存变量值最中保存在目标文件目录下的 CMakeList.txt 文件中,把它们当作正常的变量来访问(推荐)。
常用缓存变量 | 说明 |
---|---|
CMAKE_AR |
程序库归档工具的绝对路径 |
CMAKE_C_COMPILER |
C 编译器的绝对路径 |
CMAKE_LINKER |
目标文件链接器的绝对路径 |
CMAKE_MAKE_PROGRAME |
原生构造工具的绝对路径 |
CMAKE_BUILD_TYPE |
待创建的构造树的类型,包括 Debug、Release、RelWithDebInfo、MinSizeRel |
CMAKE_C_FLAGS_* |
根据 CMAKE__BUILD_TYPE 变量的取值, CMake 将使用相应缓存变量(CMAK_CFLAGS*)中列出的 C 编译器标志参数 |
(4)从 CMakeLists.txt 转译到原生构造系统
第一步:编写 CMakeLists.txt
项目目录1
2
3
4
5
6
7
8
9.
└── src
├── CMakeLists.txt # CMake 描述文件
├── add.c
├── calc.c
├── mult.c
├── numbers.h
└── sub.c
1 directory, 6 files
CMakeLists.txt
1 | # ----------------------- |
第二步:转译为原生构造系统(GNU Make)
1 | $ cmake src |
原生构造系统的目录结构
1 | . |
第三步:完成构造任务
1 | $ make |
1.6 其他有趣的特性以及进一步阅读资料
CMake 的其它有趣特性
- 字符串操作
- 列表操作
- 文件操作
- 数学表达式
- 配置数据文件
- 测试可执行程序(CTest 模块)
- 打包与安装
- 平台无关的 shell 命令
进一步阅读资料
2 显示世界的构造系统场景
(1) 源代码放在单个目录中
例如, laputa-er
1 | . |
CMakeList.txt
1 | # ----------------------- |
(2)源代码放在多个目录中
例如, lapuya-er
第一步:编写各个目录下的 CMakeList.txt
新命令 | 说明 |
---|---|
add_subdirectory |
添加字目录,从而让 CMake 了解有哪些字目录 |
1 | . |
src/CMakeLists.txt
1 | # ----------------------------------- |
src/calc/CMakeLists.txt
1 | # 创建可执行文件 |
src/libmath/CMakeLists.txt
1 | # 生成静态库 Math |
src/libprint/CMakeLists.txt
1 | # 生成静态库 Print |
第二步:生成 GNU Make 构造系统
1 | $ cmake src |
– The C compiler identification is AppleClang 8.0.0.8000042
– The CXX compiler identification is AppleClang 8.0.0.8000042
– Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
– Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc – works
– Detecting C compiler ABI info
– Detecting C compiler ABI info - done
– Detecting C compile features
– Detecting C compile features - done
– Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
– Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ – works
– Detecting CXX compiler ABI info
– Detecting CXX compiler ABI info - done
– Detecting CXX compile features
– Detecting CXX compile features - done
– Configuring done
– Generating done
– Build files have been written to: /Users/tonyearth/Projects/book_software_build_system_demos/part2/09_cmake/0902_mult_dir
第三步:完成构造过程
1 | $ make |
Scanning dependencies of target Math
[ 10%] Building C object libmath/CMakeFiles/Math.dir/clock.c.o
[ 20%] Building C object libmath/CMakeFiles/Math.dir/letter.c.o
[ 30%] Building C object libmath/CMakeFiles/Math.dir/number.c.o
[ 40%] Linking C static library libMath.a
[ 40%] Built target Math
Scanning dependencies of target Print
[ 50%] Building C object libprint/CMakeFiles/Print.dir/banner.c.o
[ 60%] Building C object libprint/CMakeFiles/Print.dir/center.c.o
[ 70%] Building C object libprint/CMakeFiles/Print.dir/normal.c.o
[ 80%] Linking C static library libPrint.a
[ 80%] Built target Print
Scanning dependencies of target calculator
[ 90%] Building C object calc/CMakeFiles/calculator.dir/calc.c.o
[100%] Linking C executable calculator
[100%] Built target calculator
(3)定义新的编译工具
新命令 | 说明 |
---|---|
separate_arguments |
将字符串中的空格替换为分号 |
假想的 mathcomp 编译器功能如下,
需要生成的原生构造系的统构造需求如下,
第一步:编写 CMakeLists.txt
扩展: 如果使用了 add_custom_command
命令, C/C++ 可以通过 IMPLICIT_DEPENDS
选项实现依赖关系图的自动更新。
1 | . |
CMakeLists.txt
- .c 文件编译过程会加入自动依赖分析功能
- .math 文件编译过程的依赖信息是和原生构造系统本身一起生成的,因此这部分依赖关系图不会自动更新
1 | # ----------------------------------------- |
第二步:生成 GNU Make 构造系统
1 | $ cmake src |
第三步:完成构造
1 | $ make |
(4)针对多个变量进行构造
原理: 利用配置缓存来实现。比如,在配置缓存时,用户可以指出要针对哪种类型的 CPU 进行编译。
配置缓存: 配置缓存有两种实现方式
- set 命令配合 CACHE 关键字。
- 使用 ccmake,它提供更交互式的用户体验。
例如, book_software_build_system_demos/part2/09_cmake/0902_mult_var at master · laputa-er/book_software_build_system_demos · GitHub,该例使用 set 命令来配置缓存。
第一步:编写 CMakeLists.txt
1 | . |
CMakeLists.txt
1 | SRCS := add.c calc.c mult.c sub.c |
第二步:生成 GNU Make 构造系统
1 | $ mkdir obj-alpha && cd obj-alpha # 针对 alpha 的构造系统生成到这个文件夹 |
1 | $ cmake -D PLATFORM=alpha ../src # 创建专门针对 alpha 的构造系统 |
cmake -D PLATFORM=alpha ../src
– The C compiler identification is AppleClang 8.0.0.8000042
– The CXX compiler identification is AppleClang 8.0.0.8000042
– Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
– Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc – works
– Detecting C compiler ABI info
– Detecting C compiler ABI info - done
– Detecting C compile features
– Detecting C compile features - done
– Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
– Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ – works
– Detecting CXX compiler ABI info
– Detecting CXX compiler ABI info - done
– Detecting CXX compile features
– Detecting CXX compile features - done
Compiling code for platform alpha
– Configuring done
– Generating done
– Build files have been written to: /Users/tonyearth/Projects/book_software_build_system_demos/part2/09_cmake/0902_mult_var/obj-alpha
1 | $ tree |
.
├── obj-alpha
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── Makefile
│ └── cmake_install.cmake
└── src
├── CMakeLists.txt
├── add.c
├── calc.c
├── mult.c
├── numbers.h
└── sub.c
第三步:完成构造
1 | $ make |
(5)清除构造树
说明: CMake 创建的原生构造系统默认已经支持了clean
标的,用于对它已知的各个目标文件进行清除。
原理: 如果在编译源文件时使用了 add_executable
或 add_library
命令,那么 CMake 就已经知道了可执行程序或程序库文件的名字,以及任何中间过度目标文件的名字。
技巧: 对于未能自动检测到的那些已生成文件,可以把它们的文件名列在 ADDITIONAL_MAKE_CLEAN_FILES
属性中。这个属性每个目录都有,其中包含该目录要删除的文件名清单。
(6)对不正确的构造系统进行测试
CMake 工具提供的调试功能
cmake 参数 | 说明 |
---|---|
—system-infomation |
对构造机器在执行 cmake 命令时的转储信息进行了深入展示。 |
—trace |
对 CMake 执行过程提供了逐行跟踪。 |
使用原生构造工具的调试特性
修复 CMake 工具本身可能的错误
3 赞扬与批评
3.1 赞扬
- 跨平台。
- 简单易用。
- 质量高。
- 集成了 CPack(用于打包和安装)和 CTest(用于对覆盖全过程的完整构造系统进行测试)。
- 构造描述语言是内置的,不需要额外安装语言解释器。
3.2 批评
- 没有提供预期的完整能力。
- 引入了另一种语言,增加了学习成本。
- 文档支持差。
- 某些情况下跨平台能力有限。
3.3 评价
C/C++ 项目的最佳选择。
- 易用性: 良
- 正确性:优
- 性能:优
- 可伸缩性:优
4 其它类似工具
4.1 Automake
说明: Autotools 套装软件的组成部分之一。
适用平台: UNIX 类系统。
建议: 一般认为 CMake 可以去取代 Autotools 套装软件,特别是那些运行在非 UNIX 系统的软件。
举个例子来说明其用法,
第一步:创建 Makefile.am 文件
1 | # 用户通过 make install 安装时, calculator 程序应当安装到 bin 默认目录 |
第二步:生成 Makefile
1 | $ automake |
第三步:编译 && 安装
1 | $ make |
4.2 Qmake
说明: Qt 开发环境的组成部分之一,专用于 Qt 开发人员,因此在生成的构造系统中,将自动包含 Qt 类应用程序所必要的 C/C++ 头文件目录和程序库。
建议: 如果要开发跨平台应用,Qt 和 Qmake 都是值得研究的工具。