├── .gitignore
├── LICENSE
├── Modern-CMake-for-C++-2ed.tex
├── README.md
├── book
├── ccs.tex
├── content
│ ├── Contributors.tex
│ ├── Foreword.tex
│ ├── Preface.tex
│ ├── Reviewers.tex
│ ├── chapter1
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ ├── chapter10
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── chapter11
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ ├── 9.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ └── 3.png
│ ├── chapter12
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── chapter13
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ ├── chapter14
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── chapter15
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ ├── 9.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ ├── 5.png
│ │ │ ├── 6.png
│ │ │ └── 7.png
│ ├── chapter16
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── chapter17
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ └── 5.tex
│ ├── chapter2
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ └── 3.png
│ ├── chapter3
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ ├── 5.png
│ │ │ └── 6.png
│ ├── chapter4
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ ├── 9.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ ├── chapter5
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ └── 3.png
│ ├── chapter6
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── chapter7
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter8
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ └── chapter9
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── images
│ │ └── 1.png
└── index.tex
└── cover.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pdf
2 | *.aux
3 | *.log
4 | *.out
5 | *.gz
6 | *.py
7 | *toc
8 | *.listing
9 | *.synctex(busy)
10 | /latex-test/
11 | /test-pandoc/
12 | *.epub
13 | *.pygtex
14 | *.pygstyle
15 |
16 | /_minted-Modern-CMake-for-C++-2ed/
17 |
--------------------------------------------------------------------------------
/Modern-CMake-for-C++-2ed.tex:
--------------------------------------------------------------------------------
1 |
2 | %\special{dvipdfmx:config z 0} %取消PDF压缩,加快速度,最终版本生成的时候最好把这句话注释掉
3 |
4 | \include{book/ccs.tex}
5 |
6 | \begin{document}
7 | \begin{sloppypar} %latex中一行文字出现溢出问题的解决方法
8 | %\maketitle
9 |
10 | \subfile{book/index.tex}
11 |
12 | \end{sloppypar}
13 | \end{document}
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Modern CMake for C++
2 | *Second Edition*
3 |
4 | *轻松构建前沿C++代码,提供高质量的解决方案*
5 |
6 | * 作者:Rafał Świdziński
7 | * 译者:陈晓伟
8 | * Packt Publishing Ltd. (出版于: 2024年5月8日)
9 |
10 | > [!IMPORTANT]
11 | > 翻译是译者用自己的思想,换一种语言,对原作者想法的重新阐释。鉴于我的学识所限,误解和错译在所难免。如果你能买到本书的原版,且有能力阅读英文,请直接去读原文。因为与之相较,我的译文可能根本不值得一读。
12 | >
13 | >
— 云风,程序员修炼之道第2版译者
14 |
15 | ## 本书概述
16 |
17 | 创建顶级软件并非易事。在线研究这个主题的开发者难以确定哪些建议是当前的,哪些方法已经更新,或已经有更好的实践方式。此外,大多数资源以混乱的方式解释过程,缺乏适当的背景、上下文和结构。
18 |
19 | 《Modern CMake for C++》提供了一个端到端的指南,通过全面处理C++解决方案的构建,提供了更简单的体验。不仅介绍如何在项目中使用CMake,还强调了如何使项目保持可维护性、优雅和简洁。该指南会协助读者们自动化完成许多项目中的常见任务,包括构建、测试和打包。
20 |
21 | 本书还会介绍如何组织源目录、构建目标和创建包。随着了解的深入,将学习编译和链接可执行文件和库,详细理解这些过程,并优化每个步骤以获得最佳结果。此外,还会介绍如何将外部依赖项(如第三方库、测试框架、程序分析工具和文档生成器)整合到自己的项目中。最后,将学习如何导出、安装和打包解决方案,以供内部和外部使用。
22 |
23 | 阅读完这本书后,将能以专业水平使用CMake。
24 |
25 | ## 作者简介
26 |
27 | **Rafał Świdziński**是谷歌的一名资深工程师,拥有超过12年的全栈开发经验。他领导过思科Meraki、亚马逊和爱立信等行业巨头的项目,居住在伦敦。他始终站在技术进步的前沿,参与了许多创业项目,最近转向了医疗保健领域的AI。Rafał重视顶尖的代码质量和工艺,也会通过YouTube频道和出版的书籍分享见解。
28 |
29 | 致Zoe --- 无汝,无书(如果没有你,我无法写出这本书)
30 |
31 |
32 |
33 | ## 本书相关
34 |
35 | * github翻译地址:https://github.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed
36 |
37 | * 第一版译文地址:https://github.com/xiaoweiChen/Modern-CMake-for-Cpp
38 |
39 | * 译文的LaTeX 环境配置:https://www.cnblogs.com/1625--H/p/11524968.html
40 |
41 | * 禁用拼写检查:https://blog.csdn.net/weixin_39278265/article/details/87931348
42 |
43 | * 使用xelatex编译时需要添加`-shell-escape`和`-8bit`选项,例如:
44 |
45 | `xelatex -synctex=1 -interaction=nonstopmode -shell-escape -8bit "Modern-CMake-for-C++-2ed".tex`
46 |
47 | * 为了内容中表格和目录索引能正常生成,至少需要连续编译两次
48 |
49 | * Latex中的中文字体([思源黑体](https://github.com/adobe-fonts/source-han-sans))和英文字体([Hack](https://github.com/source-foundry/Hack-windows-installer/releases/tag/v1.6.0)),需要安装后自行配置。如何配置请参考主book/css.tex顶部关于字体的信息。
50 |
51 | * vscode中配置LaTeX:https://blog.csdn.net/Ruins_LEE/article/details/123555016
52 |
53 |
--------------------------------------------------------------------------------
/book/content/Contributors.tex:
--------------------------------------------------------------------------------
1 | \textbf{Rafał Świdziński}是谷歌的一名资深工程师,拥有超过12年的全栈开发经验。他领导过思科Meraki、亚马逊和爱立信等行业巨头的项目,体现了对创新的承诺。作为一名自愿选择居住在伦敦的人,他始终站在技术进步的前沿,参与了许多个人创业项目。最近他转向医疗保健领域的AI。Rafał重视顶尖的代码质量和工艺,也会通过YouTube频道和出版的书籍分享见解。
2 |
3 | \textit{致Zoe --- 无汝,无书(如果没有你,我无法写出这本书)}
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/book/content/Foreword.tex:
--------------------------------------------------------------------------------
1 |
2 | 在C++的不断发展的中,掌握CMake对于致力于编写高效、可维护和可扩展程序的开发者来说是必不可少的。《Modern CMake for C++》(由Rafał Świdziński撰写)就像一座灯塔,引导开发者们穿梭于CMake之中。
3 |
4 | 这本书不仅仅是一本手册,也是一次旅行。从基础开始,随着章节的深入,读者将掌握高阶技术。本书的独特之处在于其实用的方法,现实例子和最佳实践贯穿全文,确保读者不仅理解概念,而且知道如何在项目中应用。
5 |
6 | 读完这本书后,读者不仅会对CMake有深入的理解,会对驾驭C++有更多的信心。这是有助于编写更干净、更高效代码所需的知识和技能,进而成为熟练的CMake开发者。
7 |
8 | 《Modern CMake for C++》不仅仅是一本书,也是一个工具,将提升读者的C++开发技能。无论是初学者还是专家,这本书都将有助你掌握CMake,使代码更加健壮、可维护和可扩展。
9 |
10 | \begin{center}
11 | \textit{Alexander Kushnir}
12 |
13 | \textit{Biosense Webster 首席软件工程师}
14 | \end{center}
15 |
16 |
--------------------------------------------------------------------------------
/book/content/Preface.tex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 创建顶级软件并非易事。在线研究这个主题的开发者难以确定哪些建议是当前的,哪些方法已经更新,或已经有更好的实践方式。此外,大多数资源以混乱的方式解释过程,缺乏适当的背景、上下文和结构。
5 |
6 | 《Modern CMake for C++》提供了一个端到端的指南,通过全面处理C++解决方案的构建,提供了更简单的体验。它不仅教你如何在项目中使用CMake,还强调了如何使项目保持可维护性、优雅和简洁。该指南会引导你自动化完成许多项目中的常见任务,包括构建、测试和打包。
7 |
8 | 本书还会指导如何组织源目录、构建目标和创建包。随着了解的深入,将学习编译和链接可执行文件和库,详细理解这些过程,并优化每个步骤以获得最佳结果。此外,将发现如何将外部依赖项(如第三方库、测试框架、程序分析工具和文档生成器),进而整合到自己的项目中。最后,将学习如何导出、安装和打包解决方案,以供内部和外部使用。
9 |
10 | 完成这本书后,你将能以专业水平使用CMake。
11 |
12 | \mySubsectionNoFile{}{适读人群}
13 |
14 | 学会了C++之后,很快就会发现,仅仅掌握语言本身并不足以以最高标准交付项目。这本书填补了这一空白:它面向任何渴望成为更好的软件开发者,甚至专业构建工程师的人!如果想要从零开始学习现代CMake或提升和更新自己的CMake技能,阅读这本书吧。它将帮助你了解如何制作顶级的C++项目,并从其他构建环境中过渡。
15 |
16 | \mySubsectionNoFile{}{关于本书}
17 |
18 | 第1章,\textit{CMake入门},包括CMake的安装、命令行界面的使用,并介绍了CMake项目所需的基本构建块。
19 |
20 | 第2章,\textit{CMake语言},包括CCMake语言的基本概念,包括命令调用、参数、变量、控制结构和注释。
21 |
22 | 第3章,\textit{主流IDE中使用CMake},强调了集成开发环境(IDE)的重要性,指导读者选择IDE,并为Clion、Visual Studio Code和Visual Studio IDE为例,提供设置和说明。
23 |
24 | 第4章,\textit{开启第一个CMake项目},了解如何在顶层文件中配置基本的CMake项目,结构化文件树,并准备必要的工具链进行开发。
25 |
26 | 第5章,\textit{了解目标},探讨了逻辑构建目标的概念,理解它们的属性和不同类型,并学习如何为CMake项目自定义命令。
27 |
28 | 第6章,\textit{生成器表达式},解释了生成器表达式的目的和语法,包括如何使用它们进行条件扩展、查询和转换。
29 |
30 | 第7章,\textit{编译C++源文件},深入探讨了编译过程,配置预处理器和优化器,并了解减少构建时间和提高调试的技术。
31 |
32 | 第8章,\textit{链接可执行文件和库},理解链接机制,不同类型的库,单一定义规则,链接顺序,以及如何准备测试。
33 |
34 | 第9章,\textit{CMake中管理依赖项},了解如何管理第三方库,为那些缺乏CMake支持的库添加支持,并从互联网上获取外部依赖。
35 |
36 | 第10章,\textit{使用C++20模块},介绍了C++20模块,展示了如何在CMake中启用,并相应地配置工具链。
37 |
38 | 第11章,\textit{测试框架},理解自动化测试的重要性,利用CMake内置的测试支持,并使用主流框架开始单元测试。
39 |
40 | 第12章,\textit{程序分析工具},了解如何自动格式化源代码,并在构建时间和运行时检测软件错误。
41 |
42 | 第13章,\textit{生成文档},如何使用Doxygen从源代码自动创建文档,并添加样式以增强文档外观。
43 |
44 | 第14章,\textit{安装和打包},如何对项目进行发布,无论是否安装,创建可重用包,并为打包指定单个组件。
45 |
46 | 第15章,\textit{创建专业项目},应用本书所学到的所有知识来开发一个全面、专业级别的项目。
47 |
48 | 第16章,\textit{编写CMake预设},将高级项目配置封装到使用CMake预设文件的工作流程中,使项目设置和管理更加高效。
49 |
50 | 附录 - \textit{其他命令},作为与字符串、列表、文件和数学运算相关的各种CMake命令的参考。
51 |
52 | \mySubsectionNoFile{}{如何阅读}
53 |
54 | 本书假设你对C++和类Unix系统有基本的熟悉。尽管Unix知识不是严格的要求,但它将有助于理解本书中给出的示例。
55 |
56 | 本书的目标是CMake 3.26,但这里描述的大多数技术应该适用于CMake 3.15(之后添加的功能通常会突出显示)。有些章节已经更新到CMake 3.28,以涵盖最新功能。
57 |
58 | 运行示例的环境准备在第1-3章中介绍,如果熟悉这个工具,推荐使用本书提供的Docker镜像。
59 |
60 | \mySubsectionNoFile{}{下载示例}
61 |
62 | 本书的代码托管在GitHub上,地址为\url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E}。我们还有其他丰富的书籍和视频代码包,也可在GitHub中找到,地址为:\url{https://github.com/PacktPublishing/}。去看看吧!
63 |
64 | \mySubsectionNoFile{}{下载彩图}
65 |
66 | 我们还提供了一个PDF文件,其中包含了本书中使用的屏幕截图/图表的彩色图片。可以在这里下载: \url{https://packt.link/gbp/9781805121800}
67 |
68 | \mySubsectionNoFile{}{内容约定}
69 |
70 | 本书中使用了一些文本约定。
71 |
72 | 代码块如下设置:
73 |
74 | \begin{cmake}
75 | cmake_minimum_required(VERSION 3.26)
76 | project(Hello)
77 | add_executable(Hello hello.cpp)
78 | \end{cmake}
79 |
80 | 命令行输入或输出如下:
81 |
82 | \begin{shell}
83 | cmake --build --parallel []
84 | cmake --build -j []
85 | \end{shell}
86 |
87 | \begin{myNotic}{Note}
88 | 警告或重要注释
89 | \end{myNotic}
90 |
91 | \begin{myTip}{Tip}
92 | 提示和技巧
93 | \end{myTip}
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/book/content/Reviewers.tex:
--------------------------------------------------------------------------------
1 | \textbf{Eric Noulard}拥有法国ENSEEIHT的工程学位和法国UVSQ的计算机科学博士学位。Eric有着丰富的25年编写和编译各种语言源代码的经验。自2006年开始使用CMake,他也积极参与了CMake的发展。Eric曾为私营公司和政府机构服务。目前,他在Antidot工作,这是一家专注于语义搜索、人工智能和内容可访问性的软件供应商。Eric在研究团队中,负责将如生成性AI和先进NLP处理等新技术引入Antidot的旗舰产品Fluid Topics。
2 |
3 | \hspace*{\fill}
4 | \hspace*{\fill}
5 |
6 | \textbf{Giovanni Romano} 拥有28年的IT行业经验,从软件开发到应用程序/组件设计。目前他在Leica Geosystem AG担任高级软件工程师,专注于设计SDK、微服务和低延迟后端。作为Nokia/Blackberry Qt大使,他相信开源软件并致力于为该框架做出贡献。他的兴趣包括云原生应用程序、Kubernetes、Docker和GitOps。他喜欢使用C语言编程和打网球。
7 |
8 |
--------------------------------------------------------------------------------
/book/content/chapter1/0.tex:
--------------------------------------------------------------------------------
1 |
2 | 软件开发的神奇之处在于,不仅是在创造能够运行的机制,还有创造解决方案的思想。
3 |
4 | 为了将想法变为现实,我们按照以下循环工作:设计、编码和测试。我们创造变化,并用编译器能理解的语言表达,继续检查其是否如期工作。要从源码中创建正确、高质量的软件,需要小心地重复执行容易出错的任务:调用正确的命令、检查语法、链接二进制文件、运行测试、报告问题等。
5 |
6 | 记住每个步骤需要付出巨大的努力。相反,我们希望更专注于编码,将其他事情委托给工具。理想情况下,这个过程会在更改代码后,通过一个按钮启动。这个过程应该是智能的、快速的、可扩展的,并且在不同操作系统和环境中的工作方式相同,应该得到多个集成开发环境(IDE)的支持。并且,可以将其简化为持续集成(CI)流水线,每次向仓库提交更改时,都会构建和测试软件。
7 |
8 | CMake是满足许多此类需求的答案,但要正确配置和使用也要花一些心思。CMake并不是复杂性的来源,复杂性来自于要处理的东西。别担心,我们将系统地学习整个过程,你会了解到软件构建是多么“简单”。
9 |
10 | 我知道你急于开始编写自己的CMake项目,这正是本书大部分内容中做的事情。但是,由于你将主要为了用户(包括你自己)创建项目,因此了解一下他们的视角对你来说很重要。
11 |
12 | 我们从这一点开始,逐步成为一个CMake高级用户。我们将介绍一些基础知识:这个工具是什么,工作原理,以及如何安装。然后,将深入探讨命令行和操作模式。最后,将总结项目文件的不同用途,并解释如何在完全不创建项目的情况下使用CMake。
13 |
14 | 本章中,将包括以下主题:
15 |
16 | \begin{itemize}
17 | \item
18 | 基础知识
19 |
20 | \item
21 | 安装CMake
22 |
23 | \item
24 | 掌握命令行
25 |
26 | \item
27 | 项目文件
28 |
29 | \item
30 | 脚本和Find-模块
31 | \end{itemize}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/book/content/chapter1/1.tex:
--------------------------------------------------------------------------------
1 | 可以在这个章节的GitHub上找到出现的代码文件,地址为 \url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch01}。
2 |
3 | 为了构建本书提供的示例,请执行推荐命令:
4 |
5 | \begin{shell}
6 | cmake -B -S
7 | cmake --build
8 | \end{shell}
9 |
10 | 请确保将占位符 和 替换为适当的路径。正如将在本章中学到的,是输出目录的路径,而是源代码所在的位置。
11 |
12 | 要构建C++程序,还需要适合的编译器。如果熟悉Docker,可以使用在“安装CMake”部分介绍的完全工具化的镜像。如果愿意手动设置CMake,我们将在同一部分介绍如何安装。
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/book/content/chapter1/3.tex:
--------------------------------------------------------------------------------
1 |
2 | CMake 是一个跨平台的、开源的软件,用 C++ 编写。我们可以自己编译它;然而,最好不要这样做。可以从官方网站 \url{https://cmake.org/download/} 下载预编译的二进制文件。
3 |
4 | 基于 Unix 的系统可以直接从命令行提供准备好的安装包。
5 |
6 | \begin{myNotic}{Note}
7 | CMake 并不包含编译器。如果系统还没有安装编译器,在使用 CMake 之前需要进行安装。确保将它们的执行文件路径添加到 PATH 环境变量中,这样 CMake 才能找到。
8 |
9 | 为了避免在学习本书时遇到工具和依赖问题,我建议通过第一种安装方法进行实践:Docker。在现实世界的场景中,你当然会想要使用本地的版本,除非你最初就在虚拟化的环境中。
10 | \end{myNotic}
11 |
12 | 来看看 CMake 可以使用的环境。
13 |
14 | \mySubsubsection{1.3.1}{Docker}
15 |
16 | Docker (\url{https://www.docker.com/}) 是一个跨平台的工具,提供操作系统级别的虚拟化,允许应用程序以定义良好的包形式(称为容器)进行传输。这些是自给自足的捆绑包,包含运行所需的所有库、依赖项和工具。Docker 在轻量级环境中执行其容器,彼此隔离。
17 |
18 | 这个概念使得分享完成特定过程所需的所有工具链变得极为方便,无需担心微小的环境差异。
19 |
20 | Docker 平台有一个公共的容器镜像仓库,\url{https://registry.hub.docker.com/},提供数百万个现成的镜像。
21 |
22 | 方便起见,我发布了两个 Docker 镜像:
23 |
24 | \begin{itemize}
25 | \item
26 | swidzinski/cmake2:base: 基于 Ubuntu 的镜像,包含构建时所需的精心挑选的工具和依赖项
27 |
28 | \item
29 | swidzinski/cmake2:examples: 基于上述工具链的镜像,包含本书中的所有项目和示例
30 | \end{itemize}
31 |
32 | 第一个选项,是为那些想有一个干净的镜像来构建项目的读者准备,第二个选项是为我们在章节中进行示例实践而准备。
33 |
34 | 可以按照官方文档中的说明安装 Docker(请参考 docs.docker.com/get-docker)。然后,在终端中执行以下命令来下载镜像,并启动容器:
35 |
36 | \begin{shell}
37 | $ docker pull swidzinski/cmake2:examples
38 | $ docker run -it swidzinski/cmake2:examples
39 | root@b55e271a85b2:root@b55e271a85b2:#
40 | \end{shell}
41 |
42 | 请注意,示例位于以下格式的目录中
43 |
44 | \begin{shell}
45 | devuser/examples/examples/ch/-
46 | \end{shell}
47 |
48 | 和分别是零填充的章节和示例编号(例如 01, 08, 和 12)
49 |
50 | \mySubsubsection{1.3.2}{Windows}
51 |
52 | Windows 上安装CMake非常简单——只需从官方网站下载适用于 32 位或 64 位的版本即可。还可以选择适用于 Windows Installer 的便携式 ZIP 或 MSI 包装包,它会将 CMake 的 bin 目录添加到 PATH 环境变量中(图 1.2),这样一来就可以在任何目录中使用它,而不会出现此类错误:
53 |
54 | \begin{shell}
55 | 'cmake' 不是内部或外部命令,也不是可运行的程序或批处理文件。
56 | \end{shell}
57 |
58 | 如果选择 ZIP 包,需要手动安装。MSI 安装程序带有方便的 GUI:
59 |
60 | \myGraphic{0.8}{content/chapter1/images/2.png}{图 1.2:安装向导可以设置环境变量 PATH}
61 |
62 | 这是一个开源软件,所以可以自己构建 CMake。然而,在 Windows 上,首先需要在系统上获取 CMake 的二进制文件,从而生成新版本的CMake。
63 |
64 | Windows 平台与其他平台没有什么不同,也需要一个构建工具来完成由 CMake 开始构建过程。这里的一个流行选择是 Visual Studio IDE,它附带了 C++ 编译器。社区版可以从 Microsoft 的网站免费获得:\url{https://visualstudio.microsoft.com/downloads/}.
65 |
66 | \mySubsubsection{1.3.3}{Linux}
67 |
68 | Linux 上安装 CMake 的过程与安装其他软件包相同:命令行调用包管理器。包仓库通常会更新到 CMake 的最新版本,但通常不是最新版本。如果满意于此,并且使用像 Debian 或 Ubuntu 这样的发行版,最简单的方法就是直接安装适当的包:
69 |
70 | \begin{shell}
71 | $ sudo apt-get install cmake
72 | \end{shell}
73 |
74 | 对于 Red Hat 发行版,使用以下命令:
75 |
76 | \begin{shell}
77 | $ yum install cmake
78 | \end{shell}
79 |
80 | \begin{myTip}{Tip}
81 | 当安装包时,包管理器将获取为操作系统配置的仓库中可用的最新版本。在许多情况下,包仓库不提供最新版本,而是提供经过时间考验的稳定版本,以确保可靠的工作。根据需求选择,但旧版本可能不包含本书中描述的某些功能。
82 | \end{myTip}
83 |
84 | 要获取最新版本,请参考官方 CMake 网站的下载部分。如果知道当前版本号,可以使用以下命令。
85 |
86 | Linux x86\_64 的命令:
87 |
88 | \begin{shell}
89 | $ VER=3.26.0 && wget https://github.com/Kitware/CMake/releases/download/
90 | v$VER/cmake-$VER-linux-x86_64.sh && chmod +x cmake-$VER-linux-x86_64.sh &&
91 | ./cmake-$VER-linux-x86_64.sh
92 | \end{shell}
93 |
94 | Linux AArch64 的命令:
95 |
96 | \begin{shell}
97 | $ VER=3.26.0 && wget https://github.com/Kitware/CMake/releases/download/
98 | v$VER/cmake-$VER-Linux-aarch64.sh && chmod +x cmake-$VER-Linux-aarch64.sh
99 | && ./cmake-$VER-Linux-aarch64.sh
100 | \end{shell}
101 |
102 | 或者,查看从源代码构建部分,了解如何相应的平台上自行编译 CMake。
103 |
104 | \mySubsubsection{1.3.4}{macOS}
105 |
106 | 这个平台也得到了 CMake 开发者的强烈支持。最流行的安装方法是通过 MacPorts:
107 |
108 | \begin{shell}
109 | $ sudo port install cmake
110 | \end{shell}
111 |
112 | 撰写本文时,MacPorts 中可用的最新版本是 3.24.4。要获取最新版本,请安装 cmake-devel 包:
113 |
114 | \begin{shell}
115 | $ sudo port install cmake-devel
116 | \end{shell}
117 |
118 | 或者,使用 Homebrew 包管理器:
119 |
120 | \begin{shell}
121 | $ brew install cmake
122 | \end{shell}
123 |
124 | macOS 包管理器将涵盖所有必要步骤,除非从源代码构建,否则无法获得最新版本。
125 |
126 | \mySubsubsection{1.3.5}{从源代码构建}
127 |
128 | 如果使用其他平台,或者只是想体验尚未发布(或被你最喜欢的包仓库采用)的最新构建,请从官方网站下载源代码并自行编译:
129 |
130 | \begin{shell}
131 | $ wget https://github.com/Kitware/CMake/releases/
132 | download/v3.26.0/cmake-3.26.0.tar.gz
133 | $ tar xzf cmake-3.26.0.tar.gz
134 | $ cd cmake-3.26.0
135 | $ ./bootstrap
136 | $ make
137 | $ make install
138 | \end{shell}
139 |
140 | 从源代码构建相对较慢且需要更多步骤,没有其他方法可以自由选择 CMake 的版本。这对于系统包仓库中的包陈旧时特别有用:系统版本越旧,获得的更新就越少。
141 |
142 | 现在我们已经安装了 CMake,来看看怎么使用它吧!
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/book/content/chapter1/6.tex:
--------------------------------------------------------------------------------
1 | CMake 主要是专注于构建项目以产生其他系统(如 CI/CD 和测试平台,或部署到机器上或存储在工件仓库中)所消耗的工件。然而,有两种其他概念也使用 CMake 语言:脚本和模块。让我们介绍一下它们是什么,以及它们之间的区别。
2 |
3 | \mySubsubsection{1.6.1}{脚本}
4 |
5 |
6 | CMake 提供了一种与平台无关的编程语言,并附带了许多有用的命令。用这种语言编写的脚本可以与更大的项目捆绑在一起,也可以完全独立。
7 |
8 | 将其视为一种一致的跨平台工作方式。通常,为了执行任务,需要为 Linux 创建一个单独的 Bash 脚本,为 Windows 创建单独的批处理文件或 PowerShell 脚本等。CMake 抽象了这些,可以使用一个文件在所有平台上正常工作。当然,可以使用 Python、Perl 或 Ruby 等外部工具的脚本,但这会增加依赖性,并增加 C/C++ 项目的复杂性。既然大多数时候可以使用更简单的东西来完成工作,为什么要引入另一种语言呢?使用 CMake!
9 |
10 | 可以使用 -P 选项执行脚本:\textit{cmake -P 脚本.cmake}。
11 |
12 | 但使用脚本文件的实际要求是什么呢?并不多:脚本可以很复杂,或者只是一个空文件。不过,仍然建议在每一个脚本的开头调用 cmake\_minimum\_required() 命令,以告诉 CMake 应该对这个项目后续的命令应用哪些策略。
13 |
14 | 以下是一个简单脚本的例子:
15 |
16 | \filename{ch01/02-script/script�cmake}
17 |
18 | \begin{cmake}
19 | # An example of a script
20 | cmake_minimum_required(VERSION 3.26.0)
21 | message("Hello world")
22 | file(WRITE Hello.txt "I am writing to a file")
23 | \end{cmake}
24 |
25 | 运行脚本时,CMake 不会执行常规阶段(如配置或生成),也不会使用缓存,脚本中没有源代码树或构建树的概念。所以,在脚本模式下,项目特定的 CMake 命令不可用/不可使用。
26 |
27 | \mySubsubsection{1.6.2}{工具模块}
28 |
29 | CMake 项目可以使用外部模块来增强其功能。模块是用 CMake 语言编写的,包含宏定义、变量和执行各种功能的命令。它们从相当复杂的脚本(如 CPack 和 CTest 提供的脚本)到相对简单的脚本,如 AddFileDependencies 或 TestBigEndian。
30 |
31 | CMake 发行版打包了超过 80 个不同的实用模块。如果这还不够,可以通过浏览精选列表(如 \url{https://github.com/onqtam/awesome-cmake})从互联网上下载更多,或者自己编写模块。
32 |
33 | 要使用工具模块,需要 include() 命令。以下是展示这一操作的项目示例:
34 |
35 | \filename{ch01/03-module/CMakeLists。txt}
36 |
37 | \begin{cmake}
38 | cmake_minimum_required(VERSION 3.26.0)
39 | project(ModuleExample)
40 | include (TestBigEndian)
41 | test_big_endian(IS_BIG_ENDIAN)
42 | if(IS_BIG_ENDIAN)
43 | message("BIG_ENDIAN")
44 | else()
45 | message("LITTLE_ENDIAN")
46 | endif()
47 | \end{cmake}
48 |
49 | 我们将在它们与主题相关时,可以了解哪些模块可用。如果非常好奇,可以在 \url{https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html} 找到打包模块的完整列表。
50 |
51 | \mySubsubsection{1.6.3}{Find-模块}
52 |
53 | 在“包定义文件”部分,我提到了 CMake 有一种机制来查找属于不支持 CMake ,且不提供 CMake 包配置文件的 external 依赖项的文件。这就是 Find-模块的作用。CMake 提供了超过 150 个 Find-模块,能够定位在系统上安装的这些包。与实用模块一样,网上还有更多的 Find-模块可用。
54 |
55 | 可以通过调用 find\_package() 命令,并提供相关包的名称进行使用。这样的 Find-模块将玩一个捉迷藏游戏,并检查它所寻找的软件的所有已知位置。如果找到了文件,将定义包含其路径的变量(如该模块手册中所述)。现在,CMake 可以针对该依赖项进行构建。
56 |
57 | 例如,FindCURL 模块搜索流行的客户端 URL 库,并定义以下变量:CURL\_FOUND, CURL\_INCLUDE\_DIRS, CURL\_LIBRARIES, 和 CURL\_VERSION\_STRING。
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/book/content/chapter1/7.tex:
--------------------------------------------------------------------------------
1 | 现在已经了解了 CMake 是什么,以及它是如何工作的;了解了 CMake 工具家族的关键组成部分,以及它在各种系统上的安装方式。了解了所有通过命令行运行 CMake 的方式:构建系统生成、构建项目、安装、运行脚本、命令行工具和打印帮助。知道了 CTest、CPack 和 GUI 应用程序。这将帮助你,以正确的视角为用户和其他开发者创建项目。此外,了解了项目由什么组成:目录、列表文件、配置、预设和帮助文件,以及在版本控制系统中应该忽略的内容。最后,知晓了其他非项目文件:独立的脚本和两种类型的模块——工具模块和 Find-模块。
2 |
3 | 下一章中,我们将了解如何使用 CMake 编程语言。将编写自己的列表文件,并为编写第一个脚本、项目和模块打开大门。
4 |
--------------------------------------------------------------------------------
/book/content/chapter1/8.tex:
--------------------------------------------------------------------------------
1 |
2 | 需要获取更多信息,可以参考以下资料:
3 |
4 | \begin{itemize}
5 | \item
6 | 官方 CMake 网页和文档:
7 |
8 | \url{https://cmake.org/}
9 |
10 | \item
11 | 单配置生成器:
12 |
13 | \url{https://cgold.readthedocs.io/en/latest/glossary/single-config.html}
14 |
15 | \item
16 | CMake GUI 中的阶段分离:
17 |
18 | \url{https://stackoverflow.com/questions/39401003/}
19 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter1/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter1/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter1/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter1/images/2.png
--------------------------------------------------------------------------------
/book/content/chapter1/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter1/images/3.png
--------------------------------------------------------------------------------
/book/content/chapter1/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter1/images/4.png
--------------------------------------------------------------------------------
/book/content/chapter10/0.tex:
--------------------------------------------------------------------------------
1 | C++20 引入了一个新特性:模块,可以用模块文件替代了头文件中的纯文本符号声明,该模块文件将预编译为二进制格式,减少了构建时间。
2 |
3 | 我们将讨论 CMake 中 C++20 模块的内容,从 C++20 模块作为一个概念开始:相对于标准头文件的优点,以及如何简化源码单元的管理。尽管简化构建过程令人兴奋,但本章强调了其采纳的道路既困难又漫长。
4 |
5 | 理论部分结束后,我们将继续讨论在项目中实现模块的实际方面:将讨论在早期版本的 CMake 中启用它们的实验性支持,以及在 CMake 3.28 中的完整发布。
6 |
7 | 通过 C++20 模块的旅程不仅仅是为了理解一个新特性 —— 是关于重新思考在大型 C++ 项目中组件如何交互。本章结束时,不仅会了解模块的理论知识,还能通过示例获得实践经验,增强利用该特性实现更好的项目成果。
8 |
9 | 本章中,包含以下内容:
10 |
11 | \begin{itemize}
12 | \item
13 | C++20 模块是什么?
14 |
15 | \item
16 | 使用 C++20 模块支持的编写项目
17 |
18 | \item
19 | 配置工具链
20 | \end{itemize}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/book/content/chapter10/1.tex:
--------------------------------------------------------------------------------
1 | 可以在GitHub上的\url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch10}找到本章中出现的代码文件。
2 |
3 | 尝试本章中的示例,需要以下工具链:
4 |
5 | \begin{itemize}
6 | \item
7 | CMake 3.26或更新版本(推荐3.28)
8 |
9 | \item
10 | 生成器:
11 | \begin{itemize}
12 | \item
13 | Ninja 1.11及更新版本(Ninja和Ninja Multi-Config)
14 |
15 | \item
16 | Visual Studio 17 2022及更新版本
17 | \end{itemize}
18 |
19 | \item
20 | 编译器:
21 | \begin{itemize}
22 | \item
23 | MSVC工具集14.34及更新版本
24 |
25 | \item
26 | Clang 16及更新版本
27 |
28 | \item
29 | GCC 14(针对2023年9月20日之后的开发分支)及更新版本
30 | \end{itemize}
31 | \end{itemize}
32 |
33 | 如果熟悉Docker,可以使用第1章中引入的全套工具镜像。
34 |
35 | 要构建本章提供的示例,请使用以下命令:
36 |
37 | \begin{shell}
38 | cmake -B -S -G "Ninja" -D CMAKE_CXX_COMPILER=clang++-18 && cmake --build
39 | \end{shell}
40 |
41 | 请确保将占位符和替换为适当的路径。
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/book/content/chapter10/2.tex:
--------------------------------------------------------------------------------
1 | 三年前有写过关于如何使用C++模块文章,尽管模块已经为C++20规范的一部分,但C++生态系统的支持仍然没有准备好适配这个特性。幸运的是,自从本书的第一版以来,很多事情都发生了变化,随着CMake 3.28的发布,C++20模块得到了正式支持(尽管从3.26版本开始就已经有了实验性支持)。
2 |
3 | 三年似乎实现一个特性是很长的时间,但必须要记住,这不只是取决于CMake。许多碎片必须汇集在一起并良好工作。首先,需要编译器理解如何处理模块,然后构建系统如GNU Make或Ninja必须能够与模块一起工作,只有这样CMake才能使用这些新机制来提供对模块的支持。
4 |
5 | 这说明,并不是每个人都会使用最新的兼容工具,即使是现在,当前的支持仍然处于早期阶段。这些限制使得模块不适合大多数人们。所以也许不要急于依赖它们,来构建生产级别的项目。
6 |
7 | 尽管如此,如果你是尖端解决方案的爱好者,那么将会得到一次享受!如果可以严格控制项目的构建环境,例如:使用专用机器或构建容器化(Docker等),可以在内部使用模块。只需小心行事,并理解该方式可能会有所不同。可能会有一个时刻,会因为一个工具的缺失或对特性的错误实现,而完全放弃模块。
8 |
9 | C++构建的上下文中,“模块”是一个非常重的词。我们之前在本书中讨论过CMake的模块:find模块、工具模块等。而C++模块与CMake模块无关,它是C++20版本中添加的语言的原生特性。
10 |
11 | 在其核心,一个C++模块是个单一源文件,将头文件和实现文件的功能封装成一个连贯的代码单元。其包括两个主要部分:
12 |
13 | \begin{itemize}
14 | \item
15 | 二进制模块接口(BMI)类似于头文件的目的,但它是二进制格式,当其他翻译单元使用时,会减少了重新编译的需求。
16 |
17 | \item
18 | 模块实现单元提供模块的实现、定义和内部细节。其内容不能从模块外部直接访问,有效地封装了实现细节。
19 | \end{itemize}
20 |
21 | 引入模块是为了减少编译时间,解决预处理器和传统头文件的一些问题。来看看在典型的传统项目中,多个翻译单元如何粘合在一起。
22 |
23 | \myGraphic{0.7}{content/chapter10/images/1.png}{图10.1:使用传统头文件的项目结构}
24 |
25 | 前面的图显示了预处理器如何遍历项目树来构建程序。正如第7章中,为了构建每个翻译单元,预处理器机械地将文件拼接在一起,所以会生成一个包含所有由预处理器展开(包含的头文件)的长文件。这样,main.cpp将先包含自己的源码,然后是lib.h、a.h、1.h和2.h的内容。只有然后编译器才会启动并开始解析每一个字符以生成二进制目标文件。这样做本身并没有错,直到我们意识到为了编译lib.cpp,main.cpp中包含的头文件必须重新编译。并且这种冗余会随着每个翻译单元的添加而增加。
26 |
27 | 传统头文件还有其他问题:
28 |
29 | \begin{itemize}
30 | \item
31 | 需要包含保护,忘记时会导致问题。
32 |
33 | \item
34 | 循环引用的符号需要前置声明。
35 |
36 | \item
37 | 对头文件进行小的更改,需要重新编译所有翻译单元。
38 |
39 | \item
40 | 预处理器宏可能难以调试和维护。
41 | \end{itemize}
42 |
43 | 模块解决了其中的许多问题,但一些问题仍然相关:模块与头文件一样,可以相互依赖。当一个模块导入另一个模块时,仍然需要按照正确的顺序编译它们,从最内层的模块开始。因为模块的尺寸往往要大得多,所以这通常不是一个大问题。许多情况下,整个库可以存储在单个模块中。
44 |
45 | 来看看模块在实际中如何编写和使用。这个简单的例子中,我们只返回两个参数的和:
46 |
47 | \filename{ch10/01-cxx-modules/math.cppm}
48 |
49 | \begin{cpp}
50 | export module math;
51 | export int add(int a, int b) {
52 | return a + b;
53 | }
54 | \end{cpp}
55 |
56 | 一条语句开始,就告诉程序的其余部分这确实是一个名为math的模块。然后跟着一个用export关键字指定为,可以从模块外部访问的常规函数定义。
57 |
58 | \begin{myNotic}{Note}
59 | 模块文件的扩展名与常规C++源代码的不同。这是一个约定俗成的问题,不应影响代码的处理方式。最好是基于将使用的工具链来选择扩展名:
60 |
61 | \begin{itemize}
62 | \item
63 | .ixx是MSVC的扩展名。
64 |
65 | \item
66 | .cppm是Clang的扩展名。
67 |
68 | \item
69 | .cxx是GCC的扩展名。
70 | \end{itemize}
71 | \end{myNotic}
72 |
73 | 要使用这个模块,需要在程序中导入:
74 |
75 | \filename{ch10/01-cxx-modules/main�cpp}
76 |
77 | \begin{cpp}
78 | import math;
79 |
80 | #include
81 |
82 | int main() {
83 | std::cout << "Addition 2 + 2 = " << add(2, 2) << std::endl;
84 | return 0;
85 | }
86 | \end{cpp}
87 |
88 | 导入math语句足以将模块中导出的符号,直接引入主程序。现在可以在main()函数的主体中使用add()函数。从表面上看,模块与头文件非常相似。但是,若尝试像往常一样编写CMake列表文件,将无法成功地构建项目。那么,是时候介绍使用C++模块的必要步骤了。
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/book/content/chapter10/3.tex:
--------------------------------------------------------------------------------
1 |
2 | 本书主要讨论CMake 3.26,但CMake经常更新,版本3.28就在本章完成前就发布了。如果使用的是这个版本或更新的版本,可以通过将cmake\_minimum\_required()命令设置为VERSION 3.28.0来访使用最新功能。
3 |
4 | 另一方面,如果坚持使用旧版本,或者想要迎合可能尚未升级的更广泛的受众,需要启用实验性支持才能在CMake中使用C++20模块。
5 |
6 | 让我们探讨如何做到这一点。
7 |
8 | \mySubsubsection{10.3.1.}{CMake 3.26和3.27中启用实验性支持}
9 |
10 | 实验性支持代表一种协议:作为开发者,承认这个功能尚未准备好投入生产,应仅用于测试目的。要签署这样的协议,需要在项目的列表文件中,将CMAKE\_EXPERIMENTAL\_CXX\_MODULE\_CMAKE\_API变量设置为CMake版本的一个特定值。
11 |
12 | \begin{myNotic}{Note}
13 | CMake的官方Kitware仓库托管了一个问题跟踪器,可以在其中搜索标签area:cxxmodules。直到3.28版本发布,只报告了一个问题(在3.25.0中),这是潜在稳定功能的一个很好的指标。如果决定启用实验,请构建项目,以确认它适用于用户。
14 | \end{myNotic}
15 |
16 | 以下是在CMake的仓库和文档中可以找到的标志:
17 |
18 | \begin{itemize}
19 | \item
20 | 3c375311-a3c9-4396-a187-3227ef642046 对应 3.25 (无正式文件)
21 |
22 | \item
23 | 2182bf5c-ef0d-489a-91da-49dbc3090d2a 对应 3.26
24 |
25 | \item
26 | aa1f7df0-828a-4fcd-9afc-2dc80491aca7 对应 3.27
27 | \end{itemize}
28 |
29 | 如果不能访问CMake 3.25,那就头痛了,模块在该版本☞前不可用。此外,如果CMake版本早于3.27,还需要设置一个变量来为模块启用动态依赖:
30 |
31 | \begin{cmake}
32 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
33 | \end{cmake}
34 |
35 | 以下是如何为当前版本自动选择正确的API密钥,以及明确不支持版本构建(这个例子中,将只支持CMake 3.26及以上的版本)。
36 |
37 | \filename{ch10/01-cxx-modules/CMakeLists.txt}
38 |
39 | \begin{cmake}
40 | cmake_minimum_required(VERSION 3.26.0)
41 | project(CXXModules CXX)
42 |
43 | # turn on the experimental API
44 | if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.28.0)
45 | # Assume that C++ sources do import modules
46 | cmake_policy(SET CMP0155 NEW)
47 | elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.27.0)
48 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API
49 | "aa1f7df0-828a-4fcd-9afc-2dc80491aca7")
50 | elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.26.0)
51 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API
52 | "2182bf5c-ef0d-489a-91da-49dbc3090d2a")
53 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
54 | else()
55 | message(FATAL_ERROR "Version lower than 3.26 not supported")
56 | endif()
57 | \end{cmake}
58 |
59 | 逐条解释:
60 |
61 | \begin{enumerate}
62 | \item
63 | 首先,检查版本是否为3.28或更新。允许使用cmake\_policy()启用CMP0155策略。如果想支持3.28以下的版本,这是必需的。
64 |
65 | \item
66 | 如果不是这种情况,将检查版本是否高于3.27。如果是,将设置适当的API密钥。
67 |
68 | \item
69 | 如果不低于3.27,将检查它是否高于3.26。如果是这样,设置适当的API密钥,并启用实验性C++20模块动态依赖标志。
70 |
71 | \item
72 | 如果版本低于3.26,项目不支持,并将输出致命错误消息通知用户。
73 | \end{enumerate}
74 |
75 | 这使我们能够支持从3.26开始的CMake版本范围。如果有幸在每个将构建项目的环境中运行CMake 3.28,则上述if()块是不必要的。那么什么是必要的呢?
76 |
77 | \mySubsubsection{10.3.2.}{启用对CMake 3.28及以上的支持}
78 |
79 | 要从3.28开始使用C++20模块,必须明确声明这个版本为最低版本:
80 |
81 | \begin{cmake}
82 | cmake_minimum_required(VERSION 3.28.0)
83 | project(CXXModules CXX)
84 | \end{cmake}
85 |
86 | 如果将最低要求版本设置为3.28或以上,将默认启用CMP0155策略。继续阅读以了解在定义模块之前需要配置的东西。如果需要3.27或更低的版本,即使项目是使用CMake 3.28或更新的版本构建的,构建也可能会失败。
87 |
88 | 接下来要考虑的是编译器要求。
89 |
90 | \mySubsubsection{10.3.3.}{设置编译器要求}
91 |
92 | 无论是使用CMake 3.26、3.27、3.28还是更新的版本来构建,为了使用C++模块创建解决方案,都需要设置两个全局变量。第一个是禁用不受支持的C++扩展,第二个是确保编译器支持所需的标准。
93 |
94 | \filename{ch10/01-cxx-modules/CMakeLists.txt (续)}
95 |
96 | \begin{cmake}
97 | # Libc++ has no support compiler extensions for modules.
98 | set(CMAKE_CXX_EXTENSIONS OFF)
99 | set(CMAKE_CXX_STANDARD 20)
100 | \end{cmake}
101 |
102 | 由于支持模块的编译器数量非常有限,设置标准可能看起来有些多余。然而,这对于保护项目未来不受影响是一种良好的习惯。
103 |
104 | 总体配置相当简单,到此结束。我们现在可以在CMake中定义一个模块。
105 |
106 | \mySubsubsection{10.3.4.}{声明一个C++模块}
107 |
108 | CMake模块定义利用了target\_sources()命令和FILE\_SET关键字:
109 |
110 | \begin{cmake}
111 | target_sources(math
112 | PUBLIC FILE_SET CXX_MODULES TYPE CXX_MODULES FILES math.cppm
113 | )
114 | \end{cmake}
115 |
116 | 这里,引入了一种新的文件集类型:CXX\_MODULES。这种类型默认情况下只在CMake 3.28及以后的版本中支持。对于3.26,需要启用实验性API。如果没有适当的支持,将会出现如下错误消息:
117 |
118 | \begin{shell}
119 | CMake Error at CMakeLists.txt:25 (target_sources):
120 | target_sources File set TYPE may only be "HEADERS"
121 | \end{shell}
122 |
123 | 如果在构建输出中看到这个,请检查代码是否正确。如果使用的版本的API密钥值不正确,也会出现这个消息。
124 |
125 | 如前所述,在经常使用的相同二进制文件中使用模块是有好处的。
126 |
127 | 但在创建库时,这些优势更为明显。这样的库可以在其他项目中使用,或者在同一项目中让其他库使用,从而进一步增强模块化。
128 |
129 | 要声明模块并将其与主程序链接,使用以下CMake配置:
130 |
131 | \filename{ch10/01-cxx-modules/CMakeLists.txt (continued)}
132 |
133 | \begin{cmake}
134 | add_library(math)
135 | target_sources(math
136 | PUBLIC FILE_SET CXX_MODULES FILES math.cppm
137 | )
138 | target_compile_features(math PUBLIC cxx_std_20)
139 | set_target_properties(math PROPERTIES CXX_EXTENSIONS OFF)
140 |
141 | add_executable(main main.cpp)
142 | target_link_libraries(main PRIVATE math)
143 | \end{cmake}
144 |
145 | 为了确保这个库可以在其他项目中使用,必须使用target\_compile\_features()命令并明确要求cxx\_std\_20。此外,还需要在目标级别重复设置CXX\_EXTENSIONS OFF。如果没有这个,CMake将生成错误并停止构建。这似乎有些多余,可能会在CMake的未来版本中得到解决。
146 |
147 | 项目设置完成后,终于到了构建它的时候了。
148 |
--------------------------------------------------------------------------------
/book/content/chapter10/4.tex:
--------------------------------------------------------------------------------
1 |
2 | 根据Kitware网站上的博客文章(见“扩展阅读”部分),CMake最早在3.25版本就支持模块功能。尽管3.28版本正式支持此功能,但这并不是我们要享受模块便利性的唯一拼图。
3 |
4 | 下一个要求集中在构建系统上:需要支持动态依赖。截至目前,只有两个选择:
5 |
6 | \begin{itemize}
7 | \item
8 | Ninja 1.11及更新版本(Ninja和Ninja Multi-Config)
9 |
10 | \item
11 | Visual Studio 17 2022及更新版本
12 | \end{itemize}
13 |
14 | 同样,编译器需要以特定格式生成映射源依赖的文件,以供CMake使用。这种格式在Kitware开发者撰写的一篇论文中有描述,这篇论文称为p1589r5。该论文已提交给所有主流编译器以供实施。目前,只有以下三种编译器实现了所需的格式:
15 |
16 | \begin{itemize}
17 | \item
18 | Clang 16
19 |
20 | \item
21 | Visual Studio 2022 17.4 (19.34)中的MSVC
22 |
23 | \item
24 | GCC 14(针对开发分支,2023年9月20日之后)及更新版本
25 | \end{itemize}
26 |
27 | 假设环境中有所有必要的工具(可以使用为本书提供的Docker镜像),并且Make项目已准备好构建,剩下的就是配置CMake以使用所需的工具链。
28 |
29 | \begin{shell}
30 | cmake -B -S -G "Ninja"
31 | \end{shell}
32 |
33 | 此命令将配置项目以使用Ninja构建系统。下一步是设置编译器。如果默认编译器不支持模块,并且已安装了另一个编译器来尝试,可以通过定义全局变量CMAKE\_CXX\_COMPILER来实现,如下所示:
34 |
35 | \begin{shell}
36 | cmake -B -S -G "Ninja" -D CMAKE_CXX_ COMPILER=clang++-18
37 | \end{shell}
38 |
39 | 我们选择Clang 18,因为它是撰写本文时(包含在Docker镜像中)可用的最新版本。成功配置后(会看到一些关于实验性功能的警告),需要构建项目:
40 |
41 | \begin{shell}
42 | cmake --build
43 | \end{shell}
44 |
45 | 和往常一样,确保用适当的路径替换占位符和。如果一切顺利,可以运行程序,观察模块功能按预期工作:
46 |
47 | \begin{shell}
48 | $ ./main
49 | Addition 2 + 2 = 4
50 | \end{shell}
51 |
52 | 就这样,C++20模块在实际中得以应用。
53 |
54 | \begin{myNotic}{Note}
55 | “扩展阅读”部分包括来自Kitware的博客文章和关于C++编译器的源依赖格式的提案,提供了更多关于C++20模块部署和使用的深入见解。
56 | \end{myNotic}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/book/content/chapter10/5.tex:
--------------------------------------------------------------------------------
1 | 本章中,我们深入探讨了 C++20 模块,了解了它们与 CMake 模块的区别,并代表了 C++ 在简化编译和解决与冗余头文件编译,及处理预处理器宏相关挑战方面的进步。
2 |
3 | 我们通过一个简单的示例演示了如何编写和导入 C++20 模块。然后,探讨了如何为 C++20 模块设置 CMake。由于这个特性是实验性的,需要设置特定的变量,提供了一系列条件语句来确保项目能够正确配置正在使用的 CMake 版本。关于必要的工具,我们强调构建系统必须支持动态依赖,目前的选择是 Ninja 1.11 或更新版本。对于编译器支持,Clang 16 和 Visual Studio 2022 17.4 (19.34) 中的 MSVC 适合完全支持 C++20 模块,而 GCC 的支持仍在等待中。此外,还指导各位通过配置 CMake 来使用选定的工具链,包括选择构建系统生成器和设置编译器版本。配置和构建项目后,可以运行程序来查看 C++20 模块的实际应用。
4 |
5 | 下一章中,我们将了解自动化测试的重要性及其应用,以及 CMake 对测试框架的支持。
--------------------------------------------------------------------------------
/book/content/chapter10/6.tex:
--------------------------------------------------------------------------------
1 |
2 | \begin{itemize}
3 | \item
4 | 描述新特性的博客文章
5 |
6 | \url{https://www.kitware.com/import-cmake-c20-modules/}
7 |
8 | \item
9 | 针对 C++ 编译器的建议依赖格式:
10 |
11 | \url{https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html}
12 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter10/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter10/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter11/0.tex:
--------------------------------------------------------------------------------
1 | 经验丰富的专业人士知道,测试必须自动化。这是几年前有人向他们解释的,或者通过艰难的方式学到的。对于没有经验的程序员来说,这一做法并不那么明显,看起来像是额外的、不必要的劳动,并不会带来太多价值。这是可以理解的:当某人刚开始编写代码时,还没有创建真正复杂的解决方案,也没有在大型的代码库上工作。很可能,是自己项目的唯一开发者。这些早期项目很少需要超过几个月就能完成,因此很难看到代码在较长时间内是如何恶化的。
2 |
3 | 所有这些因素都导致人们认为编写测试是浪费时间和精力。编程新手可能会告诉自己,每次进行构建和运行流程时,实际上确实在测试代码。毕竟,已经手动确认了代码能够正常工作,并做到了预期效果。所以,是时候继续下一个任务了,对吧?自动化测试确保新的更改不会无意中破坏程序。本章中,将学习为什么测试很重要,以及如何使用CTest来协调测试执行。CTest可以查询可用的测试,过滤执行,随机排序,重复执行,并设置时间限制。我们将探讨如何使用这些功能,控制CTest的输出,并处理测试失败。
4 |
5 | 接下来,将修改项目的结构以适应测试,并创建自己的测试运行器。
6 |
7 | 介绍了基本原理之后,将继续添加流行的测试框架:Catch2和GoogleTest(也称为GTest),以及其模拟库。最后,将介绍使用LCOV进行详细的测试覆盖率报告。
8 |
9 | 本章中,将包含以下内容:
10 |
11 | \begin{itemize}
12 | \item
13 | 为什么自动化测试值得麻烦?
14 |
15 | \item
16 | 使用CTest在CMake中标准化测试
17 |
18 | \item
19 | 为CTest创建最基本的单元测试
20 |
21 | \item
22 | 单元测试框架
23 |
24 | \item
25 | 生成测试覆盖率报告
26 | \end{itemize}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/book/content/chapter11/1.tex:
--------------------------------------------------------------------------------
1 | 可以在GitHub上的\url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch11}找到本章中出现的代码文件。
2 |
3 | 为了构建本书提供的示例,请使用推荐的命令:
4 |
5 | \begin{shell}
6 | cmake -B -S
7 | cmake --build
8 | \end{shell}
9 |
10 | 请确保用适当的路径替换占位符和。提醒一下,是目标/输出目录的路径,而是源码所在的位置。
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/book/content/chapter11/2.tex:
--------------------------------------------------------------------------------
1 | 一条生产线上,一台机器在钢板 上打孔。这些孔需要特定的大小和形状,以便为成品安装螺栓。生产线的设计者会设置好机器,测试这些孔,然后继续下一步。最终,某些事情会发生变化:钢材可能更厚,工人可能调整了孔的大小,或者因为设计变更需要打更多的孔。一个聪明的设计者会在关键点安装质量控制检查,以确保产品符合规格。孔是如何形成的并不重要:钻孔、冲孔,还是激光切割。
2 |
3 | 同样的原则也适用于软件开发。很难预测哪些代码能够多年保持稳定,哪些将经历多次修订。随着软件功能的扩展,必须确保不会无意中破坏已有的东西。我们也会犯错误。即使是最优秀的开发者也无法预见每一次更改的所有影响。开发者经常要处理他们最初并未编写的代码,可能并不了解所有背后的假设。他们会阅读代码,形成心理模型,进行更改,并希望一切顺利。当这种方式不奏效时,修复错误可能需要数小时或数天的时间,并且会对产品和用户产生负面影响。
4 |
5 | 有时候,会遇到难以理解的代码。甚至可能开始责怪别人造成了混乱,结果发现是自己造成的。这种情况通常发生在编写代码时过于匆忙,没有完全理解问题的情况下。
6 |
7 | 作为开发者,我们不仅受到项目截止日期或有限预算的压力;有时候还需要在夜间醒来修复关键问题。令人惊讶的是,一些不那么明显的错误是如何在代码审查中溜掉的呢?
8 |
9 | 自动化测试可以预防大多数这些问题。这些测试是代码片段,用于验证另一段代码的行为是否正确。顾名思义,每当有人进行更改时,这些测试会自动运行,通常作为构建过程的一部分。它们通常为一个步骤,以确保在将代码合并到仓库之前,保证代码的质量。
10 |
11 | 有人可能会为了节省时间而跳过创建自动化测试,但这将是一个代价高昂的错误。正如史蒂文·赖特所说:“经验是在你真正需要之后才获得的东西。”除非正在编写一次性脚本或进行实验,否则不要跳过测试。可能会因为精心编写的代码不断测试失败而感到沮丧,但一个失败的测试意味着刚刚避免在生产环境中引入一个重大问题。现在花在测试上的时间,将节省以后在修复错误上的时间——晚上能睡得更香。并且,测试也不是难以添加和维护东西。
--------------------------------------------------------------------------------
/book/content/chapter11/5.tex:
--------------------------------------------------------------------------------
1 | C++ 具备有限的内省能力,但无法提供像 Java 那样强大的反射特性。这可能是为什么在 C++ 代码中编写测试和单元测试框架,比在其他功能更丰富的环境中更具挑战性的原因。这种有限方法的一个结果是,开发者需要更深入地参与编写可测试的代码。需要仔细设计接口,并考虑实际应用。例如,如何避免编译代码两次,并在测试和生产之间重用工件?
2 |
3 | 对于较小的项目来说,编译时间可能不是大问题,但随着项目的发展,缩短编译循环的需求仍然存在。前面的例子中,将所有 SUT 源文件包含在单元测试可执行文件中,除了 main.cpp 文件。如果仔细观察,会注意到该文件中的一些代码没有测试(即 main() 函数本身的内容)。编译代码两次会引入一个轻微的风险,即产生的工件可能不会完全相同。随着时间的推移,这些差异可能会逐渐增加,特别是在添加编译标志和预处理器指令时,如果贡献者急于完成、经验不足或不熟悉项目,这可能会带来风险。
4 |
5 | 这个问题有多种解决方案,但最直接的方法是将整个解决方案构建为一个库,并与单元测试链接。可能会有人想知道然后如何运行。那么就创建一个引导可执行文件,它与库链接并执行其代码。
6 |
7 | 首先,将当前的 main() 函数重命名为 run() 或 start\_program()。然后,创建一个只包含新的 main() 函数的实现文件(bootstrap.cpp)。这个函数充当适配器:唯一作用是提供一个入口点并调用 run(),传递命令行参数。将所有东西链接在一起后,最终会得到一个可测试的项目。
8 |
9 | 通过重命名 main(),现在可以将 SUT 与测试链接,并测试其 main 功能。否则,会违反第 8 章讨论的“单一定义规则”(ODR),测试运行器也需要自己的 main() 函数。
10 |
11 | 注意,测试框架默认提供它自己的 main() 函数,它会自动检测所有链接的测试,并根据配置运行它们。
12 |
13 | 这种方法产生的工件可以分为以下目标:
14 |
15 | \begin{itemize}
16 | \item
17 | 包含生产代码的 sut 库
18 |
19 | \item
20 | 引导程序,其中包含调用 sut 中 run() 的 main() 包装器
21 |
22 | \item
23 | 单元测试,其中包含运行所有 sut 测试的 main() 包装器
24 | \end{itemize}
25 |
26 | 下面的图表显示了目标之间的符号关系:
27 |
28 | \myGraphic{0.9}{content/chapter11/images/1.png}{图 11.1:测试和生产可执行文件之间共享工件}
29 |
30 | 最终得到六个实现文件,分别产生各自的(.o)对象文件:
31 |
32 | \begin{itemize}
33 | \item
34 | calc.cpp: 将要进行单元测试的 Calc 类。这称为单元测试对象(UUT),因为 UUT 是 SUT 的一个特化。
35 |
36 | \item
37 | run.cpp: 原始入口点重命名为 run(),现在可以对其进行测试。
38 |
39 | \item
40 | bootstrap.cpp: 新的 main() 入口点,调用 run()。
41 |
42 | \item
43 | calc\_test.cpp: 测试 Calc 类。
44 |
45 | \item
46 | run\_test.cpp: 新的 run() 测试可以放在这里。
47 |
48 | \item
49 | unit\_tests.o: 单元测试的入口点,扩展为调用 run() 的测试。
50 | \end{itemize}
51 |
52 | 我们即将构建的库不一定是静态或共享库。通过选择对象库,可以避免不必要的归档或链接。从技术上讲,使用动态链接 SUT 可以节省一些时间,但经常发现自己同时修改两个目标:测试和 SUT,这抵消了节省的时间。
53 |
54 | 让我们看看之前名为 main.cpp 的文件是如何变化的:
55 |
56 | \filename{ch11/02-structured/src/run.cpp}
57 |
58 | \begin{cpp}
59 | #include
60 | #include "calc.h"
61 | using namespace std;
62 | int run() {
63 | Calc c;
64 | cout << "2 + 2 = " << c.Sum(2, 2) << endl;
65 | cout << "3 * 3 = " << c.Multiply(3, 3) << endl;
66 | return 0;
67 | }
68 | \end{cpp}
69 |
70 | 变化很小:文件和函数重命名,添加了一个返回语句,因为编译器不会隐式地为 main() 之外的函数添加返回语句。
71 |
72 | 新的 main() 函数如下所示:
73 |
74 | \filename{ch11/02-structured/src/bootstrap.cpp}
75 |
76 | \begin{cpp}
77 | int run(); // declaration
78 | int main() {
79 | run();
80 | }
81 | \end{cpp}
82 |
83 | 保持简单,我们声明链接器将提供来自另一个翻译单元的 run() 函数,并调用它。
84 |
85 | 接下来是 src 下的列表文件:
86 |
87 | \filename{ch11/02-structured/src/CMakeLists.txt}
88 |
89 | \begin{cmake}
90 | add_library(sut STATIC calc.cpp run.cpp)
91 | target_include_directories(sut PUBLIC .)
92 | add_executable(bootstrap bootstrap.cpp)
93 | target_link_libraries(bootstrap PRIVATE sut)
94 | \end{cmake}
95 |
96 | 首先,创建一个 SUT 库,并将 . 标记为 PUBLIC 包含目录,这样它就会传播到所有与 SUT 链接的目标(即 bootstrap 和 unit\_tests)。请注意,包含目录相对于列表文件,允许使用点(.)来引用当前的 /src 目录。
97 |
98 | 现在是更新 unit\_tests 目标的时候了。我们将替换对 …/src/calc.cpp 文件的直接引用,改为对 sut 的链接引用,用于 unit\_tests 目标,还将为 run\_test.cpp 文件中的主函数添加一个新的测试。为了简洁,我们将省略对此的讨论,但如果感兴趣,可以查看本书仓库中的示例。
99 |
100 | 同时,以下是整个测试列表文件:
101 |
102 | \filename{ch11/02-structured/test/CMakeLists.txt}
103 |
104 | \begin{cmake}
105 | add_executable(unit_tests
106 | unit_tests.cpp
107 | calc_test.cpp
108 | run_test.cpp)
109 | target_link_libraries(unit_tests PRIVATE sut)
110 | add_test(NAME SumAddsTwoInts COMMAND unit_tests 1)
111 | add_test(NAME MultiplyMultipliesTwoInts COMMAND unit_tests 2)
112 | add_test(NAME RunOutputsCorrectEquations COMMAND unit_tests 3)
113 | \end{cmake}
114 |
115 | 完成了!我们按需注册了新的测试。通过遵循这种做法,可以确保测试是在用于生产中的机器代码上执行的。
116 |
117 | \begin{myNotic}{Note}
118 | 这里使用的目标名称,sut 和 bootstrap,是为了从测试的角度清楚地表明它们是关于什么的。在实际项目中,应该选择与生产代码上下文(而不是测试)匹配的名称。例如,对于一个 FooApp,将目标命名为 foo 而不是 bootstrap,将 lib\_foo 而不是 sut。
119 | \end{myNotic}
120 |
121 | 现在,我们知道了如何在适当的目标中构建一个可测试的项目,再将焦点转移到测试框架本身。我们不想手动将每个测试案例添加到列表文件中,对吧?
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/book/content/chapter11/8.tex:
--------------------------------------------------------------------------------
1 | 表面上,与恰当测试相关的复杂性似乎如此之大,以至于它们不值得付出努力。有多少代码在外运行却没有任何测试,这真是令人惊讶,主要是测试软件是一项令人生畏的任务。如果是手动进行,则更是如此。不幸的是,如果没有严格的自动化测试,代码中的问题都是不完整或根本不可见的。未经测试的代码可能更快编写(但并非总是如此);然而,要阅读、重构和修复这些代码,绝对要慢得多。
2 |
3 | 本章中,概述了从一开始就进行测试工作的几个关键原因,其中最引人注目的是心理健康和良好的夜间睡眠。没有一个开发者躺在床上想:等不及几个小时后被打扰,去处理一些生产环境中的火灾和修复错误。但认真地说,在将错误部署到生产环境之前捕捉它们,对你(和公司)可能是一根救命稻草。
4 |
5 | 当涉及到测试工具时,CMake在这里真正展现了它的强大。CTest在检测故障测试方面能发挥奇迹:隔离、混洗、重复和超时。所有这些技术都非常有用,可以通过一个方便的命令行标志获得。我们了解到如何使用CTest来列出测试,过滤,并控制测试用例的输出,但最重要的是,现在知道如何使用标准解决方案的真正力量。任何用CMake构建的项目都可以进行完全相同的测试,而无需探究其内部细节。
6 |
7 | 接下来,我们结构化了项目,简化了测试过程,并在生产代码和测试运行器之间重用相同的对象文件。编写自己的测试运行器很有趣,但也许我们应专注于我们程序实际应解决的问题,并花时间接受一个主流的第三方测试框架。
8 |
9 | 说到这,我们学习了Catch2和GoogleTest的基础知识。进一步深入研究了GMock库的细节,并理解了测试替身,如何工作以实现真正的单元测试。最后,使用LCOV设置了一些报告。毕竟,没有什么比硬数据更能证明,我们的解决方案确实是经过全面测试的。
10 |
11 | 下一章中,将讨论更多有用的工具,以提高代码的质量,并找出潜在的问题。
--------------------------------------------------------------------------------
/book/content/chapter11/9.tex:
--------------------------------------------------------------------------------
1 |
2 |
3 | \begin{itemize}
4 | \item
5 | 关于CTest的CMake文档:
6 |
7 | \url{https://cmake.org/cmake/help/latest/manual/ctest.1.html}
8 |
9 | \item
10 | Catch2文档:
11 |
12 | \url{https://github.com/catchorg/Catch2/blob/devel/docs/}
13 |
14 | \item
15 | GMock教程:
16 |
17 | \url{https://google.github.io/googletest/gmock_for_dummies.html}
18 |
19 | \item
20 | Abseil:
21 |
22 | \url{https://abseil.io/}
23 |
24 | \item
25 | 使用Abseil的实时更新:
26 |
27 | \url{https://abseil.io/about/philosophy#we-recommend-that-you-choose-to-liveat-head}
28 |
29 | \item
30 | 为什么Abseil会成为GTest的依赖项:
31 |
32 | \url{https://github.com/google/googletest/issues/2883}
33 |
34 | \item
35 | GCC中的覆盖率:
36 |
37 | \url{https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html}
38 |
39 | \url{https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html}
40 |
41 | \url{https://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html}
42 |
43 | \item
44 | Clang中的覆盖率:
45 |
46 | \url{https://clang.llvm.org/docs/SourceBasedCodeCoverage.html}
47 |
48 | \item
49 | LCOV命令行工具的文档:
50 |
51 | \url{https://helpmanual.io/man1/lcov/}
52 |
53 | \item
54 | LCOV项目仓库:
55 |
56 | \url{https://github.com/linux-test-project/lcov}
57 |
58 | \item
59 | GCOV更新功能:
60 |
61 | \url{https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html#Invoking-Gcov}
62 | \end{itemize}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/book/content/chapter11/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter11/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter11/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter11/images/2.png
--------------------------------------------------------------------------------
/book/content/chapter11/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter11/images/3.png
--------------------------------------------------------------------------------
/book/content/chapter12/0.tex:
--------------------------------------------------------------------------------
1 | 编写高质量的代码并非易事,即使对于经验丰富的开发者来说也是如此。通过在解决方案中包含测试,降低了在主代码中犯基本错误的可能性,但这还不足以避免更复杂的问题。每一款软件都包含如此多的细节,跟踪它们可以成为一份全职工作。各种约定和特定的设计实践由负责维护产品的团队建立。
2 |
3 | 有些问题与一致的编码风格相关:代码应该使用80列还是120列?应该允许使用std::bind,还是坚持使用lambda函数?使用C风格数组是否可以接受?应该将小函数写在一行中吗?应该总是使用auto,还是只在它提高可读性时使用?理想情况下,应该避免使用公认的通常不正确的语句:无限循环、使用标准库保留的标识符、无意中的数据丢失、不必要的if语句,以及不是“最佳实践”的东西(更多信息请参见“扩展阅读”部分)。
4 |
5 | 另一个需要考虑的方面是代码现代化。随着C++的发展,引入了新特性,以了解更新到最新标准的挑战性。此外,手动执行此操作既耗时又增加了引入错误的风险,特别是在大型代码库中。最后,应该检查事物在运行时的操作情况:运行程序并检查其内存。内存使用后是否正确释放?是否正在访问正确初始化的数据?还是代码试图访问不存在的指针?
6 |
7 | 手动管理所有这些挑战和问题既耗时又容易出错。幸运的是,可以使用自动化工具来检查和执行规则,纠正错误,并使代码保持最新。是时候探索程序分析工具了。代码将在每次构建时受到严格审查,以确保其符合行业标准。
8 |
9 | 本章中,将包含以下内容:
10 |
11 | \begin{itemize}
12 | \item
13 | 执行格式化
14 |
15 | \item
16 | 使用静态检查器
17 |
18 | \item
19 | 使用Valgrind进行动态分析
20 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter12/1.tex:
--------------------------------------------------------------------------------
1 | 可以在GitHub上找到本章中出现的代码文件,地址为 \url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch12}。
2 |
3 | 为了构建本书提供的示例,请使用以下推荐的命令:
4 |
5 | \begin{shell}
6 | cmake -B -S
7 | cmake --build
8 | \end{shell}
9 |
10 | 请确保将占位符和替换为适当的路径。提醒一下,是目标/输出目录的路径,而
108 | using namespace std;
109 | int main() {
110 | cout << "Hello, world!" << endl;
111 | }
112 | \end{cpp}
113 |
114 | 快完成了。只需要格式化器的配置文件,通过 -{}-style=file 命令行参数启用:
115 |
116 |
117 | \filename{ch12/01-formatting/.clang-format}
118 |
119 | \begin{shell}
120 | BasedOnStyle: Google
121 | ColumnLimit: 140
122 | UseTab: Never
123 | AllowShortLoopsOnASingleLine: false
124 | AllowShortFunctionsOnASingleLine: false
125 | AllowShortIfStatementsOnASingleLine: false
126 | \end{shell}
127 |
128 | ClangFormat 将扫描父目录以查找 .clang-format 文件,该文件指定了确切的格式化规则。这让我们可以自定义每一个细节。我这里的情况是,从 Google 的编码风格开始,并做了一些调整:设置 140 个字符的列限制,不使用制表符,并且不允许短循环、函数或单行 if 语句。
129 |
130 | 构建项目后(格式化在编译前自动进行),文件看起来会像这样:
131 |
132 | \filename{ch12/01-formatting/src/header.h (格式化后)}
133 |
134 | \begin{cpp}
135 | int unused() {
136 | return 2 + 2;
137 | }
138 | \end{cpp}
139 |
140 | 即使目标没有使用头文件,其也格式化了。短函数不能放在单行上,正如预期的那样,添加了新行。现在 main.cpp 文件看起来也很整洁。不需要的空白已经消失,缩进也标准化了:
141 |
142 | \filename{ch12/01-formatting/src/main.cpp (formatted)}
143 |
144 | \begin{cpp}
145 | #include
146 | using namespace std;
147 | int main() {
148 | cout << "Hello, world!" << endl;
149 | }
150 | \end{cpp}
151 |
152 | 自动化格式化在代码审查期间节省了时间。如果曾经因为空白问题而不得不修改提交,会知道这带来了多大的安慰。一致的格式化让你的代码毫不费力地保持清洁。
153 |
154 | \begin{myNotic}{Note}
155 | 将格式化应用于整个代码库,很可能会对仓库中的大多数文件造成一次性的大更改。如果你(或你的团队成员)正在进行一些工作,这可能会引起很多合并冲突。最好的做法是在所有挂起更改完成后协调这样的努力。如果这不可能,考虑逐步采用,或许可以按目录进行,团队成员会感激你的。
156 | \end{myNotic}
157 |
158 | 尽管格式化器在使代码视觉上一致方面表现出色,但它不是一个全面的程序分析工具。对于更高级的需求,需要设计用于静态分析的其他工具。
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/book/content/chapter12/5.tex:
--------------------------------------------------------------------------------
1 | “你将在阅读代码上花费的时间比编写代码的时间要多,应该优先优化代码的可读性。”这一原则在许多关于整洁代码的书籍中都有所体现。它得到了许多软件开发者的经验支持,这也是为什么像空格数量、换行,以及\#import语句的顺序这样的小细节都要标准化。这种标准化不仅仅是为了做到细致,而是为了节省时间。遵循本章中的实践,可以忘记手动格式化代码。构建时,代码会自动格式化,这是无论如何都要进行的测试代码的步骤。使用ClangFormat,可以确保格式化符合选择的标准。
2 |
3 | 除了简单的空格调整,代码还应该满足许多其他指南。这时clang-tidy就派上用场了,其有助于执行团队或组织约定的编码规范。我们深入讨论了这款静态检查器,还提到了其他选项,如Cpplint、Cppcheck、include-what-you-use和Link What You Use。由于静态链接器相对较快,可以几乎不投入成本地将其添加到构建中,这通常非常值得。
4 |
5 | 我们还检查了Valgrind工具,特别是Memcheck,用于识别内存管理问题,如错误的读写操作。这个工具在避免手动调试数小时,和防止生产环境中出现漏洞方面,具有无法估量的价值。我们介绍了一种方法,通过Memcheck-Cover这个HTML报告生成器,使Valgrind的输出更加用户友好。这在无法运行IDE的环境中使用特别有用,比如CI流程。
6 |
7 | 本章只是一个起点。还有许多其他免费和商业工具有助于提高代码质量。探索它们,找到最适合你的工具。下一章中,我们将深入探讨生成文档的内容。
--------------------------------------------------------------------------------
/book/content/chapter12/6.tex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | \begin{itemize}
5 | \item
6 | 由C++的作者Bjarne Stroustrup策划的C++核心指南:
7 |
8 | \url{https://github.com/isocpp/CppCoreGuidelines}
9 |
10 | \item
11 | ClangFormat参考手册:
12 |
13 | \url{https://clang.llvm.org/docs/ClangFormat.html}
14 |
15 | \item
16 | C++的静态分析工具——精选列表:
17 |
18 | \url{https://github.com/analysis-tools-dev/static-analysis#cpp}
19 |
20 | \item
21 | CMake内置的静态检查器支持:
22 |
23 | \url{https://www.kitware.com//static-checks-with-cmake-cdash-iwyu-clang-tidy-lwyu-cpplint-and-cppcheck/}
24 |
25 | \item
26 | 启用clang-tidy的目标属性:
27 |
28 | \url{https://cmake.org/cmake/help/latest/prop_tgt/LANG_CLANG_TIDY.html}
29 |
30 | \item
31 | Valgrind手册:
32 |
33 | \url{https://www.valgrind.org/docs/manual/manual-core.html}
34 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter12/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter12/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter13/0.tex:
--------------------------------------------------------------------------------
1 | 高质量的代码不仅仅是编写良好、可运行且经过测试的——还应该有详尽的文档。文档使我们能够分享可能丢失的信息,描绘更大的蓝图,提供上下文,揭示意图,并且最终——对外部用户和维护者进行介绍。
2 |
3 | 你还记得上次加入一个新项目,在目录和文件的迷宫中迷失数小时的经历吗?这种情况是可以避免的。真正优秀的文档,可以让一个完全的新手在几秒钟内找到他们所需的代码行。遗憾的是,缺少文档的问题常常会被忽视。这并不奇怪——它需要相当多的技巧,而我们中的许多人并不擅长于此。此外,文档和代码很快就会过时。除非实施严格的更新和审查过程,否则很容易忘记文档也需要关注。
4 |
5 | 一些团队(为了节省时间或因为经理鼓励他们这样做)遵循编写自文档化代码的实践,通过为文件名、函数、变量等选择有意义、可读的标识符,希望避免编写文档的麻烦。即使是最优秀的函数签名也不能确保传达所有必要的信息——例如,int removeDuplicates(); 是描述性的,但它没有揭示返回了什么。可能是找到的重复项的数量,剩余项目的数量,或其他东西——都不清楚。虽然良好的命名习惯绝对正确,但它不能取代认真编写文档的行为。记住:世上没有免费的午餐。
6 |
7 | 为了使事情变得更容易,专业人士使用自动文档生成器来分析源文件中的代码和注释,以生成各种格式的全面文档。将这样的生成器添加到 CMake 项目中非常简单——来看看如何操作!
8 |
9 | 本章中,将包含以下内容:
10 |
11 | \begin{itemize}
12 | \item
13 | 将 Doxygen 添加到项目
14 |
15 | \item
16 | 生成具有现代外观的文档
17 |
18 | \item
19 | 使用自定义 HTML 增强输出
20 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter13/1.tex:
--------------------------------------------------------------------------------
1 | 可以在GitHub上找到本章中出现的代码文件,地址为 \url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch13}。
2 |
3 | 为了构建本书提供的示例,请使用以下推荐命令:
4 |
5 | \begin{shell}
6 | cmake -B -S
7 | cmake --build
8 | \end{shell}
9 |
10 | 请确保将占位符和替换为适当的路径。提醒一下:是目标/输出目录的路径,而是源码所在的路径。
11 |
12 |
--------------------------------------------------------------------------------
/book/content/chapter13/2.tex:
--------------------------------------------------------------------------------
1 | Doxygen是最成熟和流行的从C++源代码生成文档的工具。当我说“成熟”时,我是认真的:第一个版本是由Dimitri van Heesch在1997年10月发布的。从那时起,它已经广为流传,并且几乎有250名贡献者活跃地支持 (\url{https://github.com/doxygen/doxygen})。
2 |
3 | 可能会担心将Doxygen整合到,没有使用文档生成的较大项目中。确实,为每个函数添加注释可能看起来令人生畏。然而,我鼓励从小处着手。专注于记录最近在最新提交中处理过的元素。记住,即使是部分完整的文档,也比完全没有文档要好,并且它逐渐帮助建立对项目的更全面的理解。
4 |
5 | Doxygen可以生成以下格式的文档:
6 |
7 | \begin{itemize}
8 | \item
9 | 超文本标记语言 (HTML)
10 |
11 | \item
12 | 富文本格式 (RTF)
13 |
14 | \item
15 | 可移植文档格式t (PDF)
16 |
17 | \item
18 | Lamport TeX (LaTeX)
19 |
20 | \item
21 | PostScript (PS)
22 |
23 | \item
24 | Unix 手册 (man 页面)
25 |
26 | \item
27 | 微软HTML帮助手册 (.CHM)
28 | \end{itemize}
29 |
30 | 如果使用Doxygen指定的格式,在代码中添加提供信息注释,其将解析这些注释以丰富输出文件。此外,将分析代码结构以生成有用的图表和图形。后者是可选的,其需要外部Graphviz工具 (\url{https://graphviz.org/})。
31 |
32 | 开发者首先应该考虑以下问题:项目的用户只会接收文档,还是自己生成文档(可能是从源代码构建时)?第一个选项意味着文档随二进制文件分发,在线提供,或者(不太优雅地)与源代码一起检入到仓库中。
33 |
34 | 这很重要,如果希望用户在构建过程中生成文档,将需要在系统中存在依赖项。这并不是一个严重的问题,因为Doxygen和Graphviz可以通过大多数包管理器获得,并且只需要一个简单的命令,例如在Debian上:
35 |
36 | \begin{shell}
37 | apt-get install doxygen graphviz
38 | \end{shell}
39 |
40 | Windows的二进制文件也可以在项目网站上找到(请参阅“扩展阅读”部分)。
41 |
42 | 然而,一些用户可能不太愿意安装这个工具。我们必须决定是为用户生成文档,还是让他们在需要时添加依赖项。项目也可以像第9章描述的那样,为用户自动添加。注意,Doxygen是用CMake构建的。
43 |
44 | 当Doxygen和Graphviz安装到系统中后,可以将生成过程添加到我们的项目中。与一些在线资源建议的相反,这并不像看起来那么困难或复杂。不需要创建外部配置文件,提供Doxygen可执行文件的路径,或添加自定义目标。自从CMake 3.9以来,可以使用FindDoxygen查找模块中的doxygen\_add\_docs()函数,其会设置文档目标。
45 |
46 | \begin{shell}
47 | doxygen_add_docs(targetName [sourceFilesOrDirs...]
48 | [ALL] [WORKING_DIRECTORY dir] [COMMENT comment])
49 | \end{shell}
50 |
51 | 第一个参数指定目标名称,需要使用-t参数显式构建它,通过cmake生成构建树:
52 |
53 | \begin{shell}
54 | # cmake --build -t targetName
55 | \end{shell}
56 |
57 | 或者,可以通过添加ALL参数来确保始终构建文档。WORKING\_DIRECTORY选项很直接,指定命令的运行目录。COMMENT选项设置的值将在文档生成开始之前显示,提供有用的信息或指令。
58 |
59 | 我们将遵循前几章的做法,并创建一个带有辅助函数的实用模块(以便可以在其他项目中重用:
60 |
61 | \filename{ch13/01-doxygen/cmake/Doxygen.cmake}
62 |
63 | \begin{cmake}
64 | function(Doxygen input output)
65 | find_package(Doxygen)
66 | if (NOT DOXYGEN_FOUND)
67 | add_custom_target(doxygen COMMAND false
68 | COMMENT "Doxygen not found")
69 | return()
70 | endif()
71 | set(DOXYGEN_GENERATE_HTML YES)
72 | set(DOXYGEN_HTML_OUTPUT
73 | ${PROJECT_BINARY_DIR}/${output})
74 | doxygen_add_docs(doxygen
75 | ${PROJECT_SOURCE_DIR}/${input}
76 | COMMENT "Generate HTML documentation"
77 | )
78 | endfunction()
79 | \end{cmake}
80 |
81 | 该函数接受两个参数——输入和输出目录——并创建一个自定义的doxygen目标:
82 |
83 | \begin{enumerate}
84 | \item
85 | 首先,使用CMake内置的Doxygen查找模块来确定,系统中是否可用Doxygen。
86 |
87 | \item
88 | 如果不可用,创建一个虚拟的doxygen目标,通知用户并运行false命令(在类Unix系统中返回1,导致构建失败)。我们在此处用return()终止函数。
89 |
90 | \item
91 | 如果Doxygen可用,将其配置为在提供的输出目录中生成HTML输出。Doxygen非常可配置(更多信息请参阅官方文档)。要设置任何选项,只需按照示例调用set(),并在其名称前加上DOXYGEN\_。
92 |
93 | \item
94 | 设置实际的doxygen目标。所有DOXYGEN\_变量都将被转发到Doxygen的配置文件中,并将从源树中提供的输入目录生成文档。
95 | \end{enumerate}
96 |
97 | 如果文档由用户生成,那么第二步可能应该涉及安装Doxygen。
98 |
99 | 要使用这个函数,可以将其纳入我们项目的主列表文件中:
100 |
101 | \filename{ch13/01-doxygen/CMakeLists.txt}
102 |
103 | \begin{cmake}
104 | cmake_minimum_required(VERSION 3.26)
105 | project(Doxygen CXX)
106 | enable_testing()
107 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
108 | add_subdirectory(src bin)
109 | include(Doxygen)
110 | Doxygen(src docs)
111 | \end{cmake}
112 |
113 | 一点也不难!构建doxygen目标将生成如下所示的HTML文档:
114 |
115 | \myGraphic{0.9}{content/chapter13/images/1.png}{图13.1:使用Doxygen生成的类参考}
116 |
117 | 为了在成员函数文档中添加重要细节,可以在头文件中的C++方法声明前加上适当的注释:
118 |
119 | \filename{ch13/01-doxygen/src/calc.h (片段)}
120 |
121 | \begin{cpp}
122 | /**
123 | Multiply... Who would have thought?
124 | @param a the first factor
125 | @param b the second factor
126 | @result The product
127 | */
128 | int Multiply(int a, int b);
129 | \end{cpp}
130 |
131 | 这种格式被称为Javadoc。重要的是,注释块要以双星号开始:/**。更多关于Doxygen的docblocks的描述可以在“扩展阅读”部分的链接中找到。带有此类注释的Multiply函数将呈现如下所示的图形:
132 |
133 | \myGraphic{0.5}{content/chapter13/images/2.png}{图13.2:参数和结果的注释}
134 |
135 | 如果安装了Graphviz,Doxygen将检测到它并生成依赖关系图:
136 |
137 | \myGraphic{0.9}{content/chapter13/images/3.png}{图13.3:Doxygen生成的继承和协作图}
138 |
139 | 通过直接从源代码生成文档,建立了一个过程,使得在开发周期中与代码更改同步快速更新成为可能。此外,代码审查期间很可能会注意到忽略的注释更新。
140 |
141 | 许多开发者表示担忧,Doxygen提供的设计看起来过时,这让他们犹豫是否向客户展示生成的文档。然而,这个问题有一个简单的解决方案。
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/book/content/chapter13/3.tex:
--------------------------------------------------------------------------------
1 | 使用清新的设计,来记录项目非常重要。毕竟,如果投入大量工作来为尖端项目编写高质量的文档,那么用户必须将其视为如此。尽管Doxygen功能丰富,但它并不以遵循最新的视觉趋势而闻名。然而,改造其外观并不需要大量的努力。
2 |
3 | 幸运的是,一位名为jothepro的开发者创建了一个名为doxygen-awesome-css的主题,提供了一个现代化、可定制的设计。以下截图展示了这个主题:
4 |
5 | \myGraphic{0.8}{content/chapter13/images/4.png}{图13.4:使用doxygen-awesome-css主题的HTML文档}
6 |
7 | 这个主题不需要任何依赖,可以轻松地从其GitHub页面获取,网址为: \url{https://github.com/jothepro/doxygen-awesome-css}。
8 |
9 | \begin{myNotic}{Note}
10 | 有些在线资源推荐使用应用程序组合,比如通过Sphinx的Breathe和Exhale扩展来转换Doxygen的输出,这种方法可能比较复杂且依赖较多(例如需要Python)。对于一个并非所有成员都深入了解CMake的团队来说,更简单的方法通常更实用。
11 | \end{myNotic}
12 |
13 | 我们可以通过一个自动化过程高效地实现这个主题。看看如何通过添加一个新的宏,来扩展Doxygen.cmake文件并使用:
14 |
15 | \filename{ch13/02-doxygen-nice/cmake/Doxygen.cmake (片段)}
16 |
17 | \begin{cmake}
18 | macro(UseDoxygenAwesomeCss)
19 | include(FetchContent)
20 | FetchContent_Declare(doxygen-awesome-css
21 | GIT_REPOSITORY
22 | https://github.com/jothepro/doxygen-awesome-css.git
23 | GIT_TAG
24 | V2.3.1
25 | )
26 | FetchContent_MakeAvailable(doxygen-awesome-css)
27 | set(DOXYGEN_GENERATE_TREEVIEW YES)
28 | set(DOXYGEN_HAVE_DOT YES)
29 | set(DOXYGEN_DOT_IMAGE_FORMAT svg)
30 | set(DOXYGEN_DOT_TRANSPARENT YES)
31 | set(DOXYGEN_HTML_EXTRA_STYLESHEET
32 | ${doxygen-awesome-css_SOURCE_DIR}/doxygen-awesome.css)
33 | endmacro()
34 | \end{cmake}
35 |
36 | 我们已经从本书的前几章中了解了所有这些命令,但为了完全清晰,再来看看发生了什么:
37 |
38 | \begin{enumerate}
39 | \item
40 | 使用FetchContent模块从Git获取doxygen-awesome-css
41 |
42 | \item
43 | 为Doxygen配置额外的选项(这些选项特别由主题的README文件推荐)
44 |
45 | \item
46 | 将主题的css文件复制到Doxygen的输出目录
47 | \end{enumerate}
48 |
49 | 最好在Doxygen函数中调用这个宏,正好在doxygen\_add\_docs()之前:
50 |
51 | \filename{ch13/02-doxygen-nice/cmake/Doxygen.cmake (片段)}
52 |
53 | \begin{cmake}
54 | function(Doxygen input output)
55 | # ...
56 | UseDoxygenAwesomeCss()
57 | doxygen_add_docs (...)
58 | endfunction()
59 |
60 | macro(UseDoxygenAwesomeCss)
61 | # ...
62 | endmacro()
63 | \end{cmake}
64 |
65 | 记住,宏中的所有变量都在调用函数的作用域内设置。现在,可以享受生成的HTML文档中的现代风格,并且自豪地与世界分享。然而,我们的演示主题提供了一些JavaScript模块来增强体验。
66 |
67 | 最后,我们要如何包含它们?
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/book/content/chapter13/4.tex:
--------------------------------------------------------------------------------
1 | Doxygen Awesome 提供了一些额外的功能,这些功能可以通过在文档头部的 HTML 标签内包含几个 JavaScript 代码片段来启用。这些功能非常有用,允许用户在浅色和深色模式之间切换、为代码片段添加复制按钮、提供段落标题的永久链接以及创建交互式的目录。
2 |
3 | 然而,实现这些功能需要将额外的代码复制到输出目录,并将其包含在生成的 HTML 文件中。
4 |
5 | 以下是需要在 标签前包含的 JavaScript 代码:
6 |
7 | \filename{ch13/cmake/extra\_headers}
8 |
9 | \begin{minted}[frame=single]{html}
10 |
11 |
12 |
13 |
14 |
15 |
21 | \end{minted}
22 |
23 | 这段代码首先会包含几个 JavaScript 文件,然后初始化不同的扩展。不幸的是,这段代码不能简单地添加到某个变量中。相反,需要用一个自定义文件覆盖默认的头部文件。这种覆盖可以通过向 Doxygen 的 HTML\_HEADER 配置变量提供该文件的路径来完成。
24 |
25 | 为了创建一个自定义头部而无需硬编码全部内容,可以使用 Doxygen 的命令行工具来生成一个默认的头部文件,并在生成文档之前编辑它:
26 |
27 | \begin{shell}
28 | doxygen -w html header.html footer.html style.css
29 | \end{shell}
30 |
31 | 尽管我们不会使用或更改 footer.html 或 style.css,但它们是必需的参数,所以无论如何都需要创建它们。
32 |
33 | 最后,需要自动在 标签前插入 ch13/cmake/extra\_headers 文件的内容以包含所需的 JavaScript。这可以通过 Unix 命令行工具 sed 来完成,其会就地编辑 header.html 文件:
34 |
35 | \begin{shell}
36 | sed -i '/<\/head>/r ch13/cmake/extra_headers' header.html
37 | \end{shell}
38 |
39 | 现在需要用 CMake 语言来编写这些步骤。以下是实现这一目标的宏:
40 |
41 | \filename{ch13/02-doxygen-nice/cmake/Doxygen.cmake (片段)}
42 |
43 | \begin{cmake}
44 | macro(UseDoxygenAwesomeExtensions)
45 | set(DOXYGEN_HTML_EXTRA_FILES
46 | ${doxygen-awesome-css_SOURCE_DIR}/doxygen-awesome-darkmode-toggle.js
47 | ${doxygen-awesome-css_SOURCE_DIR}/doxygen-awesome-fragment-copybutton.js
48 | ${doxygen-awesome-css_SOURCE_DIR}/doxygen-awesome-paragraph-link.js
49 | ${doxygen-awesome-css_SOURCE_DIR}/doxygen-awesome-interactive-toc.js
50 | )
51 |
52 | execute_process(
53 | COMMAND doxygen -w html header.html footer.html style.css
54 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
55 | )
56 | execute_process(
57 | COMMAND sed -i
58 | "/<\\/head>/r ${PROJECT_SOURCE_DIR}/cmake/extra_headers"
59 | header.html
60 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
61 | )
62 | set(DOXYGEN_HTML_HEADER ${PROJECT_BINARY_DIR}/header.html)
63 | endmacro()
64 | \end{cmake}
65 |
66 | 这段代码看起来很复杂,但仔细看过后,会发现它其实相当简单。
67 |
68 | 下面列出它的功能:
69 |
70 | \begin{enumerate}
71 | \item
72 | 将四个 JavaScript 文件复制到输出目录
73 |
74 | \item
75 | 执行 doxygen 命令来生成默认的 HTML 文件
76 |
77 | \item
78 | 执行 sed 命令将所需的 JavaScript 注入头部
79 |
80 | \item
81 | 用自定义版本覆盖默认的头部
82 | \end{enumerate}
83 |
84 | 为了完成集成,可以在启用基本样式表之后调用这个宏:
85 |
86 | \filename{ch13/02-doxygen-nice/cmake/Doxygen.cmake (fragment)}
87 |
88 | \begin{cmake}
89 | function(Doxygen input output)
90 | # …
91 | UseDoxygenAwesomeCss()
92 | UseDoxygenAwesomeExtensions()
93 | # …
94 | endfunction()
95 | \end{cmake}
96 |
97 | 本例的完整代码以及实际示例可以在本书的在线仓库中找到,我建议你在实际环境中阅读和探索这些示例。
98 |
99 | \begin{myNotic}{其他文档生成工具}
100 | 有许多其他工具没有在这本书中涵盖,我们主要关注由 CMake 支持的项目。不过,其中一些可能更适合各位的具体情况。如果愿意尝试新事物,可以访问两个我觉得有趣的项目的网站:
101 |
102 | \begin{itemize}
103 | \item
104 | Adobe 的 Hyde (\url{https://github.com/adobe/hyde}):针对 Clang 编译器设计,Hyde 生成 Markdown 文件,这些文件可以像 Jekyll(https://jekyllrb.com/)这样的静态页面生成器消费,Jekyll 是 GitHub 支持的工具
105 |
106 | \item
107 | Standardese (\url{https://github.com/standardese/standardese}):使用 libclang 编译代码,并提供 HTML、Markdown、LaTeX 和手册页格式的输出,有望成为下一个 Doxygen。
108 | \end{itemize}
109 | \end{myNotic}
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/book/content/chapter13/5.tex:
--------------------------------------------------------------------------------
1 | 本章中,深入探讨了将Doxygen这一强大的文档生成工具添加到您的CMake项目中,并提升其吸引力的实际操作。尽管这项任务看起来有些令人生畏,但实际上它是相当易于管理的,并且能显著提升解决方案中信息的流动性和清晰度。各位到后面会发现,特别是当个人或团队成员努力理解应用程序中的复杂关系时,投入时间和精力添加和维护文档是一笔值得的投资。探讨了如何使用CMake内置的Doxygen支持来实际生成文档之后,我们稍微转变了一下方向,确保文档不仅可读,而且易读。
2 |
3 | 由于过时的设计可能对眼睛造成负担,我们探讨了生成HTML的替代外观。这是通过使用Doxygen Awesome扩展来完成的。为了启用它带来的增强功能,我们通过添加必要的javascript来自定义标准页头。
4 |
5 | 通过生成文档,确保了它与实际代码的接近性,这使得将书面解释与逻辑保持同步变得更加容易,尤其是它们都在同一个文件中。此外,作为开发者,可能同时在处理许多任务和细节。文档作为一种记忆辅助工具,帮助保留和回忆项目的复杂性。记住,“即使是最短的铅笔也比最长的记忆要长。”帮助一下自己——把写下来,从而取得成功。
6 |
7 | 总结来说,本章强调了Doxygen在项目管理工具中的价值,有助于团队的理解和沟通。
8 |
9 | 下一章中,我将介绍如何使用CMake自动化项目的打包和安装,进一步提升项目的管理技巧。
--------------------------------------------------------------------------------
/book/content/chapter13/6.tex:
--------------------------------------------------------------------------------
1 |
2 |
3 | \begin{itemize}
4 | \item
5 | Doxygen 官方网站:
6 |
7 | \url{https://www.doxygen.nl/}
8 |
9 | \item
10 | FindDoxygen 查找模块文档:
11 |
12 | \url{https://cmake.org/cmake/help/latest/module/FindDoxygen.html}
13 |
14 | \item
15 | Doxygen 的文档:
16 |
17 | \url{https://www.doxygen.nl/manual/docblocks.html#specialblock}
18 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter13/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter13/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter13/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter13/images/2.png
--------------------------------------------------------------------------------
/book/content/chapter13/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter13/images/3.png
--------------------------------------------------------------------------------
/book/content/chapter13/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter13/images/4.png
--------------------------------------------------------------------------------
/book/content/chapter14/0.tex:
--------------------------------------------------------------------------------
1 | 我们的项目已经构建、测试并记录在案。现在,到了将其发布给用户的时候。本章主要关注需要采取的最后两个步骤:安装和打包。这些是我们迄今为止所学的一切基础上的高级技术:管理目标和它们的依赖关系,短暂的使用需求,生成器表达式等。
2 |
3 | 安装使得项目能够在整个系统中发现和访问。我们将介绍如何在不进行安装的情况下导出目标,以供其他项目使用;以及如何安装项目,以便于整个系统轻松访问。将了解如何配置项目,以自动将各种工件类型放置到适当的目录中。为了处理更高级的场景,将介绍用于安装文件和目录,以及执行自定义脚本和 CMake 命令的低层命令。
4 |
5 | 接下来,将探索设置可重用的 CMake 包,其他项目可以使用 find\_package() 命令来发现它们。我们将解释如何确保目标,和定义特定文件的系统位置。我们还将讨论如何编写基本和高级的配置文件,以及与包相关联的版本文件。然后,为了模块化,我们将简要介绍组件的概念,这既适用于 CMake 包也适用于 install() 命令。所有这些准备工作将为本章最后要介绍的方面铺平道路:使用 CPack 生成各种操作系统中的包管理器都能识别的存档、安装程序、捆绑包和包。这些包可以分发预构建的工件、可执行文件和库。这是最终用户开始使用软件的最简单方式。
6 |
7 | 本章中,将包含以下内容:
8 |
9 | \begin{itemize}
10 | \item
11 | 无需安装即可导出
12 |
13 | \item
14 | 在系统上安装项目
15 |
16 | \item
17 | 创建可重用包
18 |
19 | \item
20 | 定义组件
21 |
22 | \item
23 | 使用 CPack 打包
24 | \end{itemize}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/book/content/chapter14/1.tex:
--------------------------------------------------------------------------------
1 | 可以在GitHub上的\url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch14}找到本章中出现的代码文件。
2 |
3 | 要构建本书提供的示例,请使用以下推荐命令:
4 |
5 | \begin{shell}
6 | cmake -B -S
7 | cmake --build
8 | \end{shell}
9 |
10 | 要安装示例,请使用以下命令:
11 |
12 | \begin{shell}
13 | cmake --install
14 | \end{shell}
15 |
16 | 请确保将和占位符替换为适当的路径。提醒一下:是目标/输出目录的路径,而是源码所在的路径。
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/book/content/chapter14/2.tex:
--------------------------------------------------------------------------------
1 | 我们如何使项目A的目标对项目B可用?通常,我们会使用find\_package()命令,但这需要创建一个包并在系统上安装。虽然这种方法有用,但是略显麻烦。有时,我们构建一个项目后,希望有一种简单的方法使得项目中的目标可以被其他项目使用。
2 |
3 | 一种简单的方法是直接在项目B中包含项目A的主CMakeLists.txt,该文件中包含了所有目标定义。然而,这个文件还会包括全局配置、具有副作用的CMake命令、额外的依赖项,以及B项目可能不需要的目标(比如单元测试)。因此,这种方法并不太好。我们可以使用如下的方式,在项目A中导出一个.cmake文件(称为目标导出文件),然后在项目B中通过include()命令包含该文件,实现该目的:
4 |
5 | \begin{cmake}
6 | cmake_minimum_required(VERSION 3.26.0)
7 | project(B)
8 | include(/path/to/A/TargetsOfA.cmake)
9 | \end{cmake}
10 |
11 | 项目A的目标导出文件中包含使用add\_library()和add\_executable()等命令定义的所有目标,并包含为这些目标设置的属性。
12 |
13 | 项目A中使用如下命令生成目标导出文件,命令中必须在TARGETS关键字后指定要导出的所有目标,并在FILE后提供目标文件名。其他参数是可选的:
14 |
15 | \begin{shell}
16 | export(TARGETS [target1 [target2 [...]]]
17 | [NAMESPACE ] [APPEND] FILE
18 | [EXPORT_LINK_INTERFACE_LIBRARIES]
19 | )
20 | \end{shell}
21 |
22 | 其中的参数:
23 |
24 | \begin{itemize}
25 | \item
26 | NAMESPACE: 目标导出文件中,所有目标前都会包含NAMESPACE指定的前缀,这些目标被导入其他项目使用时,也应该包含该前缀。推荐为导出的目标都指定前缀,以在使用时可以知道目标是从其他项目导入的。
27 |
28 | \item
29 | APPEND: 将导出的内容追加到 FILE 指定的文件,避免写入前清除原文件的内容。
30 |
31 | \item
32 | EXPORT\_LINK\_INTERFACE\_LIBRARIES: 同时导出目标的链接依赖项(包括导入的和特定配置的变体)。
33 | \end{itemize}
34 |
35 | 让我们将这种导出方法应用到Calc库示例中,该库提供了两个简单的方法:
36 |
37 | \filename{ch14/01-export/src/include/calc/basic.h}
38 |
39 | \begin{cpp}
40 | #pragma once
41 | int Sum(int a, int b);
42 | int Multiply(int a, int b);
43 | \end{cpp}
44 |
45 | 首先需要声明Calc目标以便我们有东西可以导出:
46 |
47 | \filename{ch14/01-export/src/CMakeLists.txt}
48 |
49 | \begin{cmake}
50 | add_library(calc STATIC basic.cpp)
51 | target_include_directories(calc INTERFACE include)
52 | \end{cmake}
53 |
54 | 然后,使用export(TARGETS)命令生成导出文件:
55 |
56 | \filename{ch14/01-export/CMakeLists.txt (fragment)}
57 |
58 | \begin{cmake}
59 | cmake_minimum_required(VERSION 3.26)
60 | project(ExportCalc CXX)
61 | add_subdirectory(src bin)
62 | set(EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake")
63 | export(TARGETS calc
64 | FILE "${EXPORT_DIR}/CalcTargets.cmake"
65 | NAMESPACE Calc::
66 | )
67 | \end{cmake}
68 |
69 | 如果想要将目标声明文件导到构建树的cmake子目录中(遵循.cmake文件的约定),为了能够复用路径,将出目录路径设置在EXPORT\_DIR变量中。然后,调用export()来生成目标声明文件CalcTargets.cmake,其中包含了定义的calc目标。对于使用include包含这个文件的项目,可以直接通过Calc::calc使用目标。
70 |
71 | 注意,这个导出文件还不是一个包。更重要的是,这个文件中的所有路径都是绝对路径,并且硬编码到构建树中,这使得它们不可重定位。
72 |
73 | export()命令还有一个使用EXPORT关键字的简写版本:
74 |
75 | \begin{shell}
76 | export(EXPORT [NAMESPACE ] [FILE ])
77 | \end{shell}
78 |
79 | 然而,它需要一个预定义的导出名称,而不是要导出的目标列表, 这样的实例是由install(TARGETS)创建的目标命名列表。
80 |
81 | 以下是如何在实践中使用这种简写的示例:
82 |
83 | \filename{ch14/01-export/CMakeLists.txt (continued)}
84 |
85 | \begin{cmake}
86 | install(TARGETS calc EXPORT CalcTargets)
87 | export(EXPORT CalcTargets
88 | FILE "${EXPORT_DIR}/CalcTargets2.cmake"
89 | NAMESPACE Calc::
90 | )
91 | \end{cmake}
92 |
93 | 这段代码的工作方式与前面的示例类似,但现在它在export()和install()命令之间共享了同一个目标列表。
94 |
95 | 两种生成导出文件的方法产生类似的结果,包括一些样板代码和定义目标的几行。将设置为构建树路径后,将创建一个类似以下的目标导出文件:
96 |
97 | \filename{/cmake/CalcTargets.cmake (片段)}
98 |
99 | \begin{cmake}
100 | # Create imported target Calc::calc
101 | add_library(Calc::calc STATIC IMPORTED)
102 | set_target_properties(Calc::calc PROPERTIES
103 | INTERFACE_INCLUDE_DIRECTORIES
104 | "//include"
105 | )
106 | # Import target "Calc::calc" for configuration ""
107 | set_property(TARGET Calc::calc APPEND PROPERTY
108 | IMPORTED_CONFIGURATIONS NOCONFIG
109 | )
110 | set_target_properties(Calc::calc PROPERTIES
111 | IMPORTED_LINK_INTERFACE_LANGUAGES_NOCONFIG "CXX"
112 | IMPORTED_LOCATION_NOCONFIG "//libcalc.a"
113 | )
114 | \end{cmake}
115 |
116 | 通常,我们不会编辑甚至打开这个文件,但重要的是文件中的路径为硬编码的(参见突出显示的行)。在当前的形式下,构建的项目不可重定位。要改变这一点,需要采取一些其他步骤。下一节中,我们将解释什么是重定位,及其重要性。
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/book/content/chapter14/5.tex:
--------------------------------------------------------------------------------
1 |
2 | 我们将从解释术语“组件”可能引起的混淆开始。考虑find\_package()的完整签名:
3 |
4 | \begin{shell}
5 | find_package(
6 | [version] [EXACT] [QUIET] [MODULE] [REQUIRED]
7 | [[COMPONENTS] [components...]]
8 | [OPTIONAL_COMPONENTS components...]
9 | [NO_POLICY_SCOPE]
10 | )
11 | \end{shell}
12 |
13 | 重要的是不要将这里提到的组件,与install()命令中使用的COMPONENT关键字混淆。尽管它们名称相同,但是不同的概念,必须分开理解。我们将在以下子节中进一步探讨这一点。
14 |
15 | \mySubsubsection{14.5.1.}{如何在find\_package()中使用组件}
16 |
17 | 当调用带有COMPONENTS或OPTIONAL\_COMPONENTS列表的find\_package()时,告诉CMake我们只对提供这些组件的包感兴趣。然而,理解验证这一要求是包的责任至关重要。如果包提供商没有在配置文件中实现必要的检查,则不会按预期进行。
18 |
19 | 请求的组件通过\_FIND\_COMPONENTS变量传递给配置文件(包括可选和不可选的)。对于每个不可选组件,都会设置一个\_FIND\_REQUIRED\_变量。包作者可以编写宏来扫描这个列表,并验证所有必需组件的提供情况。check\_required\_components()函数就为此目的服务。当找到必要的组件时,配置文件应设置\_\_FOUND变量。文件末尾的一个宏将验证是否设置了所有必需的变量。
20 |
21 | \mySubsubsection{14.5.2.}{如何在install()命令中使用组件}
22 |
23 | 并非在所有情况下都需要安装所有生成的工件。例如,一个项目可能为了开发而安装静态库和公共头文件,但默认情况下,可能只需要为运行时安装一个共享库。为了启用这种双重行为,可以使用COMPONENT关键字将工件分组在install()命令下的一个通用名称下。有兴趣限制安装到特定组件的用户可以通过执行以下区分大小写的命令来实现:
24 |
25 | \begin{shell}
26 | cmake --install
27 | --component= --component=
28 | \end{shell}
29 |
30 | 为工件分配COMPONENT关键字并不会自动将其从默认安装中排除。要实现这种排除,必须添加EXCLUDE\_FROM\_ALL关键字。
31 |
32 | 让我们在代码示例中探讨这个概念:
33 |
34 | \filename{ch14/13-components/CMakeLists.txt (片段)}
35 |
36 | \begin{cmake}
37 | install(TARGETS calc EXPORT CalcTargets
38 | ARCHIVE
39 | COMPONENT lib
40 | FILE_SET HEADERS
41 | COMPONENT headers
42 | )
43 | install(EXPORT CalcTargets
44 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/calc/cmake
45 | NAMESPACE Calc::
46 | COMPONENT lib
47 | )
48 | install(CODE "MESSAGE(\"Installing 'extra' component\")"
49 | COMPONENT extra
50 | EXCLUDE_FROM_ALL
51 | )
52 | \end{cmake}
53 |
54 | 前面的安装命令定义了以下组件:
55 |
56 | \begin{itemize}
57 | \item
58 | lib: 这包含静态库和目标导出文件。默认安装。
59 |
60 | \item
61 | headers: 这包含C++头文件。也默认安装。
62 |
63 | \item
64 | extra: 这执行一段代码以打印消息。不默认安装。
65 | \end{itemize}
66 |
67 | 让我们重申:
68 |
69 | \begin{itemize}
70 | \item
71 | cmake -{}-install 没有 -{}-component 参数将安装lib和headers组件。
72 |
73 | \item
74 | cmake -{}-install -{}-component headers 将只安装公共头文件。
75 |
76 | \item
77 | cmake -{}-install -{}-component extra 将打印一条消息,其他命令不会打印(EXCLUDE\_FROM\_ALL关键字阻止了这一点)。
78 | \end{itemize}
79 |
80 | 如果没有为安装的工件指定COMPONENT关键字,它默认为未指定,由CMAKE\_INSTALL\_DEFAULT\_COMPONENT\_NAME变量定义。
81 |
82 | \begin{myNotic}{Note}
83 | 由于无法从cmake命令行列出所有可用组件,因此记录包的所有组件对于用户来说可能非常有帮助。安装"README"文件是放置这些信息的绝佳位置。
84 | \end{myNotic}
85 |
86 | 如果cmake使用-{}-component参数调用一个不存在的组件,命令将成功完成,不会有警告或错误,但不会安装任何东西。
87 |
88 | 将我们的安装划分为组件,使得用户可以选择性地安装包的某一部分。现在来管理版本化共享库的符号链接,这是一个优化你的安装过程的有用功能。
89 |
90 | \mySubsubsection{14.5.3.}{管理版本化共享库的符号链接}
91 |
92 | 有些安装的目标平台可以使用符号链接,来帮助链接器发现共享库的当前安装版本。如通常创建 lib.so 符号链接链接到 lib.so.1 实际库文件(可以使用set\_property()指令给动态库目标指定版本,这时该目标包含有数字后缀的实际库文件和无数字后缀的链接到实际库文件的符号链接),之后就可以通过向链接器统一传递 -l 参数(不需要指定共享库的版本后缀)来链接这样的库。
93 |
94 | CMake 的 install(TARGETS LIBRARY) 在安装时会同时安装符号链接和实际库文件。也可以只安装实际库文件,将符号链接的安装移到另一个 install() 命令中。这通过在这个块中添加 NAMELINK\_SKIP 来实现:
95 |
96 | \begin{shell}
97 | install(TARGETS LIBRARY
98 | COMPONENT cmp NAMELINK_SKIP)
99 | \end{shell}
100 |
101 | 以上指令在执行安装时,只会安装 中的实际库文件,且 cmp 组件中只包含实际库文件。
102 |
103 | 可以使用有 NAMELINK\_ONLY 关键词的 install() 命令只安装符号链接文件:
104 |
105 | \begin{shell}
106 | install(TARGETS LIBRARY
107 | COMPONENT lnk NAMELINK_ONLY)
108 | \end{shell}
109 |
110 | 以上指令在执行安装时,只会安装 中的符号链接,且 lnk 组件中只包含符号链接。
111 |
112 | 以上的效果也可以通过使用 NAMELINK\_COMPONENT 关键词来达到。以下命令执行后,会同时安装 中的实际库文件和符号链接,但是 cmp 组件中只包含实际库文件, lnk 组件中只包含符号链接:
113 |
114 | \begin{shell}
115 | install(TARGETS LIBRARY
116 | COMPONENT cmp NAMELINK_COMPONENT lnk)
117 | \end{shell}
118 |
119 | 现在我们已经配置了自动安装过程,可以使用随 CMake 一起提供的 CPack 工具来为用户提供预构建的工件。
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/book/content/chapter14/6.tex:
--------------------------------------------------------------------------------
1 | 虽然从源代码构建项目有其好处,但对于最终用户特别是非开发人员来说,这可能会既耗时又复杂。一种更方便的分发方法是使用二进制包,其中包含了编译后的制品和其他必要的静态文件。CMake 支持使用名为 cpack 的命令行工具来生成此类包。
2 |
3 | 要生成一个包,需要为目标平台和包类型选择一个合适的包生成器。不要将包生成器与像 Unix Makefiles 或 Visual Studio 这样的构建系统生成器混淆。
4 |
5 | 下表列出了可用的包生成器:
6 |
7 | % Please add the following required packages to your document preamble:
8 | % \usepackage{longtable}
9 | % Note: It may be necessary to compile the document several times to get a multi-page table to line up properly
10 | \begin{longtable}{|l|l|l|}
11 | \hline
12 | \textbf{生成器名称} & \textbf{生成的文件类型} & \textbf{平台} \\ \hline
13 | \endfirsthead
14 | %
15 | \endhead
16 | %
17 | Archive &
18 | \begin{tabular}[c]{@{}l@{}}7Z, 7zip - (.7z)\\ TBZ2 (.tar.bz2)\\ TGZ (.tar.gz)\\ TXZ (.tar.xz)\\ TZ (.tar.Z)\\ TZST (.tar.zst)\\ ZIP (.zip)\end{tabular} &
19 | 跨平台 \\ \hline
20 | Bundle & macOs Bundle (.bundle) & macOS \\ \hline
21 | Cygwin & Cygwin packages & Cygwin \\ \hline
22 | DEB & Debian packages (.deb) & Linux \\ \hline
23 | External & 第三方打包程序使用的 JSON (.json) 文件 & 跨平台 \\ \hline
24 | FreeBSD & PKG (.pkg) & *BSD, Linux, macOS \\ \hline
25 | IFW & QT 安装程序二进制文件 & Linux, Windows, macOS \\ \hline
26 | NSIS & Binary (.exe) & Windows \\ \hline
27 | NuGet & NuGet 包 (.nupkg) & Windows \\ \hline
28 | productbuild & PKG (.pkg) & macOS \\ \hline
29 | RPM & RPM (.rpm) & Linux \\ \hline
30 | WIX & Microsoft Installer (.msi) & Windows \\ \hline
31 | \end{longtable}
32 |
33 | \begin{center}
34 | 表 14.3: 可用的包生成器
35 | \end{center}
36 |
37 | 大多数这些生成器都有广泛的配置选项。本书不打算深入探讨所有细节,相应信息可以在“扩展阅读”部分找到更多信息。
38 |
39 | 为了使用 CPack,需要使用必要的 install() 命令来配置项目的安装,并构建项目。CPack 会根据构建树中的 CPackConfig.cmake 文件准备二进制包。虽然可以手动创建这个文件,但在项目的列表文件中使用 include(CPack) 更加简便,它会在构建树中生成配置文件并提供所需的默认值。
40 |
41 | 让我们扩展 13-components 示例以供 CPack 使用:
42 |
43 | \filename{ch14/14-cpack/CMakeLists.txt (fragment)}
44 |
45 | \begin{cmake}
46 | cmake_minimum_required(VERSION 3.26)
47 | project(CPackPackage VERSION 1.2.3 LANGUAGES CXX)
48 | include(GNUInstallDirs)
49 | add_subdirectory(src bin)
50 | install(...)
51 | install(...)
52 | install(...)
53 | set(CPACK_PACKAGE_VENDOR "Rafal Swidzinski")
54 | set(CPACK_PACKAGE_CONTACT "email@example.com")
55 | set(CPACK_PACKAGE_DESCRIPTION "Simple Calculator")
56 | include(CPack)
57 | \end{cmake}
58 |
59 | CPack 模块从 project() 命令中提取以下变量:
60 |
61 | \begin{itemize}
62 | \item
63 | CPACK\_PACKAGE\_NAME
64 |
65 | \item
66 | CPACK\_PACKAGE\_VERSION
67 |
68 | \item
69 | CPACK\_PACKAGE\_FILE\_NAME
70 | \end{itemize}
71 |
72 | The CPACK\_PACKAGE\_FILE\_NAME 存储了包名的结构:
73 |
74 | \begin{shell}
75 | $CPACK_PACKAGE_NAME-$CPACK_PACKAGE_VERSION-$CPACK_SYSTEM_NAME
76 | \end{shell}
77 |
78 | CPACK\_SYSTEM\_NAME 是目标操作系统的名称,例如 Linux 或 win32。例如,在 Debian 上执行 ZIP 生成器时,CPack 将生成一个名为 CPackPackage-1.2.3-Linux.zip 的文件。
79 |
80 | 要在构建项目后生成包,请转到项目的构建树并运行:
81 |
82 | \begin{shell}
83 | cpack []
84 | \end{shell}
85 |
86 | CPack 从 CPackConfig.cmake 文件读取选项,可以覆盖这些设置:
87 |
88 | \begin{itemize}
89 | \item
90 | -G : 以分号分隔的包生成器列表。默认值可以在 CPackConfig.cmake 中的 CPACK\_GENERATOR 变量中指定。
91 |
92 | \item
93 | -C : 以分号分隔的构建配置列表(debug, release),用于生成包(对于多配置构建系统生成器是必需的)。
94 |
95 | \item
96 | -D =: 此选项覆盖 CPackConfig.cmake 文件中设置的变量。
97 |
98 | \item
99 | -{}-config : 此选项使用指定的配置文件代替默认的 CPackConfig.cmake 文件。
100 | cmake.
101 |
102 | \item
103 | -{}-verbose, -V: 此选项提供详细的输出。
104 |
105 | \item
106 | -P : 此选项覆盖包名。
107 |
108 | \item
109 | -R : 此选项覆盖包版本。
110 |
111 | \item
112 | -{}-vendor : 此选项覆盖包供应商。
113 |
114 | \item
115 | -B : 此选项指定 cpack 的输出目录(默认情况下,这将是当前工作目录)。
116 | \end{itemize}
117 |
118 | 让我们尝试为我们的 14-cpack 示例项目生成包。我们将使用 ZIP、7Z 和 Debian 包生成器:
119 |
120 | \begin{shell}
121 | cpack -G "ZIP;7Z;DEB" -B packages
122 | \end{shell}
123 |
124 | 应该会得到以下这些包:
125 |
126 | \begin{itemize}
127 | \item
128 | CPackPackage-1.2.3-Linux.7z
129 |
130 | \item
131 | CPackPackage-1.2.3-Linux.deb
132 |
133 | \item
134 | CPackPackage-1.2.3-Linux.zip
135 | \end{itemize}
136 |
137 | 这些二进制包已准备好发布在项目网站上、GitHub Release页面中,或作为最终用户的包存储库。
138 |
--------------------------------------------------------------------------------
/book/content/chapter14/7.tex:
--------------------------------------------------------------------------------
1 | 编写跨平台安装脚本的复杂性可能会令人望而却步,但 CMake 显著简化了这一任务。尽管它需要一些初始设置,CMake 流程化了这一过程,与这本书中探讨的概念和技术无缝集成。
2 |
3 | 我们从理解如何从项目中导出 CMake 目标开始,使得它们可以在不需要安装的情况下在其他项目中使用。接下来,深入了解已经为导出配置的项目的安装。探讨安装基础时,专注于一个关键方面:安装 CMake 目标。现在掌握了 CMake 如何为不同工件类型分配不同目的地,以及公共头文件的特殊考虑。我们还检查了 install() 命令的其他模式,包括安装文件、程序和目录,以及在安装过程中执行脚本。
4 |
5 | 然后,CMake可创建重用包。我们探索了如何使项目目标可重定位,从而方便用户定义安装位置。这包括创建可以通过 find\_package() 使用的完全定义的包,涉及准备目标导出文件、配置文件和版本文件。考虑到不同用户的需求,了解了如何将工件和操作分组到安装组件中,将它们与 CMake 包的组件区分开来。我们的探索最终以 CPack 的介绍达到尾声,我们了解了如何准备基本的二进制包,提供了一种有效的方法来分发预编译的软件。虽然掌握 CMake 中安装和打包的细微之处是一个持续的过程,但这一章为我们奠定了坚实的基础。它使我们能够处理常见场景,并且自信地进一步深入。
6 |
7 | 下一章中,我们将运用我们积累的知识,通过创建一个连贯的、专业级别的项目,展示这些 CMake 技术的实际应用。
--------------------------------------------------------------------------------
/book/content/chapter14/8.tex:
--------------------------------------------------------------------------------
1 |
2 |
3 | \begin{itemize}
4 | \item
5 | GNU目标编码标准
6 |
7 | \url{https://www.gnu.org/prep/standards/html_node/Directory-Variables.html}
8 |
9 | \item
10 | 讨论使用FILE\_SET的新关键字:
11 |
12 | \url{https://gitlab.kitware.com/cmake/cmake/-/issues/22468#note_991860}
13 |
14 | \item
15 | 如何安装共享库:
16 |
17 | \url{https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html}
18 |
19 | \item
20 | 创建可重定位的包:
21 |
22 | \url{https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html#creating-relocatable-packages}
23 |
24 | \item
25 | find\_package()搜索配置文件时扫描的路径列表:
26 |
27 | \url{https://cmake.org/cmake/help/latest/command/find_package.html#configmode-search-procedure}
28 |
29 | \item
30 | CMakePackageConfigHelpers的完整文档:
31 |
32 | \url{https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html}
33 |
34 | \item
35 | CPack包生成器:
36 |
37 | \url{https://cmake.org/cmake/help/latest/manual/cpack-generators.7.html}
38 |
39 | \item
40 | 关于不同平台首选包生成器的讨论:
41 |
42 | \url{https://stackoverflow.com/a/46013099}
43 |
44 | \item
45 | CPack实用模块文档:
46 |
47 | \url{https://cmake.org/cmake/help/latest/module/CPack.html}
48 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter14/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter14/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter15/0.tex:
--------------------------------------------------------------------------------
1 | 我们已经汇集了构建专业项目所需的所有必要知识,包括结构化、构建、依赖管理、测试、分析、安装和打包等方面。现在,是时候运用这些技能来创建一个连贯且专业的项目了。重要的是要理解,即使是简单的程序也能从自动化的质量检查,将原始代码转变为完整解决方案的无缝流程中获益。确实,实施这些检查和流程是一项重大的投资,这需要许多步骤来正确地设置一切。当将这些机制添加到现有的代码库时尤其如此,这些代码库往往庞大且复杂。因此,从一开始就使用 CMake 并尽早建立所有必要的过程是非常有益的。这样配置起来更容易,也更高效,因为这些质量控制和构建自动化最终无论如何都需要集成到长期项目中。
2 |
3 | 本章中,将开发一个新的解决方案,尽可能地保持简单,同时充分利用本书迄今为止讨论过的 CMake 实践。为了简化问题,仅实现一个实用的功能——即两个数相加。这样的基础业务代码可使我们能够专注于前面章节中学到的与构建相关的项目方面。为了处理一个与构建更加相关的挑战性问题,此项目将包含一个库和一个可执行文件。
4 |
5 | 该库将处理内部业务逻辑,并作为 CMake 包供其他项目使用。而可执行文件,旨在供最终用户使用,将提供一个用户界面来展示该库的功能。
6 |
7 | 本章中,将包含以下内容:
8 |
9 | \begin{itemize}
10 | \item
11 | 规划工作
12 |
13 | \item
14 | 项目布局
15 |
16 | \item
17 | 构建和管理依赖项
18 |
19 | \item
20 | 测试和程序分析
21 |
22 | \item
23 | 安装和打包
24 |
25 | \item
26 | 提供文档
27 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter15/1.tex:
--------------------------------------------------------------------------------
1 | 可以在 GitHub 上找到本章中存在的代码文件,地址为 \url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch15}。
2 |
3 | 为了构建本书提供的示例,请使用推荐的命令:
4 |
5 | \begin{shell}
6 | cmake -B -S
7 | cmake --build
8 | \end{shell}
9 |
10 | 请确保将占位符 和 替换为适当的路径。
11 | 作为提醒: 是指向目标/输出目录的路径,而 是源代码所在的位置的路径。
12 |
13 | 本章使用 GCC 编译,以提供与使用 lcov 工具收集结果的代码覆盖率仪器兼容性。如果想使用 llvm 或其他工具链进行编译,请确保根据需要调整覆盖率处理过程。
14 |
15 | 要运行测试,请执行以下命令:
16 |
17 | \begin{shell}
18 | ctest --test-dir
19 | \end{shell}
20 |
21 | 或者,只需从 build tree 目录执行:
22 |
23 | \begin{shell}
24 | ctest
25 | \end{shell}
26 |
27 | 本章中,测试结果将输出到 test 子目录中。
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/book/content/chapter15/2.tex:
--------------------------------------------------------------------------------
1 | 本章将构建的软件并不复杂——将创建一个简单的计算器,可以实现两个数字的相加(图15.1)。这是一个控制台应用程序,具有文本用户界面,利用第三方库和独立的计算库,这些库可以用于其他项目。尽管这个项目可能没有重要的实际应用,但其简单性非常适合演示本书讨论的各种技术应用。
2 |
3 | \myGraphic{0.4}{content/chapter15/images/1.png}{图15.1:项目在支持鼠标的终端中执行的文本用户界面}
4 |
5 | 通常,项目要么生成面向用户的可执行文件,要么为开发者生成库。项目同时产生这两者的情况较少,尽管这种情况确实存在。例如,一些应用程序附带了独立的SDK或库以帮助开发插件。另一个例子是附带了使用示例的库。我们的项目属于后者,展示了库的功能。
6 |
7 | 我们将通过回顾章节列表,回忆每个章节的内容,并选择将用来构建应用程序的技术和工具来开始规划:
8 |
9 | \begin{itemize}
10 | \item
11 | 第1章,CMake入门:
12 |
13 | 本章提供了关于CMake的基本细节,包括安装和用于构建项目的命令行使用。还包含了关于项目文件的基本信息,如作用、典型的命名约定和特殊性。
14 |
15 | \item
16 | 第2章,CMake语言:
17 |
18 | 我们介绍了编写正确的CMake列表文件和脚本所需的工具,包含了代码基础,如注释、命令调用和参数。我们解释了变量、列表和控制结构,引入了几个有用的命令。这个基础在我们的项目中至关重要。
19 |
20 | \item
21 | 第3章,在IDE中使用CMake:
22 |
23 | 我们讨论了三个IDE——CLion、VS Code和Visual Studio IDE,介绍了它们的优点。在最终项目中,选择IDE(或不选择)由你决定。做出决定后,可以在这个项目中使用Dev容器开始,只需几个步骤就可以构建Docker镜像(或者直接从Docker Hub获取)。在容器中运行镜像可以确保开发环境与生产环境相匹配。
24 |
25 | \item
26 | 第4章,设置CMake项目:
27 |
28 | 配置项目至关重要,其决定了将生效的CMake策略、命名、版本控制和编程语言。我们将使用本章来影响构建过程的基本行为。
29 |
30 | 我们还将遵循建立的项目分区和结构来确定目录和文件的布局,并利用系统发现变量以适应不同的构建环境。工具链配置是另一个关键方面,可以强制使用特定的C++版本和编译器支持的标准。按照章节的建议,我们将禁用源内构建以保持工作区清洁。
31 |
32 | \item
33 | 第5章,使用目标:
34 |
35 | 了解到每个现代CMake项目都广泛使用目标,当然也会应用目标来定义一些库和可执行文件(用于测试和生产),这将使项目保持组织并确保我们遵循DRY(不要重复自己)的原则。对目标属性和传递使用要求(传播属性)的了解,将能够使配置接近目标定义。
36 |
37 | \item
38 | 第6章,使用生成器表达式:
39 |
40 | 生成器表达式在项目中大量使用,力求使这些表达式尽可能简单。项目将包含自定义命令以生成Valgrind和覆盖率报告的文件。此外,还将使用目标钩子,特别是PRE\_BUILD,来清理覆盖率检测过程产生的.gcda文件。
41 |
42 | \item
43 | 第7章,使用CMake编译C++源文件:
44 |
45 | 没有C++项目的编译是不可能的。基础知识相当简单,但CMake允许我们以许多方式调整这个过程:扩展目标源代码、配置优化器并提供调试信息。对于这个项目,默认的编译标志就可以了,也研究了一下预处理器:
46 |
47 | \begin{itemize}
48 | \item
49 | 我们将构建元数据(项目版本、构建时间和Git提交SHA)存储在编译后的可执行文件中并向用户展示。
50 |
51 | \item
52 | 我们将启用预编译头文件。在如此小的项目中,这并不是真正必要的,但它将帮助我们练习这个概念。
53 | \end{itemize}
54 |
55 | 不需要Unity构建。
56 |
57 | \item
58 | 第8章,链接可执行文件和库:
59 |
60 | 我们将获得默认情况下对项目都有用的链接的一般信息。此外,由于这个项目包含一个库,将明确引用以下特定构建指令:
61 |
62 | \begin{itemize}
63 | \item
64 | 用于测试和开发的静态库
65 |
66 | \item
67 | 用于发布的共享库
68 | \end{itemize}
69 |
70 | 本章还概述了如何隔离main()函数以用于测试目的,我们将采用这种做法。
71 |
72 | \item
73 | 第9章,管理依赖关系:
74 |
75 | 为了增强项目的吸引力,将引入一个外部依赖:一个基于文本的用户界面库。第9章探讨了管理依赖关系的各种方法。选择将很简单:FetchContent实用模块通常推荐且最方便。
76 |
77 | \item
78 | 第10章,使用C++20模块:
79 |
80 | 尽管我们已经探讨了使用C++20模块,以及支持此功能的环境要求(CMake 3.28,最新编译器),但其广泛支持仍然不足。为了确保项目的可访问性,我们暂时不会引入模块。
81 |
82 | \item
83 | 第11章:测试框架
84 |
85 | 实施适当的自动化测试,对于确保解决方案质量随时间保持一致至关重要。我们将集成CTest并组织项目以方便测试,并应用之前提到的main()函数分离方法。
86 |
87 | 本章将讨论两种测试框架:Catch2和GTest与GMock;我们将使用后者。为了获取覆盖率的详细信息,我们将使用LCOV生成HTML报告。
88 |
89 | \item
90 | 第12章:程序分析工具
91 |
92 | 对于静态分析,可以从一系列工具中选择:Clang-Tidy、Cpplint、Cppcheck、include-what-you-use(IWYU)和link-what-you-use(LWYU)。我们将选择Cppcheck,因为Clang-Tidy与使用GCC构建的预编译头文件兼容性较差。
93 |
94 | 动态分析将使用Valgrind的Memcheck工具,并配合Memcheck-cover包装器来生成HTML报告。此外,在构建过程中,源码将自动通过ClangFormat进行格式化。
95 |
96 | \item
97 | 第13章:文档生成
98 |
99 | 提供文档对于我们项目中的库来说是必不可少的。CMake支持使用Doxygen自动化生成文档。我们将采用这种方法,并在设计中加入doxygen-awesome-css主题以更新样式。
100 |
101 | \item
102 | 第14章:安装与打包
103 |
104 | 最后,将配置解决方案的安装和打包,并准备文件形成包,包括目标定义。安装这些内容及构建目标产生的工件到合适的目录中,通过包含GNUInstallDirs模块实现。还将配置一些组件以模块化解决方案,并为CPack做好准备。
105 | \end{itemize}
106 |
107 | 专业的项目通常会附带一些文本文件:README、LICENSE、INSTALL等。我们将在章节末尾简要介绍这些文件。
108 |
109 | 为了简化流程,不会实现自定义逻辑来检查所有必需的工具和依赖项是否可用。我们将依赖于CMake来显示其诊断信息并告诉用户缺少什么。如果项目获得了重要的关注,可能需要考虑添加这些机制以改善用户体验。
110 |
111 | 有了清晰的计划后,让我们讨论如何实际地构建项目结构,包括逻辑目标和目录结构。
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/book/content/chapter15/3.tex:
--------------------------------------------------------------------------------
1 |
2 | 为了构建任何项目,应该首先明确项目内部将创建哪些逻辑目标。这个案例中,将遵循下图所示的结构:
3 |
4 | \myGraphic{0.8}{content/chapter15/images/2.png}{图 15.2: 逻辑目标的结构}
5 |
6 | 按照构建顺序来探索这个结构。首先,编译calc\_obj,一个对象库。然后,将注意力转向静态库和共享库。
7 |
8 | \mySubsubsection{15.3.1.}{共享库与静态库}
9 |
10 | 第8章中,介绍了共享库和静态库。当多个程序使用相同的库时,共享库可以减少整体内存使用量,用户通常已经安装了流行的库或者知道如何快速安装它们。
11 |
12 | 更重要的是,共享库是独立的文件,必须放置在特定路径以便动态链接器能够找到它们。相比之下,静态库直接嵌入到可执行文件中,这使得使用更快,不需要额外步骤在内存中定位代码。
13 |
14 | 作为库的作者,可以决定提供静态库,还是共享库,也可以同时发布两个版本,将这个决定留给使用库的开发者。既然在积累知识,就来提供两个版本的库。
15 |
16 | calc\_test目标包含了单元测试以验证库的核心功能,它将使用静态库。虽然是从相同的对象文件构建两种类型的库,但无论使用哪种类型的库进行测试都可以接受,它们的功能相同。与calc\_console\_static目标相关的控制台应用程序将使用共享库。此目标还链接了一个外部依赖,即Arthur Sonzogni的Functional Terminal (X) User Interface (FTXUI)库(在扩展阅读部分中有GitHub项目的链接)。
17 |
18 | 最后两个目标,calc\_console和calc\_console\_test,旨在解决测试可执行文件中的常见问题:测试框架和可执行文件提供的多个入口点之间的冲突。为此,可将main()函数隔离到一个引导目标calc\_console中,该目标仅仅调用calc\_console\_static中的主要函数。
19 |
20 | 理解了必要的目标及其相互关系之后,下一步是通过适当的文件和目录来组织项目的结构。
21 |
22 | \mySubsubsection{15.3.2.}{项目文件结构}
23 |
24 | 项目由两个关键元素组成:calc库和calc\_console可执行文件。为了有效地组织项目,将采用以下目录结构:
25 |
26 | \begin{itemize}
27 | \item
28 | src 包含所有发布的目标的源代码和库头文件。
29 |
30 | \item
31 | test 包含上述库和可执行文件的测试。
32 |
33 | \item
34 | cmake 包含用于构建和安装项目的CMake辅助模块和文件。
35 |
36 | \item
37 | 根目录包含顶级配置和文档文件。
38 | \end{itemize}
39 |
40 | 这一结构(如图15.3所示)确保了职责的清晰划分,便于更轻松地导航和维护项目:
41 |
42 | \myGraphic{0.8}{content/chapter15/images/3.png}{图 15.3: 项目的目录结构}
43 |
44 | 下面是每个主要目录中的文件完整列表:
45 |
46 | % Please add the following required packages to your document preamble:
47 | % \usepackage{longtable}
48 | % Note: It may be necessary to compile the document several times to get a multi-page table to line up properly
49 | \begin{longtable}{|l|l|}
50 | \hline
51 | \textbf{根目录} &
52 | \textbf{./test} \\ \hline
53 | \endfirsthead
54 | %
55 | \endhead
56 | %
57 | CHANGELOG &
58 | CMakeLists.txt \\ \hline
59 | \textbf{CMakeLists.txt} &
60 | \textbf{calc/CMakeLists.txt} \\ \hline
61 | \begin{tabular}[c]{@{}l@{}}INSTALL\\ LICENSER\\ EADME.md\end{tabular} &
62 | \begin{tabular}[c]{@{}l@{}}calc/calc\_test.cpp\\ calc\_console/CMakeLists.txt\\ calc\_console/tui\_test.cpp\end{tabular} \\ \hline
63 | \textbf{./src} &
64 | \textbf{./cmake} \\ \hline
65 | \begin{tabular}[c]{@{}l@{}}CMakeLists.txt\\ calc/CMakeLists.txt\\ calc/CalcConfig.cmake\\ calc/basic.cpp\\ calc/include/calc/basic.h\\ calc\_console/CMakeLists.txt\\ calc\_console/bootstrap.cpp\\ calc\_console/include/tui.h\\ calc\_console/tui.cpp\end{tabular} &
66 | \begin{tabular}[c]{@{}l@{}}BuildInfo.cmake\\ Coverage.cmake\\ CppCheck.cmake\\ Doxygen.cmake\\ Format.cmake\\ GetFTXUI.cmake\\ Packaging.cmake\\ Memcheck.cmake\\ NoInSourceBuilds.cmake\\ Testing.cmake\\ buildinfo.h.in\\ doxygen\_extra\_headers\end{tabular} \\ \hline
67 | \end{longtable}
68 |
69 | \begin{center}
70 | 表 15.1: 项目的文件结构
71 | \end{center}
72 |
73 | 看起来CMake引入了相当大的开销,初始阶段cmake目录包含的内容比实际业务代码还要多,但随着项目的扩展,这种状况将会改变。建立干净且有组织的项目结构需要较大的初始努力,但请放心,这种投资在未来将带来显著的好处。
74 |
75 | 我们将在整个章节中详细介绍表15.1中列出的所有文件,以及其作用和在项目中的角色。这将分为四个步骤:构建、测试、安装和提供文档。
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/book/content/chapter15/6.tex:
--------------------------------------------------------------------------------
1 |
2 | 图15.6展示了如何配置项目以便进行安装和打包:
3 |
4 | \myGraphic{1.0}{content/chapter15/images/6.png}{图15.6: 配置安装和打包的文件}
5 |
6 | 顶层列表文件包含了Packaging模块:
7 |
8 | \filename{ch15/01-full-project/CMakeLists�txt (片段)}
9 |
10 | \begin{cmake}
11 | # ... configure project
12 | # ... enable testing
13 | # ... include src and test subdirectories
14 |
15 | include(Packaging)
16 | \end{cmake}
17 |
18 | Packaging模块详细说明了项目的包配置,这部分将在使用CPack进行打包的部分进行探讨。现在,关注的是安装三个主要组件:
19 |
20 | \begin{itemize}
21 | \item
22 | Calc库工件:静态和共享库、头文件以及目标导出文件
23 |
24 | \item
25 | Calc库的包定义配置文件
26 |
27 | \item
28 | Calc控制台可执行文件
29 | \end{itemize}
30 |
31 | 一切已经计划好了,现在是时候安装库。
32 |
33 | \mySubsubsection{15.6.1.}{安装库}
34 |
35 | 为了安装库,首先定义逻辑目标及其工件的目的地,利用GNUInstallDirs模块的默认值以避免手动指定路径。工件将分组到组件中,默认安装会安装所有文件,可以选择只安装运行时组件,并跳过开发工件:
36 |
37 | \filename{ch15/01-full-project/src/calc/CMakeLists.txt (续)}
38 |
39 | \begin{cmake}
40 | # ... calc library targets definition
41 | # ... configuration, testing, program analysis
42 |
43 | # Installation
44 | include(GNUInstallDirs)
45 | install(TARGETS calc_obj calc_shared calc_static
46 | EXPORT CalcLibrary
47 | ARCHIVE COMPONENT development
48 | LIBRARY COMPONENT runtime
49 | FILE_SET HEADERS COMPONENT runtime
50 | )
51 | \end{cmake}
52 |
53 | 对于UNIX系统,我们还配置了安装后注册共享库到ldconfig:
54 |
55 | \filename{ch15/01-full-project/src/calc/CMakeLists.txt (续)}
56 |
57 | \begin{cmake}
58 | if (UNIX)
59 | install(CODE "execute_process(COMMAND ldconfig)"
60 | COMPONENT runtime
61 | )
62 | endif()
63 | \end{cmake}
64 |
65 | 为了使其他CMake项目能够复用,将通过生成并安装一个目标导出文件和一个引用它的配置文件来打包库:
66 |
67 | \filename{ch15/01-full-project/src/calc/CMakeLists.txt (续)}
68 |
69 | \begin{cmake}
70 | install(EXPORT CalcLibrary
71 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/calc/cmake
72 | NAMESPACE Calc::
73 | COMPONENT runtime
74 | )
75 |
76 | install(FILES "CalcConfig.cmake"
77 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/calc/cmake
78 | )
79 | \end{cmake}
80 |
81 | 为了简化,CalcConfig.cmake文件保持最小化:
82 |
83 | \filename{ch15/01-full-project/src/calc/CalcConfig.cmake}
84 |
85 | \begin{cmake}
86 | include("${CMAKE_CURRENT_LIST_DIR}/CalcLibrary.cmake")
87 | \end{cmake}
88 |
89 | 这个文件位于src/calc目录下,它只包含了库目标。如果还有其他目录的目标定义,比如calc\_console,通常会把CalcConfig.cmake放在顶层目录或src目录下。
90 |
91 | 现在,库已经准备好通过cmake -{}-install命令在构建项目后进行安装。不过,还需要配置可执行文件的安装。
92 |
93 | \mySubsubsection{15.6.2.}{可执行文件的安装}
94 |
95 | 当然,我们希望用户能够在他们的系统中使用可执行文件,因此需要使用CMake来安装。准备二进制可执行文件的安装很简单;为了实现这一点,只需要包含GNUInstallDirs并使用install()命令:
96 |
97 | \filename{ch15/01-full-project/src/calc\_console/CMakeLists.txt (续)}
98 |
99 | \begin{cmake}
100 | # ... calc_console_static definition
101 | # ... configuration, testing, program analysis
102 | # ... calc_console bootstrap executable definition
103 |
104 | # Installation
105 | include(GNUInstallDirs)
106 | install(TARGETS calc_console
107 | RUNTIME COMPONENT runtime
108 | )
109 | \end{cmake}
110 |
111 | 这样,可执行文件就设置好进行安装了。现在,继续进行打包。
112 |
113 | \mySubsubsection{15.6.3.}{使用CPack打包}
114 |
115 | 可以尽情配置大量的支持的包类型;但对于这个项目,基本配置就够了:
116 |
117 | \filename{ch15/01-full-project/cmake/Packaging.cmake}
118 |
119 | \begin{cmake}
120 | # CPack configuration
121 | set(CPACK_PACKAGE_VENDOR "Rafal Swidzinski")
122 | set(CPACK_PACKAGE_CONTACT "email@example.com")
123 | set(CPACK_PACKAGE_DESCRIPTION "Simple Calculator")
124 | include(CPack)
125 | \end{cmake}
126 |
127 | 这样的最小化配置对于标准存档,如ZIP文件,工作得很好。为了在构建项目后测试安装和打包过程,请在构建树内使用以下命令:
128 |
129 | \begin{shell}
130 | # cpack -G TGZ -B packages
131 | CPack: Create package using TGZ
132 | CPack: Install projects
133 | CPack: - Run preinstall target for: Calc
134 | CPack: - Install project: Calc []
135 | CPack: Create package
136 | CPack: - package: .../packages/Calc-1.0.0-Linux.tar.gz generated.
137 | \end{shell}
138 |
139 | 这就完成了安装和打包的过程;接下来的任务是提供文档。
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/book/content/chapter15/8.tex:
--------------------------------------------------------------------------------
1 | 本章中,基于目前为止所学的内容构建了一个专业项目。让我们快速回顾一下。
2 |
3 | 首先规划了项目的布局,并讨论了哪些文件将位于哪个目录中。基于以往的经验,以及想要实践更高级场景的愿望,界定了一个面向用户的主应用程序和一个其他开发者可能使用的库。这塑造了目录的结构,以及希望构建的 CMake 目标之间的关系。随后,配置了各个构建目标:提供了库的源代码,定义了它的目标,并设置了使用位置独立代码参数供使用。面向用户的应用程序也可以定义其可执行目标,提供了源代码,并配置了其依赖项:FTXUI 库。
4 |
5 | 有了待构建的制品之后,继续增强了项目的测试和质量保证功能。添加了覆盖率模块来生成覆盖率报告,使用 Memcheck 在运行时通过 Valgrind 验证解决方案,并执行了静态分析 CppCheck。
6 |
7 | 这样一个项目现在已经准备好安装,所以我们使用学到的技术为库和可执行文件创建了适当的安装条目,并为 CPack 准备了包配置。最后的任务是确保项目文档正确无误,因此设置了使用 Doxygen 自动生成文档,并编写了几份基本文档来处理软件分发中不太技术性的方面。
8 |
9 | 这就完成了项目配置,并且现在可以轻松地使用几个精确的 CMake 命令来构建和安装项目。但如果我们可以只用一个简单的命令来完成整个过程呢?让我们在最后一章:第 16 章中探索这个主题。
--------------------------------------------------------------------------------
/book/content/chapter15/9.tex:
--------------------------------------------------------------------------------
1 |
2 | \begin{itemize}
3 | \item
4 | 构建静态库和共享库的相关信息:
5 |
6 | \url{https://stackoverflow.com/q/2152077}
7 |
8 | \item
9 | FXTUI 库项目:
10 |
11 | \url{https://github.com/ArthurSonzogni/FTXUI}
12 |
13 | \item
14 | option() 命令的文档:
15 |
16 | \url{https://cmake.org/cmake/help/latest/command/option.html}
17 |
18 | \item
19 | Google 关于开源软件发布的准备指南:
20 |
21 | \url{https://opensource.google/docs/releasing/preparing/}
22 |
23 | \item
24 | 为什么不能在 GCC 预编译头文件中使用 Clang-Tidy:
25 |
26 | \url{https://gitlab.kitware.com/cmake/cmake/-/issues/22081#note_943104}
27 |
28 | \item
29 | Cppcheck 手册:
30 |
31 | \url{https://cppcheck.sourceforge.io/manual.pdf}
32 |
33 | \item
34 | 如何撰写 README 文件:
35 |
36 | \url{https://www.freecodecamp.org/news/how-to-write-a-good-readme-file/}
37 |
38 | \item
39 | GitHub 项目的 Creative Commons 许可证:
40 |
41 | \url{https://github.com/santisoler/cc-licenses}
42 |
43 | \item
44 | GitHub 认可的常用项目许可证:
45 |
46 | \url{https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository}
47 | \end{itemize}
48 |
--------------------------------------------------------------------------------
/book/content/chapter15/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter15/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter15/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter15/images/2.png
--------------------------------------------------------------------------------
/book/content/chapter15/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter15/images/3.png
--------------------------------------------------------------------------------
/book/content/chapter15/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter15/images/4.png
--------------------------------------------------------------------------------
/book/content/chapter15/images/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter15/images/5.png
--------------------------------------------------------------------------------
/book/content/chapter15/images/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter15/images/6.png
--------------------------------------------------------------------------------
/book/content/chapter15/images/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter15/images/7.png
--------------------------------------------------------------------------------
/book/content/chapter16/0.tex:
--------------------------------------------------------------------------------
1 | CMake 3.19 版本中引入了预设(Presets),目的是简化项目设置的管理。预设出现之前,用户需要记住冗长的命令行配置,或是直接在项目文件中设置覆盖选项,这往往会变得复杂且容易出错。预设让用户能够以更简单的方式处理,诸如用于配置项目的生成器、并发构建任务的数量,以及要构建或测试的项目组件等设置。通过使用预设,CMake 变得更加易于使用。用户可以一次性设置预设,并在需要时使用它们,从而使每次 CMake 执行更加一致且易于理解。还有助于跨不同用户和计算机标准化设置,从而简化协作项目的工作流程。
2 |
3 | 预设兼容 CMake 的四种主要模式:配置构建系统、构建、运行测试和打包。允许用户将这些部分链接在一起形成工作流,使整个过程更加自动化和有序。此外,预设提供了如条件和宏表达式(或简称宏)等功能,给予用户更大的控制力。
4 |
5 | 本章中,将包含以下内容:
6 |
7 | \begin{itemize}
8 | \item
9 | 使用项目中定义的预设
10 |
11 | \item
12 | 编写预设文件
13 |
14 | \item
15 | 定义特定阶段的预设
16 |
17 | \item
18 | 定义工作流预设
19 |
20 | \item
21 | 添加条件和宏
22 | \end{itemize}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/book/content/chapter16/1.tex:
--------------------------------------------------------------------------------
1 |
2 | 可以在 GitHub 上找到本章中存在的代码文件,地址是 \url{https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch16}。
3 |
4 | 执行本章示例所需的命令将在每个章节中提供。
--------------------------------------------------------------------------------
/book/content/chapter16/2.tex:
--------------------------------------------------------------------------------
1 | 项目的配置可能成为一个复杂的任务,特别是需要具体指定缓存变量、选定的生成器等元素时——尤其是当项目有多种构建方式的时候,这时预设就变得非常有用。不必记住命令行参数或是编写 shell 脚本,来以不同的参数运行 cmake,而是可以创建一个预设文件,并将所需的配置存储在项目中。
2 |
3 | CMake 使用两个可选文件来存储项目预设:
4 |
5 | \begin{itemize}
6 | \item
7 | CMakePresets.json: 由项目作者提供的官方预设。
8 |
9 | \item
10 | CMakeUserPresets.json: 专为希望向项目添加自定义预设的用户设计。项目应当将此文件添加到版本控制系统忽略列表中,以确保自定义设置不会共享到仓库中。
11 | \end{itemize}
12 |
13 | 预设文件必须放置在项目的顶层目录中以便 CMake 能够识别。每个预设文件可以为每个阶段定义多个预设:配置 (configure)、构建 (build)、测试 (test)、打包 (package),以及涵盖多个阶段的工作流 (workflow) 预设。然后用户可以通过集成开发环境 (IDE)、图形用户界面 (GUI) 或者命令行选择并执行一个预设。
14 |
15 | 预设可以通过在命令行中添加 -{}-list-presets 参数来列出,具体取决于我们要列出的阶段。例如,列出构建预设可以用下面的命令:
16 |
17 | \begin{shell}
18 | cmake --build --list-presets
19 | \end{shell}
20 |
21 | 列出测试预设可以用下面的命令:
22 |
23 | \begin{shell}
24 | ctest --list-presets
25 | \end{shell}
26 |
27 | 要使用一个预设,需要遵循相同的模式,并在 -{}-preset 参数之后提供预设名称。
28 |
29 | 另外,不能使用 cmake 命令来列出打包预设,需要使用 cpack。这里是一个用于打包预设的命令行示例:
30 |
31 | \begin{shell}
32 | cpack --preset
33 | \end{shell}
34 |
35 | 选择好预设后,还可以添加特定于阶段的命令行参数,例如指定构建树或者安装路径。添加的参数会覆盖预设中设置的内容。
36 |
37 | 工作流预设有特殊情况,运行 cmake 命令时如果存在 -{}-workflow 参数,则可以列出并应用工作流预设:
38 |
39 | \begin{shell}
40 | $ cmake --workflow --list-presets
41 | Available workflow presets:
42 | "myWorkflow"
43 | $ cmake --workflow --preset myWorkflow
44 | Executing workflow step 1 of 4: configure preset "myConfigure"
45 | ...
46 | \end{shell}
47 |
48 | 这就是如何在项目中应用和查看可用预设的方法。现在,让我们探索一下如何构建预设文件。
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/book/content/chapter16/3.tex:
--------------------------------------------------------------------------------
1 | CMake 在项目的顶层目录中搜索 CMakePresets.json 和 CMakeUserPresets.json 文件。这两个文件使用相同的 JSON 结构来定义预设,它们之间的区别不大。其格式是一个 JSON 对象,包含以下键:
2 |
3 | \begin{itemize}
4 | \item
5 | version: 这是一个必需的整数,指定了预设 JSON 架构的版本。
6 |
7 | \item
8 | cmakeMinimumRequired: 这是一个对象,指定了所需的 CMake 版本。
9 |
10 | \item
11 | include: 这是一个字符串数组,从数组中提供的文件路径包含外部预设(自第 4 版架构开始)。
12 |
13 | \item
14 | configurePresets: 这是一个对象数组,定义了配置阶段的预设。
15 |
16 | \item
17 | buildPresets: 这是一个对象数组,定义了构建阶段的预设。
18 |
19 | \item
20 | testPresets: 这是一个对象数组,专门针对测试阶段的预设。
21 |
22 | \item
23 | packagePresets: 这是一个对象数组,专门针对打包阶段的预设。
24 |
25 | \item
26 | workflowPresets: 这是一个对象数组,专门针对工作流模式的预设。
27 |
28 | \item
29 | vendor: 这是一个对象,包含由 IDE 和其他供应商定义的自定义设置;CMake 不处理这个字段。
30 | \end{itemize}
31 |
32 | 编写预设时,CMake 要求存在 version 入口;其他值则可选。下面是一个预设文件的例子(实际的预设将在后续部分添加):
33 |
34 | \filename{ch16/01-presets/CMakePresets.json}
35 |
36 | \begin{json}
37 | {
38 | "version": 6,
39 | "cmakeMinimumRequired": {
40 | "major": 3,
41 | "minor": 26,
42 | "patch": 0
43 | },
44 | "include": [],
45 | "configurePresets": [],
46 | "buildPresets": [],
47 | "testPresets": [],
48 | "packagePresets": [],
49 | "workflowPresets": [],
50 | "vendor": {
51 | "data": "IDE-specific information"
52 | }
53 | }
54 | \end{json}
55 |
56 | 上述例子中,并不需要添加空数组;除了 version 以外的条目都是可选的。顺便说一下,对于 CMake 3.26 的适当架构版本是 6。
57 |
58 | 已经了解了预设文件的结构,接下来我们就学习如何实际定义这些预设。
59 |
--------------------------------------------------------------------------------
/book/content/chapter16/5.tex:
--------------------------------------------------------------------------------
1 | 工作流预设是我们项目的终极自动化解决方案,允许我们按照预定顺序自动执行多个特定阶段的预设。这样,实际上可以用一步完成端到端的构建。
2 |
3 | 要发现项目的可用工作流,我们可以执行以下命令:
4 |
5 | \begin{shell}
6 | cmake --workflow --list-presets
7 | \end{shell}
8 |
9 | 要选择并应用一个预设,使用以下命令:
10 |
11 | \begin{shell}
12 | cmake --workflow --preset
13 | \end{shell}
14 |
15 | 此外,使用 -{}-fresh 标志,可以清除构建树和缓存。
16 |
17 | 定义工作流预设非常简单;需要定义一个名称,并且可以像为特定阶段的预设那样可选地提供 displayName 和 description。之后,必须枚举出工作流应该执行的所有特定阶段的预设。这是通过提供一个 steps 数组来完成的,数组中的对象包含 type 和 name 属性:
18 |
19 | \filename{ch16/01-presets/CMakePresets.json (续)}
20 |
21 | \begin{json}
22 | ...
23 | "workflowPresets": [
24 | {
25 | "name": "myWorkflow",
26 | "steps": [
27 | {
28 | "type": "configure",
29 | "name": "myConfigure"
30 | },
31 | {
32 | "type": "build",
33 | "name": "myBuild"
34 | },
35 | {
36 | "type": "test",
37 | "name": "myTest"
38 | },
39 | {
40 | "type": "package",
41 | "name": "myPackage"
42 | },
43 | {
44 | "type": "build",
45 | "name": "myInstall"
46 | }
47 | ]
48 | ...
49 | \end{json}
50 |
51 | steps 数组中的每个对象引用了本章中早些时候定义的预设,指示其类型(configure, build, test, 或 package)和名称。这些预设一起执行所有必要的步骤,只需一条命令即可从头开始完全构建和安装项目:
52 |
53 | \begin{shell}
54 | cmake --workflow --preset myWorkflow
55 | \end{shell}
56 |
57 | 工作流预设是用于自动化 C++ 构建、测试、打包和安装的解决方案。接下来,让我们探讨如何通过条件和宏来管理一些特殊情况。
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/book/content/chapter16/6.tex:
--------------------------------------------------------------------------------
1 | 当讨论每个特定阶段预设的一般字段时,提到了 condition 字段。现在是回到这个话题的时候了。condition 字段可以启用或禁用一个预设,在与工作流集成时揭示其真正的潜力,它允许在某些条件下绕过不适合的预设,并创建替代的预设。
2 |
3 | 条件要求预设架构版本 3 或以上(在 CMake 3.22 中引入),并且是 JSON 对象,编码了一些简单的逻辑操作,可以根据所使用的操作系统、环境变量甚至选定的生成器等条件来判断预设是否适用。CMake 通过宏提供了这些数据,这些宏基本上是一组只读变量,可以在预设文件中使用。
4 |
5 | condition 对象的结构根据检查类型的不同而变化。每个条件都必须包含一个 type 字段,以及由该类型定义的其他字段。其基本类型包括:
6 |
7 | \begin{itemize}
8 | \item
9 | const: 这个检查 value 字段中提供的值是否为布尔真。
10 |
11 | \item
12 | equals 和 notEquals: 比较 lhs 字段的值与 rhs 字段的值。
13 |
14 | \item
15 | inList 和 notInList: 这些检查 string 字段中提供的值是否存在于 list 字段的数组中。
16 |
17 | \item
18 | matches 和 notMatches: 这些判断 string 字段的值是否符合 regex 字段中定义的模式。
19 | \end{itemize}
20 |
21 | 一个示例条件如下所示:
22 |
23 | \begin{json}
24 | "condition": {
25 | "type": "equals",
26 | "lhs": "${hostSystemName}",
27 | "rhs": "Windows"
28 | }
29 | \end{json}
30 |
31 | const 条件的实际用途,主要是为了在不从 JSON 文件中删除预设的情况下禁用。除了 const 之外,所有基本条件都允许在其引入的字段 (lhs, rhs, string, list, regex) 中使用宏。
32 |
33 | 高级条件类型,类似于“not”,“and”和“or”操作,使用其他条件作为参数:
34 |
35 | \begin{itemize}
36 | \item
37 | not: 这是对 condition 字段中提供的条件进行布尔反转。
38 |
39 | \item
40 | anyOf 和 allOf: 这些检查 conditions 数组中的任意条件或所有条件是否为真。
41 | \end{itemize}
42 |
43 | 例如:
44 |
45 | \begin{json}
46 | "condition": {
47 | "type": "anyOf",
48 | "conditions": [
49 | {
50 | "type": "equals",
51 | "lhs": "${hostSystemName}",
52 | "rhs": "Windows"
53 | },
54 | {
55 | "type": "equals",
56 | "lhs": "${hostSystemName}",
57 | "rhs": "Linux"
58 | }
59 | ]
60 | }
61 | \end{json}
62 |
63 | 这个条件在系统为 Linux 或 Windows 时计算为真。
64 |
65 | 通过这些示例,引入了第一个宏:\$\{hostSystemName\}。宏遵循简单的语法,并限于特定实例:
66 |
67 | \begin{itemize}
68 | \item
69 | \$\{sourceDir\}: 源代码树的路径。
70 |
71 | \item
72 | \$\{sourceParentDir\}: 源代码树父目录的路径。
73 |
74 | \item
75 | \$\{sourceDirName\}: 项目的目录名。
76 |
77 | \item
78 | \$\{presetName\}: 预设的名称。
79 |
80 | \item
81 | \$\{generator\}: 用于创建构建系统的生成器。
82 |
83 | \item
84 | \$\{hostSystemName\}: 系统名称:Linux, Windows, 或 macOS 上的 Darwin。
85 |
86 | \item
87 | \$\{fileDir\}: 包含当前预设的文件名(当使用 include 数组导入外部预设时适用)。
88 |
89 | \item
90 | \$\{dollar\}: 转义的美元符号 \$。
91 |
92 | \item
93 | \$\{pathListSep\}: 、环境特定的路径分隔符。
94 |
95 | \item
96 | \$env\{\}: 如果预设指定了环境变量,则返回该环境变量(区分大小写),否则返回父环境中的值。
97 |
98 | \item
99 | \$penv\{\}: 返回父环境中的环境变量。
100 |
101 | \item
102 | \$vendor\{\}: 允许 IDE 厂商引入自己的宏。
103 | \end{itemize}
104 |
105 | 这些宏为在预设及其条件中的使用提供了足够的灵活性,使我们能够根据需要有效地切换工作流步骤。
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/book/content/chapter16/7.tex:
--------------------------------------------------------------------------------
1 | 我们刚刚完成了一个关于 CMake 预设的全面概述,这些预设是从 CMake 3.19 版本开始引入的,旨在简化项目管理。预设让产品作者可以通过配置项目构建和交付的所有阶段来为用户提供整洁的体验。预设不仅简化了 CMake 的使用,还提高了一致性,并允许根据环境进行设置。
2 |
3 | 我们解释了 CMakePresets.json 和 CMakeUserPresets.json 文件的结构和用法,并提供了关于定义各种类型的预设的见解,包括配置预设、构建预设、测试预设、打包预设和工作流预设。每种类型都进行了详细的描述:了解了常见的字段,如何在内部构建预设、建立它们之间的继承关系,以及为最终用户提供特定的配置选项。
4 |
5 | 对于配置预设,讨论了重要的主题,如选择生成器、构建目录和安装目录,并通过 configurePreset 字段将预设链接在一起。现在知道了如何处理构建预设和设置构建作业数量、目标以及清理选项。随后,学习了测试预设如何通过广泛的过滤和排序选项、输出格式化,以及执行参数(如超时和容错)来辅助测试选择。了解了如何通过指定打包生成器、过滤选项,以及打包元数据来管理打包预设。甚至还介绍了一种通过专门的构建预设应用,来执行安装阶段的变通方法。
6 |
7 | 接着,我们发现了工作流预设如何将多个特定阶段的预设组合在一起。最后,讨论了条件和宏表达式,为项目作者提供了对单个预设的行为,及其集成到工作流中的更大控制权。
8 |
9 | 我们的 CMake 之旅至此结束!恭喜你——现在拥有了开发、测试和打包高质量 C++ 软件所需的所有工具。最好的前进方式就是应用你所学的知识去创造优秀的软件给你的用户。祝各位好运!
--------------------------------------------------------------------------------
/book/content/chapter16/8.tex:
--------------------------------------------------------------------------------
1 |
2 | \begin{itemize}
3 | \item
4 | 预设的官方文档:
5 |
6 | \url{https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html}
7 | \end{itemize}
--------------------------------------------------------------------------------
/book/content/chapter16/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Modern-CMake-for-Cpp-2ed/9b1fa1abbe54b17fe0d77fbc9198b1f497dadc4d/book/content/chapter16/images/1.png
--------------------------------------------------------------------------------
/book/content/chapter17/0.tex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/book/content/chapter17/1.tex:
--------------------------------------------------------------------------------
1 |
2 | 每种编程语言都包含了对各种任务有用的实用命令,CMake 也不例外。它提供了用于算术运算、位操作、字符串处理以及列表和文件操作的工具。尽管由于增强功能和大量模块的发展,这些命令的需求已经减少,但在高度自动化的项目中它们仍然重要。如今,可能会更多地在使用 cmake -P 调用的 CMake 脚本中发现它们的作用。
3 |
4 | 因此,本附录总结了各种 CMake 命令及其不同的模式,可以作为方便的离线参考,或是官方文档的简化版。对于更详细的信息,建议查阅提供的链接。
5 |
6 | 此参考适用于 CMake 3.26.6 版本。
7 |
8 | 本附录中,包括以下内容:
9 |
10 | \begin{itemize}
11 | \item
12 | string()
13 |
14 | \item
15 | list()
16 |
17 | \item
18 | file()
19 |
20 | \item
21 | math()
22 | \end{itemize}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/book/content/chapter17/3.tex:
--------------------------------------------------------------------------------
1 |
2 |
3 | 此命令提供了基本的列表操作:读取、查找、修改及排序。某些模式会改变列表(即修改原始值)。如果稍后还需要原始值,请确保先复制一份。
4 |
5 | 完整详情可以在在线文档中找到:
6 |
7 | \url{https://cmake.org/cmake/help/latest/command/list.html}
8 |
9 | 可用的 list() 模式的类别包括读取、查找、修改及排序。
10 |
11 | \mySubsubsection{A.3.1.}{读取}
12 |
13 | 以下是可用的模式:
14 |
15 | \begin{itemize}
16 | \item
17 | list(LENGTH ):计算 变量中的元素数量,并将结果存储在 变量中。
18 |
19 | \item
20 | list(GET ... ):将由 索引列表指定的 元素复制到 变量中。
21 |
22 | \item
23 | list(JOIN ):使用 分隔符交错 元素,并将结果字符串存储在 变量中。
24 |
25 | \item
26 | list(SUBLIST ):其作用类似于 GET 模式,但不是使用显式索引而是使用范围。如果 是 -1,则从 索引到 变量中的列表末尾的所有元素都会被返回。
27 | \end{itemize}
28 |
29 | \mySubsubsection{A.3.2.}{查找}
30 |
31 | 此模式仅仅是在 变量中查找 元素的索引,并将结果存储在 变量中(如果没有找到该元素,则存储 -1):
32 |
33 | \begin{cmake}
34 | list(FIND )
35 | \end{cmake}
36 |
37 | \mySubsubsection{A.3.3.}{修改}
38 |
39 | 以下是可用的模式:
40 |
41 | \begin{itemize}
42 | \item
43 | list(APPEND ...) :将一个或多个 值添加到 变量的末尾。
44 |
45 | \item
46 | list(PREPEND [...]):其作用类似于 APPEND,但将元素添加到 变量的开头。
47 |
48 | \item
49 | list(FILTER \{INCLUDE | EXCLUDE\} REGEX ):根据 值过滤 变量,以 INCLUDE 或 EXCLUDE 匹配的元素。
50 |
51 | \item
52 | list(INSERT [...]):在给定的 位置将一个或多个 值添加到 变量中。
53 |
54 | \item
55 | list(POP\_BACK [...]):从 变量的末尾移除一个元素,并将它存储在可选的 变量中。如果提供了多个 变量,则会移除更多元素来填充它们。
56 |
57 | \item
58 | list(POP\_FRONT [...]):其作用类似于 POP\_BACK,但从 变量的开头移除元素。
59 |
60 | \item
61 | list(REMOVE\_ITEM ...):是 FILTER EXCLUDE 的简写,但不支持正则表达式。
62 |
63 | \item
64 | list(REMOVE\_AT ...):从 中移除特定 的元素。
65 |
66 | \item
67 | list(REMOVE\_DUPLICATES ):从 中移除重复项。
68 |
69 | \item
70 | list(TRANSFORM [] [OUTPUT\_VARIABLE ]) :对 元素应用特定的转换。默认情况下,操作应用于所有元素,但可以通过添加 来限制其效果。除非提供 OUTPUT\_VARIABLE 关键词,结果将存储在 变量中,否则将修改提供的列表(就地更改)。
71 | \end{itemize}
72 |
73 | 以下选择器可用:AT ,FOR [],以及 REGEX 。
74 |
75 | 操作包括 APPEND ,PREPEND ,TOLOWER,TOUPPER,STRIP,GENEX\_STRIP,以及 REPLACE 。它们的工作方式与具有相同名称的 string() 模式完全相同。
76 |
77 | \mySubsubsection{A.3.4.}{排序}
78 |
79 | 以下是可用的模式:
80 |
81 | \begin{itemize}
82 | \item
83 | list(REVERSE ):简单地反转 的顺序。
84 |
85 | \item
86 | list(SORT ):按字母顺序对列表进行排序。
87 | \end{itemize}
88 |
89 | 有关更高级选项的参考,请参阅在线手册。
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/book/content/chapter17/4.tex:
--------------------------------------------------------------------------------
1 |
2 | 此命令提供了与文件相关的各种操作:读取、传输、锁定及归档。还提供了用于检查文件系统和对路径字符串进行操作的模式。
3 |
4 | 完整详情可以在在线文档中找到:
5 |
6 | \url{https://cmake.org/cmake/help/latest/command/file.html}
7 |
8 | 可用的 file() 模式的类别包括读取、写入、文件系统、路径转换、传输、锁定及归档。
9 |
10 | \mySubsubsection{A.4.1.}{读取}
11 |
12 | 以下是可用的模式:
13 |
14 | \begin{itemize}
15 | \item
16 | file(READ [OFFSET ] [LIMIT ] [HEX]):从 读取文件到 变量。读取可选地从偏移量 开始,并遵循最大字节数 的限制。HEX 标志指明输出应转换为十六进制表示。
17 |
18 | \item
19 | file(STRINGS ):从 中读取字符串到 变量。
20 |
21 | \item
22 | file( ):计算 文件的 哈希值,并将结果存储在 变量中。可用的算法与 string() 哈希函数相同。
23 |
24 | \item
25 | file(TIMESTAMP []):生成 文件的时间戳字符串表示,并将其存储在 变量中。可选地接受一个 字符串。
26 |
27 | \item
28 | file(GET\_RUNTIME\_DEPENDENCIES [...]):获取指定文件的运行时依赖项。这是一个高级命令,只应在 install(CODE) 或 install(SCRIPT) 场景中使用。自 CMake 3.21 开始可用。
29 | \end{itemize}
30 |
31 | \mySubsubsection{A.4.2.}{写入}
32 |
33 | 以下是可用的模式:
34 |
35 | \begin{itemize}
36 | \item
37 | file(\{WRITE | APPEND\} ...):将所有 参数写入或追加到 文件中。如果提供的系统路径不存在,将会递归创建。
38 |
39 | \item
40 | file(\{TOUCH | TOUCH\_NOCREATE\} [...]):更新 的时间戳。如果文件不存在,则仅在 TOUCH 模式下创建。
41 |
42 | \item
43 | file(GENERATE OUTPUT [...]):这是一种高级模式,为当前 CMake 生成器的每个构建配置生成输出文件。
44 |
45 | \item
46 | file(CONFIGURE OUTPUT CONTENT [...]) :其作用类似于 GENERATE\_OUTPUT,但同时通过替换变量占位符来配置生成的文件。
47 | \end{itemize}
48 |
49 | \mySubsubsection{A.4.3.}{文件系统}
50 |
51 | 以下是可用的模式:
52 |
53 | \begin{itemize}
54 | \item
55 | file(\{GLOB | GLOB\_RECURSE\} [...] [...]):生成匹配 的文件列表,并将其存储在 变量中。GLOB\_RECURSE 模式也会扫描嵌套目录。
56 |
57 | \item
58 | file(RENAME ):将文件从 移动到 。
59 |
60 | \item
61 | file(\{REMOVE | REMOVE\_RECURSE \} [...]):删除 。REMOVE\_RECURSE 也会删除目录。
62 |
63 | \item
64 | file(MAKE\_DIRECTORY [...]):创建目录。
65 |
66 | \item
67 | file(COPY ... DESTINATION [...]):将文件复制到 目的地。它提供了过滤、设置权限、跟随符号链接链等选项。
68 |
69 | \item
70 | file(COPY\_FILE [...]):将单个文件复制到 路径。自 CMake 3.21 开始可用。
71 |
72 | \item
73 | file(SIZE ):读取 的大小(以字节为单位),并将其存储在 变量中。
74 |
75 | \item
76 | file(READ\_SYMLINK ):读取 符号链接的目标路径,并将其存储在