第一步:基本起点

我应该如何开始使用 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 1” 到 “TODO 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” 中将 “atof” 替换为 “std::stod” 来为我们的项目添加一些 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”。 确保在调用 add_executable() 之前添加 CMAKE_CXX_STANDARD 声明。

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_MAJOR” 和 “Tutorial_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;
  }