现代 CMake 使用技巧

Blog
Author:
Dori ExtermanDori Exterman
Published On:
1月 17, 2021
Estimated reading time:
1 minute

简介

2020 年 8 月 31 日,是 CMake 诞生的 20 周年纪念日。CMake 风靡软件世界数载,至今仍然占据着大半市场。据估计,50% 以上 C++ 项目的构建系统都是 CMake。CMake 3.0 之后的版本,我们称为现代 CMake(类似于 C++ 11 之后的“现代” C++)。本文将讨论使用现代 CMake 的一些技巧。如需更深入了解 CMake 的历史,以及与 Mak e的比较,请查看文章 CMake 与 Make

1. 使用现代 CMake

使用现代 CMake 最重要的技巧:如果您的项目仍在使用低于 2. 6的 CMake版本,请花一些时间和精力升级为较新的版本。经验建议使用的 CMake 版本最好在编译器版本之后。

2. CMake 不仅限于 C++

CMake 支持的语言种类不断增加,如 CMake 3.8 增加了 C# 和 CUDA。此外,CMake 还支持 C/C++、java、Obj/C++、Swift 和 Fortran 等多种语言。

3. -std=C++11 标记已经过时

与其说这是建议,不如说是告诫。不要手动添加 -STD= C++ 11 到CMAKE_CXX_FLAGS,这是过时的做法。对于现代 CMake,请改用 CXX_STANDARD 和 CXX_STANDARD_REQUIRED 标记。

set(CMAKE_CXX_STANDARD 11)

set(CMAKE_CXX_STANDARD_REQUIRED True)

4.确保没有内部构建

可以通过在顶层 CMakelists.txt 文件显式禁止内部构建,避免内部构建中与构建相关的人工产品(Artifacts)污染源目录。你可以用:

if ( ${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR} )

message( FATAL_ERROR “In-source builds not allowed! Create a build directory and run CMake from there. ” )

endif()

5.使用文档化的选项指定源目录和二进制目录

在 CMake 中进行推荐的外部构建时,请使用有文档化的 -S 和 -B 标记来指定源代码和构建目录。旧版本使用的是未进行文档化的-H和-B标记。

CMake 3.13+ 支持:cmake -S . -B build -G “Visual Studio 16 2019” ,推荐使用。

对于较老的 CMake  版本:cmake -H. -Bbuild -G “MSYS Makefiles”,注意标记-B 和文件名称之间没有空格。

6.使用构建预设设置,便于使用

预设设置是使用文件指定 CMake 选项集合的一种方法。如果需要支持多个平台的复杂项目,最好提供(同时也方便后续使用)CMake 预设文件。预设可以保证不同编译器工具链和包的多个配置一致。预设文件是通过传递 -C 标记来加载和使用的,建议将其放在源目录的 cmake/presets 文件夹中。

7.将 Linter 和 Formatter 集成到 CMake

使用 linter 检查代码库,报告编译器遗漏的错误,始终是最佳选择。与程序员的 IDE 相比,在中心位置运行 linter 需要将 linting 集成到 CMake。针对 C/C++ 项目,CMake 从第3.7.2版开始支持 clang-tidy(一种静态代码分析框架)。警告将被视为 CI 生成中的错误,以检查技术债务。

代码格式有时就像是信仰,开发者各执己见,互不妥协。但是,保证格式一致始终是最妥当的做法,尤其需要多个开发人员共同协作时。在 C++ 项目中,clang 格式可以轻易地集成到 CMake。

8. 使用 CMake 将测试集成到构建

无论你决定使用 CTest 还是 Google 测试框架,都需要确保测试是作为持续集成进程的一部分。或者可以作为构建的后期步骤(POST_BUILD),在编译时运行测试。

9.使用 TARGET_*()  声明构建标记和依赖关系

使用 include_ directories 并不是过时的做法,或者也可以用target_include_directores 和 target_link_libraries 替代,强化项目的设计。CMake 拥有 PRIVATE, PUBLIC 和 INTERFACE 的关键字是有其充分理由的,正确使用这些关键字是保持项目组件单向分层的关键。

10.清楚何时应该使用宏和函数

可以使用宏和函数在 CMake 中创建自定义命令。宏不会给变量引入额外的作用域,但函数会。宏对于包装具有输出参数的命令,另外函数在其他情况下也非常使用。

11.使用 CMake 将模块依赖关系可视化

CMake 支持本机依赖关系的可视化。使用 ZGRViewer 等程序可以很容易地查看输出点文件。

12.将 CMake 代码视为生产代码

像关注生产代码一样关注 CMakeLists.txt 文件,确保对其进行了充分的注释,并遵循了所有现代 CMake 最佳实践。

13. 什么时候使用 PRIVATE,PUBLIC  和  INTERFACE

下面的图表很好地解释了什么时候应该在 CMake 使用 PRIVATE,PUBLIC 和 INTERFACE.

依赖关系 描述
PRIVATE 我需要,但是依赖者不需要
PUBLIC 我和依赖者都需要
INTERFACE 我不需要,但是依赖者需要

 

14. FindPackage VS PackageConfig

Findpackage 是 CMake 或用户提供的搜索脚本,用于查找包文件。现代的包制作方会提供 <package>Config.cmake. 一旦包已安装,PackageConfig 可以向 CMake 传递其详细信息。

15.充分利用所有内核加速构建

在 CMake 构建过程中,可以选择 pass–parallel 标记,进行并行作业。但是,并不是所有的构建生成器都可以通过并行加速构建,CMake 3.12 之后的版本才支持并行选项。当使用分布式编译来加速 CMake 构建时,例如 Incredibuild,你需要将 –parallel 标记设置成一个非常大的数字,例如 300,指示 CMake 执行多达300个并发执行的任务。Incredibuild 可以向远程闲置内核分发多达 300 个任务,从而高效加快编译速度。

16. 永不言弃

其实这更像是建议而不是技巧。CMake 是大量实战测试的强大系统,因此如果您决定使用或迁移到 CMake,请不要放弃!因为对于一个新手来说,CMake 可能并不友好。CMake 是开源的,它的社区非常有用,相关的参考文件也很实用。因此,希望大家不要轻易气馁,各种资源触手可得。

最后

CMake 是一个伟大的构建系统生成器,支持所有主流平台。Visual Studio 17 支持本机 CMake,Qt 从 Qt6.0 开始支持 CMake,这一切都说明 CMake 是一款成功的产品。希望以上的技巧能对你有所帮助。祝大家工作顺利!