面向 C++ 的现代 CMake 教程(一)(1)https://developer.aliyun.com/article/1526962
生成器的选项
如前所述,在生成阶段,你可以指定一些选项。选择和配置生成器决定了我们将使用我们系统中的哪个构建工具来构建,构建文件将呈现什么样子,以及构建树的结构将如何。
那么,你应该关心吗?幸运的是,答案通常是“不”。CMake 在许多平台上支持多种本地构建系统;然而,除非你同时安装了几个生成器,否则 CMake 会正确地为你选择一个。这可以通过设置CMAKE_GENERATOR
环境变量或直接在命令行上指定生成器来覆盖,如下所示:
cmake -G <generator-name> <path-to-source>
一些生成器(如 Visual Studio)支持对工具集(编译器)和平面(编译器或 SDK)进行更详细的指定。另外,这些生成器还有相应的环境变量,这些环境变量会覆盖默认值:CMAKE_GENERATOR_TOOLSET
和CMAKE_GENERATOR_PLATFORM
。我们可以像下面这样直接指定它们:
cmake -G <generator-name> -T <toolset-spec> -A <platform-name> <path-to-source>
Windows 用户通常希望为他们的首选 IDE 生成一个构建系统。在 Linux 和 macOS 上,使用 Unix Makefiles 或 Ninja 生成器非常普遍。
为了检查你的系统上可用的生成器,请使用以下命令:
cmake --help
在help
打印输出结束时,你应该看到一个完整的列表,如下所示:
Windows 10 上有许多生成器可供选择:
The following generators are available on this platform: Visual Studio 16 2019 Visual Studio 15 2017 [arch] Visual Studio 14 2015 [arch] Visual Studio 12 2013 [arch] Visual Studio 11 2012 [arch] Visual Studio 10 2010 [arch] Visual Studio 9 2008 [arch] Borland Makefiles NMake Makefiles NMake Makefiles JOM MSYS Makefiles MinGW Makefiles Green Hills MULTI Unix Makefiles Ninja Ninja Multi-Config Watcom Wmake CodeBlocks - MinGW Makefiles CodeBlocks - NMake Makefiles CodeBlocks - NMake Makefiles JOM CodeBlocks - Ninja CodeBlocks - Unix Makefiles CodeLite - MinGW Makefiles CodeLite - NMake Makefiles CodeLite - Ninja CodeLite - Unix Makefiles Eclipse CDT4 - NMake Makefiles Eclipse CDT4 - MinGW Makefiles Eclipse CDT4 - Ninja Eclipse CDT4 - Unix Makefiles Kate - MinGW Makefiles Kate - NMake Makefiles Kate - Ninja Kate - Unix Makefiles Sublime Text 2 - MinGW Makefiles Sublime Text 2 - NMake Makefiles Sublime Text 2 - Ninja Sublime Text 2 - Unix Makefiles
缓存选项
CMake 在配置阶段查询系统获取各种信息。这些信息存储在构建树目录中的CMakeCache.txt
文件中。有一些选项可以让你更方便地管理该文件。
我们首先可以使用的功能是预填充缓存信息的能力:
cmake -C <initial-cache-script> <path-to-source>
我们可以提供 CMake 脚本的路径,该脚本(仅)包含一个set()
命令列表,用于指定将用于初始化空构建树的变量。
现有缓存变量的初始化和修改可以通过另一种方式完成(例如,仅设置几个变量而创建一个文件似乎有些过于繁琐)。你只需在命令行中简单地设置它们,如下所示:
cmake -D <var>[:<type>]=<value> <path-to-source>
:
部分是可选的(GUIs 使用它);你可以使用BOOL
、FILEPATH
、PATH
、STRING
或INTERNAL
。如果你省略了类型,它将设置为已有变量的类型;否则,它将设置为UNINITIALIZED
。
一个特别重要的变量包含构建类型:例如,调试和发布。许多 CMake 项目会在多个场合读取它,以决定诸如消息的冗余度、调试信息的的存在以及创建的艺术品的优化级别等事情。
对于单配置生成器(如 Make 和 Ninja),你需要在配置阶段指定CMAKE_BUILD_TYPE
变量,并为每种类型的配置生成一个单独的构建树:Debug
、Release
、MinSizeRel
或RelWithDebInfo
。
以下是一个示例:
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
请注意,多配置生成器在构建阶段进行配置。
我们可以使用-L
选项:
cmake -L[A][H] <path-to-source>
这样的列表将包含未标记为ADVANCED
的缓存变量。我们可以通过添加A
修饰符来改变这一点。要打印带有变量的帮助信息 - 添加H
修饰符。
令人惊讶的是,使用-D
选项手动添加的自定义变量如果不指定支持的一种类型,将不可见。
删除一个或多个变量的操作可以通过以下选项完成:
cmake -U <globbing_expr> <path-to-source>
在此,通配符表达式支持*
通配符和任何?
字符符号。使用时要小心,以免破坏东西。
-U
和-D
选项都可以重复多次。
调试和跟踪选项
CMake 可以运行多种选项,让你窥视其内部。要获取有关变量、命令、宏和其他设置的一般信息,请运行以下操作:
cmake --system-information [file]
可选的文件参数允许你将输出存储在文件中。在构建树目录中运行它将打印有关缓存变量和日志文件中的构建信息的额外信息。
在我们的项目中,我们将使用message()
命令来报告构建过程的详细信息。CMake 根据当前日志级别(默认情况下是STATUS
)过滤这些日志输出。以下行指定了我们感兴趣的日志级别:
cmake --log-level=<level>
在这里,level
可以是以下任意一个:ERROR
、WARNING
、NOTICE
、STATUS
、VERBOSE
、DEBUG
或 TRACE
。你可以在 CMAKE_MESSAGE_LOG_LEVEL
缓存变量中永久指定这个设置。
另一个有趣的选项允许你使用message()
调用。为了调试非常复杂的工程,CMAKE_MESSAGE_CONTEXT
变量可以像栈一样使用。每当你代码进入一个特定的上下文时,你可以向栈中添加一个描述性的名称,并在离开时移除它。通过这样做,我们的消息将被当前CMAKE_MESSAGE_CONTEXT
变量装饰如下:
[some.context.example] Debug message.
启用这种日志输出的选项如下:
cmake --log-context <path-to-source>
我们将在第二章 CMake 语言 中更详细地讨论日志记录。
如果其他方法都失败了——我们必须使用大杀器——总是有跟踪模式。这将打印出每个命令以及它来自的文件名和确切的行号及其参数。你可以按照如下方式启用它:
cmake --trace
预设选项
正如你可能已经猜到的,用户可以指定很多选项来从你的项目中生成一个构建树。当处理构建树路径、生成器、缓存和环境变量时,很容易感到困惑或遗漏某些内容。开发者可以简化用户与他们项目交互的方式,并提供一个指定一些默认值的CMakePresets.json
文件。要了解更多,请参考导航项目文件部分。
要列出所有可用的预设,执行以下操作:
cmake --list-presets
你可以按照如下方式使用其中一个预设:
cmake --preset=<preset>
这些值覆盖了系统默认值和环境。然而,同时,它们也可以被命令行上明确传递的任何参数覆盖:
[外链图片转存中…(img-DV4rlnY3-1716544491728)]
图 1.3 – 预设如何覆盖 CMakeCache.txt 和系统环境变量
构建项目
在生成我们的构建树之后,我们准备进入下一阶段:运行构建工具。CMake 不仅知道如何为许多不同的构建器生成输入文件,而且还知道如何为你提供特定于项目的参数来运行它们。
不推荐
许多在线资源建议在生成阶段之后直接运行 GNU Make:make
。这是 Linux 和 macOS 的默认生成器,通常可以工作。然而,我们更喜欢本节描述的方法,因为它与生成器无关,并且支持所有平台。因此,我们不需要担心我们应用程序每个用户的准确环境。
构建模式的语法
cmake --build <dir> [<options>] [-- <build-tool-options>]
在这些大多数情况下,提供最少量的东西以获得成功的构建就足够了:
cmake --build <dir>
CMake 需要知道我们生成的构建树的位置。这是我们在生成阶段传递给 -B
参数的相同路径。
通过提供一些选项,CMake 允许您指定对每个构建器都有效的关键构建参数。如果您需要向您选择的本地构建器提供特殊参数,请在--
标记之后,在命令的末尾传递它们:
cmake --build <dir> -- <build-tool-options>
并行构建选项
默认情况下,许多构建工具会使用多个并发进程来利用现代处理器并并行编译您的源代码。构建器知道项目依赖的结构,因此它们可以同时处理满足其依赖的步骤,以节省用户的时间。
如果您在强大的机器上构建(或者为了调试而强制进行单线程构建),您可能想要覆盖那个设置。只需使用以下任一选项指定作业数量:
cmake --build <dir> --parallel [<number-of-jobs>] cmake --build <dir> -j [<number-of-jobs>]
另一种方法是使用CMAKE_BUILD_PARALLEL_LEVEL
环境变量来设置。像往常一样,我们总是可以使用前面的选项来覆盖变量。
目标选项
我们将在书的第二部分讨论目标。现在,我们只需说每个项目都由一个或多个称为目标的部分组成。通常,我们想要构建它们所有;然而,在某些情况下,我们可能想要跳过一些或者显式构建被正常构建中故意排除的目标。我们可以这样做:
cmake --build <dir> --target <target1> -t <target2> ...
正如您将观察到的,我们可以通过重复-t
参数来指定多个目标。
通常不构建的一个目标是clean
。这将从构建目录中删除所有工件。您可以这样调用它:
cmake --build <dir> -t clean
此外,CMake 还提供了一个方便的别名,如果你想要先清理然后再进行正常构建的话:
cmake --build <dir> --clean-first
多配置生成器的选项
所以,我们已经对生成器有了一些了解:它们有不同的形状和大小。其中一些提供的功能比其他的多,而这些功能之一就是能够在单个构建树中构建Debug
和Release
构建类型。
支持此功能的生成器包括 Ninja 多配置、Xcode 和 Visual Studio。其余的生成器都是单配置生成器,为此目的需要一个独立的构建树。
选择Debug
、Release
、MinSizeRel
或RelWithDebInfo
,并按照以下方式指定:
cmake --build <dir> --config <cfg> • 1
否则,CMake 将使用Debug
作为默认值。
调试选项
当事情出错时,我们首先应该做的是检查输出信息。然而,经验丰富的开发者知道,一直打印所有细节是令人困惑的,所以它们通常默认隐藏它们。当我们需要揭开盖子时,我们可以通过告诉 CMake 要详细输出日志来请求更详细的日志:
cmake --build <dir> --verbose cmake --build <dir> -v
通过设置CMAKE_VERBOSE_MAKEFILE
缓存变量也可以达到同样的效果。
安装项目
当构建工件时,用户可以将它们安装到系统中。通常,这意味着将文件复制到正确的目录中,安装库,或者从 CMake 脚本中运行一些自定义安装逻辑。
安装模式的语法
cmake --install <dir> [<options>]
与其他操作模式一样,CMake 需要一个生成构建树的路径:
cmake --install <dir>
多配置生成器选项
与构建阶段类似,我们可以指定我们想要用于安装的构建类型(有关详细信息,请参阅构建项目部分)。可用的类型包括Debug
、Release
、MinSizeRel
和RelWithDebInfo
。签名如下:
cmake --install <dir> --config <cfg>
组件选项
作为开发者,您可能会选择将项目拆分为可以独立安装的组件。我们将在第十一章进一步讨论组件的概念,安装与打包。现在,我们假设它们代表解决方案的不同部分。这可能类似于application
、docs
和extra-tools
。
要安装单个组件,请使用以下选项:
cmake --install <dir> --component <comp>
权限选项
如果在类 Unix 平台上进行安装,您可以使用以下选项指定安装目录的默认权限,格式为u=rwx,g=rx,o=rx
:
cmake --install <dir> --default-directory-permissions <permissions>
安装目录选项
我们可以为项目配置中指定的安装路径添加一个自定义前缀(例如,当我们对某些目录的写入权限有限时)。原本的/usr/local
路径通过添加/home/user
前缀后变为/home/user/usr/local
。此选项的签名如下:
cmake --install <dir> --prefix <prefix>
请注意,这在 Windows 上不起作用,因为该平台上的路径通常以驱动器字母开头。
调试选项
同样地,我们也可以选择在构建阶段查看安装阶段的详细输出。为此,可以使用以下任意一个选项:
cmake --build <dir> --verbose cmake --build <dir> -v
如果设置了VERBOSE
环境变量,也可以达到同样的效果。
运行脚本
CMake 项目使用 CMake 的自定义语言进行配置。它是跨平台的,相当强大,并且已经存在。那么为什么不将其用于其他任务呢?确实,你可以编写独立的脚本(我们将在本章末尾讨论到这一点)。
CMake 可以像这样运行这些脚本:
脚本模式的语法
cmake [{-D <var>=<value>}...] -P <cmake-script-file> [-- <unparsed-options>...]
运行此类脚本不会运行任何配置或生成阶段。此外,它不会影响缓存。你可以通过以下两种方式将值传递给此脚本:
- 通过使用
-D
选项定义的变量。 - 通过在
--
标记后传递的参数。CMake 将为传递给脚本的的所有参数(包括--
标记)创建CMAKE_ARGV
变量。
运行命令行工具
在少数情况下,我们可能需要以平台无关的方式运行单个命令——可能是复制文件或计算校验和。并非所有的平台都是平等的,因此并非所有的命令在每一个系统中都是可用的,或者它们有不同的名称。
CMake 提供了一种模式,可以在不同平台上一致地执行最常用的命令:
命令行工具模式的语法
cmake -E <command> [<options>]
由于这种特定模式的使用相当有限,我们不会深入讨论。然而,如果你对细节感兴趣,我建议调用cmake -E
来列出所有可用的命令。为了简单地了解提供的功能,CMake 3.20 支持以下命令:
capabilities
, cat
, chdir
, compare_files
, copy
, copy_directory
, copy_if_different
, echo
, echo_append
, env
, environment
, make_directory
, md5sum
, sha1sum
, sha224sum
, sha256sum
, sha384sum
, sha512sum
, remove
, remove_directory
, rename
, rm
, server
, sleep
, tar
, time
, touch
, touch_nocreate
, create_symlink
, create_hardlink
, true
, 和 false
。
如果你想要使用的命令缺失,或者你需要更复杂的行为,考虑将其封装在脚本中,并以-P
模式运行它。
获取帮助
毫不奇怪,CMake 通过其命令行提供了广泛的帮助。
帮助模式的语法
cmake ––help[-<topic>]
CTest
自动化测试对于生成和维护高质量代码非常重要。这就是为什么我们专门用了一整章来讨论这个主题(请参考第八章,测试框架),其中我们深入探讨了 CTest 的使用。它是可用的命令行工具之一,所以我们现在简要介绍一下。
CTest 是在更高层次的抽象中封装 CMake,其中构建阶段成为开发我们软件过程中的一个垫脚石。CMake 还可以为我们执行其他任务,包括更新、运行各种测试、将项目状态报告给外部仪表板以及运行编写在 CMake 语言中的脚本。
更重要的是,CTest 标准化了使用 CMake 构建的解决方案的测试运行和报告。这意味着作为用户,你不需要知道项目使用的是哪个测试框架或如何运行它。CTest 提供了一个方便的界面来列出、筛选、洗牌、重试和计时测试运行。此外,如果需要构建,它还可以调用 CMake。
运行构建项目测试的最简单方法是在生成的构建树中调用ctest
:
$ ctest Test project C:/Users/rapha/Desktop/CMake/build Guessing configuration Debug Start 1: SystemInformationNew 1/1 Test #1: SystemInformationNew ......... Passed 3.19 sec 100% tests passed, 0 tests failed out of 1 Total Test time (real) = 3.24 sec
CPack
在我们构建并测试了我们的神奇软件之后,我们准备与世界分享它。在少数情况下,高级用户完全可以使用源代码,这就是他们想要的。然而,世界上绝大多数人使用预编译的二进制文件,因为这样方便且能节省时间。
CMake 不会让你在这里陷入困境;它自带了电池。CPack 正是为了创建不同平台的包而设计的:压缩归档、可执行安装程序、向导、NuGet 包、macOS 捆绑包、DMG 包、RPMs 等。
CPack 的工作方式与 CMake 非常相似:它使用 CMake 语言进行配置,并有许多可供选择的包生成器(只是不要将它们与 CMake 构建系统生成器混淆)。我们将在第十一章中详细介绍,该章节讨论安装和打包,因为这是一个用于 CMake 项目最后阶段的相当庞大的工具。
CMake GUI
对于 Windows,CMake 附带一个 GUI 版本,用于配置先前准备好的项目的构建过程。对于 Unix-like 平台,有一个用 QT 库构建的版本。Ubuntu 在cmake-qt-gui
包中提供。
要访问 CMake GUI,运行cmake-gui
可执行文件:
[外链图片转存中…(img-PFdAFGRN-1716544491729)]
图 1.4 – CMake GUI –使用 Visual Studio 2019 的生成器配置构建系统的配置阶段
图形用户界面(GUI)应用程序是方便的工具,因为那里的选项相当有限。对于不熟悉命令行并且更喜欢基于窗口的界面的用户来说,这可能很有用。
不推荐
我肯定会推荐 GUI 给那些追求方便的最终用户;然而,作为一名程序员,我避免引入任何需要每次构建程序时点击表单的手动、阻塞步骤。这对于 CI 管道中的构建自动化尤为重要。这些工具需要无头应用程序,以便在没有用户交互的情况下完全执行构建。
CCMake
ccmake
可执行文件是 CMake 的 Unix-like 平台的curses
界面(Windows 上不可用)。它不是 CMake 包的一部分,因此用户必须单独安装。
Debian/Ubuntu 系统的命令如下:
$ sudo apt-get install cmake-curses-gui
请注意,可以通过这个 GUI 交互式地指定项目配置设置。程序运行时,终端底部会提供简短的说明:
CCMake 命令的语法
ccmake [<options>] ccmake {<path-to-source> | <path-to-existing-build>}
CCMake 使用与cmake
相同的选项集:
[外链图片转存中…(img-Gjg9HYGG-1716544491729)]
图 1.5 – ccmake 中的配置阶段
与图形用户界面(GUI)一样,这种模式相当有限,旨在供经验较少的用户使用。如果您在 Unix 机器上工作,我建议您快速查看并更快地继续。
这结束了关于 CMake 命令行的基本介绍。是时候探索一下典型 CMake 项目的结构了。
浏览项目文件
CMake 使用很多文件来管理其项目。在修改内容之前,让我们试图了解每个文件的作用。重要的是要意识到,尽管一个文件包含 CMake 语言命令,但这并不意味着它一定是为了让开发者编辑的。有些文件是为了被后续工具使用而生成的,对这些文件所做的任何更改都将在某个阶段被覆盖。其他文件是为了让高级用户根据个人需求调整项目。最后,还有一些在特定上下文中提供宝贵信息的临时文件。本节还将指定哪些应该放在您版本控制系统的忽略文件中。
源代码树
这是您的项目将所在的目录(也称为项目根)。它包含所有的 C++源代码和 CMake 项目文件。
此目录的关键收获如下:
- 您必须在它的顶部目录中提供一个
CMakeLists.txt
配置文件。 - 它应该使用如
git
这样的 VCS 进行管理。 - 此目录的路径由用户通过
cmake
命令的-S
参数给出。 - 避免在您的 CMake 代码中硬编码任何绝对路径到源代码树——您的软件的用户可以将项目存储在不同的路径下。
构建树
CMake 使用此目录来存储构建过程中生成的所有内容:项目的工件、短暂配置、缓存、构建日志以及您的本地构建工具将创建的任何内容。这个目录的别名还包括构建根和二进制树。
此目录的关键收获如下:
- 您的二进制文件将在此处创建,例如可执行文件和库文件,以及用于最终链接的对象文件和归档文件。
- 不要将此目录添加到您的 VCS 中——它是特定于您的系统的。如果您决定将其放在源代码树内,请确保将其添加到 VCS 忽略文件中。
- CMake 建议进行源外构建,或生成工件的目录与所有源文件分离的构建。这样,我们可以避免用临时、系统特定的文件污染项目的源代码树(或者进行源内构建)。
- 如果提供了源代码的路径,例如
cmake -S ../project ./
,则使用-B
或作为cmake
命令的最后一个参数来指定此目录。 - 建议您的项目包括一个安装阶段,允许您将最终工件放在系统中的正确位置,以便可以删除用于构建的所有临时文件。
列表文件
包含 CMake 语言的文件称为列表文件,可以通过调用include()
和find_package()
,或者间接地通过add_subdirectory()
来相互包含:
- CMake 不强制这些文件的一致命名,但通常它们具有
.cmake
扩展名。 - 一个非常重要的命名异常是一个名为
CMakeLists.txt
的文件,这是在配置阶段第一个被执行的文件。它需要位于源树的顶部。 - 当 CMake 遍历源树并包含不同的列表文件时,以下变量将被设置:
CMAKE_CURRENT_LIST_DIR
,CMAKE_CURRENT_LIST_FILE
,CMAKE_PARENT_LIST_FILE
和CMAKE_CURRENT_LIST_LINE
。
面向 C++ 的现代 CMake 教程(一)(3)https://developer.aliyun.com/article/1526964