第 1 步:一个基本起点

如何开始使用 CMake?本步骤将介绍 CMake 的一些基本语法、命令和变量。随着这些概念的引入,我们将完成三个练习并创建一个简单的 CMake 项目。

本步骤中的每个练习都将从一些背景信息开始。然后,将提供一个目标和一些有用的资源列表。在 Files to Edit 部分中的每个文件都在 Step1 目录中,并包含一个或多个 TODO 注释。每个 TODO 代表一行或两行要更改或添加的代码。TODO 旨在按数字顺序完成,首先完成 TODO 1,然后是 TODO 2 等。Getting Started 部分将提供一些有用的提示并指导您完成练习。然后,Build and Run 部分将逐步讲解如何构建和测试练习。最后,在每个练习结束时,将讨论预期的解决方案。

另请注意,本教程中的每个步骤都建立在前一个步骤的基础上。例如,Step2 的起始代码是 Step1 的完整解决方案。

练习 1 - 构建一个基本项目

最基本的 CMake 项目是一个由单个源代码文件构建的可执行文件。对于此类简单项目,一个包含三个命令的 CMakeLists.txt 文件就足够了。

注意: 虽然 CMake 支持大写、小写和混合大小写命令,但教程中将优先使用小写命令。

任何项目的顶层 CMakeLists.txt 都必须通过使用 cmake_minimum_required() 命令指定最低 CMake 版本。这会建立策略设置并确保以下 CMake 函数使用兼容的 CMake 版本运行。

要启动一个项目,我们使用 project() 命令设置项目名称。此调用对于每个项目都是必需的,并且应在 cmake_minimum_required() 之后尽快调用。正如我们稍后将看到的,此命令还可以用于指定其他项目级别信息,例如语言或版本号。

最后,add_executable() 命令告诉 CMake 使用指定的源代码文件创建一个可执行文件。

目标

了解如何创建一个简单的 CMake 项目。

有用资源

要编辑的文件

  • CMakeLists.txt

开始

tutorial.cxx 的源代码位于 Help/guide/tutorial/Step1 目录中,可用于计算数字的平方根。此步骤中无需编辑此文件。

在同一目录中有一个 CMakeLists.txt 文件,您将完成它。从 TODO 1 开始,逐步完成 TODO 3

构建并运行

一旦 TODO 1TODO 3 完成,我们就可以构建并运行我们的项目了!首先,运行 cmake 可执行文件或 cmake-gui 来配置项目,然后使用您选择的构建工具构建它。

例如,从命令行我们可以导航到 CMake 源代码树的 Help/guide/tutorial 目录并创建一个构建目录

mkdir Step1_build

接下来,导航到该构建目录并运行 cmake 以配置项目并生成原生构建系统

cd Step1_build
cmake ../Step1

然后调用该构建系统来实际编译/链接项目

cmake --build .

对于多配置生成器(例如 Visual Studio),首先导航到相应的子目录,例如

cd Debug

最后,尝试使用新构建的 Tutorial

Tutorial 4294967296
Tutorial 10
Tutorial

注意:根据 shell 的不同,正确的语法可能是 Tutorial./Tutorial.\Tutorial。为简单起见,练习中将始终使用 Tutorial

解决方案

如上所述,一个三行 CMakeLists.txt 文件就是我们启动和运行所需的全部。第一行是使用 cmake_minimum_required() 设置 CMake 版本,如下所示

TODO 1:点击显示/隐藏答案
TODO 1:CMakeLists.txt
cmake_minimum_required(VERSION 3.10)

创建基本项目的下一步是使用 project() 命令,如下所示设置项目名称

TODO 2:点击显示/隐藏答案
TODO 2:CMakeLists.txt
project(Tutorial)

基本项目要调用的最后一个命令是 add_executable()。我们这样调用它

TODO 3:点击显示/隐藏答案
TODO 3:CMakeLists.txt
add_executable(Tutorial tutorial.cxx)

练习 2 - 指定 C++ 标准

CMake 有一些特殊变量,它们要么是在幕后创建的,要么在项目代码设置时对 CMake 有意义。这些变量中的许多都以 CMAKE_ 开头。在为您的项目创建变量时,请避免这种命名约定。其中两个特殊的可由用户设置的变量是 CMAKE_CXX_STANDARDCMAKE_CXX_STANDARD_REQUIRED。这些可以一起使用来指定构建项目所需的 C++ 标准。

目标

添加一个需要 C++11 的功能。

有用的资源

要编辑的文件

  • CMakeLists.txt

  • tutorial.cxx

开始

继续编辑 Step1 目录中的文件。从 TODO 4 开始,完成到 TODO 6

首先,编辑 tutorial.cxx,添加一个需要 C++11 的功能。然后更新 CMakeLists.txt,使其要求 C++11。

构建并运行

让我们再次构建我们的项目。由于我们已经为练习 1 创建了一个构建目录并运行了 CMake,我们可以跳到构建步骤

cd Step1_build
cmake --build .

现在我们可以尝试使用与以前相同的命令来使用新构建的 Tutorial

Tutorial 4294967296
Tutorial 10
Tutorial

解决方案

我们首先通过在 tutorial.cxx 中用 std::stod 替换 atof 来向我们的项目添加一些 C++11 功能。这看起来像下面这样

TODO 4:点击显示/隐藏答案
TODO 4:tutorial.cxx
  double const inputValue = std::stod(argv[1]);

要完成 TODO 5,只需删除 #include <cstdlib>

我们需要在 CMake 代码中明确指出它应该使用正确的标志。在 CMake 中启用对特定 C++ 标准的支持的一种方法是使用 CMAKE_CXX_STANDARD 变量。对于本教程,将 CMakeLists.txt 文件中的 CMAKE_CXX_STANDARD 变量设置为 11,并将 CMAKE_CXX_STANDARD_REQUIRED 设置为 True。确保将 CMAKE_CXX_STANDARD 声明放在调用 add_executable() 的上方。

TODO 6:点击显示/隐藏答案
TODO 6:CMakeLists.txt
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

练习 3 - 添加版本号和配置头文件

有时,在 CMakelists.txt 文件中定义的变量在源代码中也可用可能很有用。在这种情况下,我们希望打印项目版本。

一种实现方法是使用配置好的头文件。我们创建一个输入文件,其中包含一个或多个要替换的变量。这些变量具有特殊的语法,看起来像 @VAR@。然后,我们使用 configure_file() 命令将输入文件复制到给定的输出文件,并将这些变量替换为 CMakelists.txt 文件中 VAR 的当前值。

虽然我们可以直接在源代码中编辑版本,但首选使用此功能,因为它创建了单一的数据源并避免了重复。

目标

定义并报告项目的版本号。

有用的资源

要编辑的文件

  • CMakeLists.txt

  • tutorial.cxx

  • TutorialConfig.h.in

入门

继续编辑 Step1 中的文件。从 TODO 7 开始,完成到 TODO 12。在本练习中,我们首先在 CMakeLists.txt 中添加一个项目版本号。在同一个文件中,使用 configure_file() 将给定的输入文件复制到输出文件,并替换输入文件内容中的一些变量值。

接下来,创建一个输入头文件 TutorialConfig.h.in,其中定义版本号,该版本号将接受从 configure_file() 传递的变量。

最后,更新 tutorial.cxx 以打印其版本号。

构建和运行

让我们再次构建我们的项目。像以前一样,我们已经创建了一个构建目录并运行了 CMake,所以我们可以跳到构建步骤

cd Step1_build
cmake --build .

验证在不带任何参数运行可执行文件时是否报告了版本号。

解决方案

在此练习中,我们通过打印版本号来改进我们的可执行文件。虽然我们可以在源代码中单独完成此操作,但使用 CMakeLists.txt 允许我们为版本号维护单一的数据源。

首先,我们修改 CMakeLists.txt 文件以使用 project() 命令设置项目名称和版本号。当调用 project() 命令时,CMake 会在后台定义 Tutorial_VERSION_MAJORTutorial_VERSION_MINOR

TODO 7:点击显示/隐藏答案
TODO 7:CMakeLists.txt
project(Tutorial VERSION 1.0)

然后我们使用 configure_file() 复制已替换指定 CMake 变量的输入文件

TODO 8:点击显示/隐藏答案
TODO 8:CMakeLists.txt
configure_file(TutorialConfig.h.in TutorialConfig.h)

由于配置的文件将写入项目二进制目录,因此我们必须将该目录添加到包含文件搜索路径列表中。

注意:在本教程中,我们将交替使用项目构建目录和项目二进制目录。它们是相同的,并非指 bin/ 目录。

我们使用 target_include_directories() 指定可执行目标应在何处查找包含文件。

TODO 9:点击显示/隐藏答案
TODO 9:CMakeLists.txt
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

TutorialConfig.h.in 是要配置的输入头文件。当从我们的 CMakeLists.txt 调用 configure_file() 时,@Tutorial_VERSION_MAJOR@@Tutorial_VERSION_MINOR@ 的值将替换为 TutorialConfig.h 中项目对应的版本号。

TODO 10:点击显示/隐藏答案
TODO 10:TutorialConfig.h.in
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

接下来,我们需要修改 tutorial.cxx 以包含配置好的头文件 TutorialConfig.h

TODO 11:点击显示/隐藏答案
TODO 11:tutorial.cxx
#include "TutorialConfig.h"

最后,我们通过如下更新 tutorial.cxx 来打印可执行文件名称和版本号

TODO 12:点击显示/隐藏答案
TODO 12:tutorial.cxx
  if (argc < 2) {
    // report version
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
              << Tutorial_VERSION_MINOR << std::endl;
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }