mdn-cmk-cpp-2e-merge-4

面向 C++ 的现代 CMake 第二版(五)

原文:zh.annas-archive.org/md5/4abd6886e8722cebdc63cd42f86a9282

译者:飞龙

协议:CC BY-NC-SA 4.0

第十六章:编写 CMake 预设

预设是在 CMake 3.19 版本中加入的,旨在简化项目设置的管理。在有了预设之前,用户必须记住冗长的命令行配置,或者直接在项目文件中设置覆盖项,这样的做法容易出错并且变得复杂。使用预设后,用户可以更加简便地处理项目配置时所使用的生成器、并行构建任务的数量,以及需要构建或测试的项目组件等设置。使用预设后,CMake 变得更加易用。用户只需配置一次预设,之后可以随时使用它们,这使得每次执行 CMake 时更加一致,且更容易理解。预设还帮助标准化不同用户和计算机之间的设置,简化了协作项目的工作。

预设与 CMake 的四种主要模式兼容:配置构建系统、构建、运行测试和打包。它们允许用户将这些部分连接在一起,形成工作流,使整个过程更加自动化和有序。此外,预设还提供了条件和宏表达式(或简单地称为宏)等功能,赋予用户更大的控制力。

本章将涵盖以下主要内容:

  • 在项目中使用定义的预设

  • 编写预设文件

  • 定义特定阶段的预设

  • 定义工作流预设

  • 添加条件和宏

技术要求

你可以在 GitHub 上找到本章中出现的代码文件,地址为github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch16

本章中执行示例所需的命令将在每个部分中提供。

在项目中使用定义的预设

当我们需要具体指定诸如缓存变量、选择的生成器等元素时,项目配置可能变得非常复杂,尤其是在有多种方式可以构建项目时。这时预设就显得非常有用。我们可以通过创建一个预设文件,并将所需的配置存储在项目中,避免记住命令行参数或编写 shell 脚本以不同的参数执行cmake

CMake 使用两个可选文件来存储项目预设:

  • CMakePresets.json:由项目作者提供的官方预设。

  • CMakeUserPresets.json:专为希望向项目添加自定义预设的用户设计。项目应将此文件添加到版本控制忽略列表,以确保自定义设置不会无意间共享到仓库中。

预设文件必须放在项目的顶级目录中,CMake 才能识别它们。每个预设文件可以为每个阶段(配置、构建、测试、打包以及涵盖多个阶段的工作流预设)定义多个预设。用户随后可以通过 IDE、GUI 或命令行选择一个预设进行执行。

通过在命令行中添加--list-presets参数,可以列出预设,具体取决于我们要列出的阶段。例如,可以使用以下命令列出构建预设:

cmake --build --list-presets 

测试预设可以通过以下命令列出:

ctest --list-presets 

要使用预设,我们需要遵循相同的模式,并在--preset参数后提供预设名称。

此外,无法使用cmake命令列出包预设;需要使用cpack。以下是包预设的命令行:

cpack --preset <preset-name> 

选择预设后,当然可以添加阶段特定的命令行参数,例如,指定构建树或安装路径。添加的参数将覆盖预设中设置的任何内容。

工作流预设有一个特殊情况,如果在运行cmake命令时附加了--workflow参数,则可以列出并应用它们:

$ cmake --workflow --list-presets
Available workflow presets:
  "myWorkflow"
$ cmake --workflow --preset myWorkflow
Executing workflow step 1 of 4: configure preset "myConfigure"
... 

这就是如何在项目中应用和查看可用预设。现在,让我们来探索预设文件的结构。

编写预设文件

CMake 会在顶层目录中查找CMakePresets.jsonCMakeUserPresets.json文件。这两个文件使用相同的 JSON 结构来定义预设,因此它们之间没有太大的区别可以讨论。该格式是一个包含以下键的 JSON 对象:

  • version:这是一个必需的整数,指定预设 JSON 架构的版本。

  • cmakeMinimumRequired:这是一个对象,指定了所需的 CMake 版本。

  • include:这是一个字符串数组,包含从数组中提供的文件路径加载的外部预设(自 schema 版本 4 起)。

  • configurePresets:这是一个对象数组,用于定义配置阶段的预设。

  • buildPresets:这是一个对象数组,用于定义构建阶段的预设。

  • testPresets:这是一个对象数组,特定于测试阶段的预设。

  • packagePresets:这是一个对象数组,特定于包阶段的预设。

  • workflowPresets:这是一个对象数组,特定于工作流模式的预设。

  • vendor:这是一个包含由 IDE 和其他供应商定义的自定义设置的对象;CMake 不会处理此字段。

在编写预设时,CMake 要求version条目必须存在;其他值是可选的。这里有一个预设文件示例(实际的预设将在后续章节中添加):

ch16/01-presets/CMakePresets.json

{
  "version": 6,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 26,
    "patch": 0
  },
  "include": [],
  "configurePresets": [],
  "buildPresets": [],
  "testPresets": [],
  "packagePresets": [],
  "workflowPresets": [],
  "vendor": {
    "data": "IDE-specific information"
  }
} 

不需要像前面的示例那样添加空数组;除了version之外的条目是可选的。说到这,CMake 3.26 的适用架构版本是6

现在我们了解了预设文件的结构,让我们来实际学习如何定义这些预设。

定义阶段特定的预设

特定于阶段的预设只是配置单独 CMake 阶段的预设:配置、构建、测试、打包和安装。它们允许以精细且结构化的方式定义构建配置。以下是所有预设阶段共享的常见特性概述,之后将介绍如何为单独的阶段定义预设。

预设的共同特性

有三个特性用于配置预设,无论是哪个 CMake 阶段。即,这些是唯一名称字段、可选字段以及与配置预设的关联。以下各节将分别介绍这些内容。

唯一名称字段

每个预设在其阶段内必须具有唯一的名称字段。鉴于 CMakeUserPresets.json(如果存在)隐式包含了 CMakePresets.json(如果存在),这两个文件共享命名空间,防止它们之间出现重复名称。例如,你不能在这两个文件中都有名为 myPreset 的打包阶段预设。

一个最小的预设文件可能如下所示:

{
  "version": 6,
  "configurePresets": [
    {
      "name": "myPreset"
    },
    {
      "name": "myPreset2"
    }
  ]
} 

可选字段

每个特定于阶段的预设都可以使用相同的可选字段:

  • displayName:这是一个字符串,提供预设的用户友好名称。

  • description:这是一个字符串,提供预设功能的说明。

  • inherits:这是一个字符串,或一个字符串数组,它有效地复制此字段中命名的预设的配置作为基础,进一步扩展或修改。

  • hidden:这是一个布尔值,用于将预设从列表中隐藏;这些隐藏的预设只能通过继承使用。

  • environment:这是一个对象,用于覆盖此阶段的 ENV 变量;每个键标识一个单独的变量,值可以是字符串或 null;它支持宏。

  • condition:这是一个对象,用于启用或禁用此预设(稍后我们会详细讲解)。

  • vendor:这是一个自定义对象,包含供应商特定的值,并遵循与根级别 vendor 字段相同的约定。

预设可以形成类似图形的继承结构,只要没有循环依赖。CMakeUserPresets.json 可以从项目级别的预设继承,但反过来则不行。

与配置阶段预设的关联

所有特定于阶段的预设都必须与配置预设相关联,因为它们必须知道构建树的位置。虽然 configure 预设本身默认与自身相关联,但构建、测试和打包预设需要通过 configurePreset 字段显式定义这种关联。

与你可能想到的不同,这种关联并不意味着 CMake 会在你决定运行任何后续预设时自动执行配置预设。你仍然需要手动执行每个预设,或者使用工作流预设(稍后我们会讨论这个)。

在掌握了这些基础概念后,我们可以继续进入单个阶段的预设细节,首先是配置阶段。随着进展,我们将探索这些预设如何相互作用,以及它们如何帮助简化 CMake 中的项目配置和构建过程。

定义配置阶段预设

如前所述,配置预设位于configurePresets数组中。通过在命令行中添加--list-presets参数,可以列出它们,特定于配置阶段:

cmake --list-presets 

要使用选择的预设配置项目,请在--preset参数后指定其名称,如下所示:

cmake --preset myConfigurationPreset 

配置预设有一些通用字段,如namedescription,但它还有自己的一套可选字段。以下是最重要字段的简化描述:

  • generator:一个字符串,指定用于预设的生成器;对于 schema 版本 < 3 是必需的

  • architecturetoolset:一个字符串,配置支持这些选项的生成器

  • binaryDir:一个字符串,提供构建树的相对或绝对路径;对于 schema 版本 < 3 是必需的;支持宏

  • installDir:一个字符串,提供安装目录的相对或绝对路径;对于 schema 版本 < 3 是必需的,并且支持宏

  • cacheVariables:定义缓存变量的映射;值支持宏

在定义cacheVariables映射时,请记住项目中变量解析的顺序。如图 16.1所示,任何通过命令行定义的缓存变量将覆盖预设变量。任何缓存或环境预设变量将覆盖来自缓存文件或主机环境的变量。

https://github.com/OpenDocCN/freelearn-c-cpp-pt2-zh/raw/master/docs/mdn-cmk-cpp-2e/img/B19844_16_01.png

图 16.1:预设如何覆盖 CMakeCache.txt 和系统环境变量

让我们声明一个简单的myConfigure配置预设,指定生成器、构建树和安装路径:

ch16/01-presets/CMakePresets.json(续)

...
  "configurePresets": [
    {
      "name": "myConfigure",
      "displayName": "Configure Preset",
      "description": "Ninja generator",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build",
      "installDir": "${sourceDir}/build/install"
    }
  ],
... 

我们已经完成了对configure预设的介绍,这也带我们进入构建阶段预设。

定义构建阶段预设

你不会惊讶地发现,构建预设位于buildPresets数组中。通过在命令行中添加--list-presets参数,可以列出它们,特定于构建阶段:

cmake --build --list-presets 

要使用选择的预设构建项目,请在--preset参数后指定其名称,如下所示:

cmake --build --preset myBuildingPreset 

构建预设也有一些通用字段,如namedescription,并且它有自己的一套可选字段。最重要字段的简化描述如下:

  • jobs:一个整数,设置用于构建项目的并行作业数

  • targets:一个字符串或字符串数组,设置要构建的目标,并支持宏

  • configuration:一个字符串,确定多配置生成器的构建类型(DebugRelease等)

  • cleanFirst: 一个布尔值,确保在构建之前始终清理项目

就这样。现在,我们可以像这样编写构建预设:

ch16/01-presets/CMakePresets.json(续)

...
  "buildPresets": [
    {
      "name": "myBuild",
      "displayName": "Build Preset",
      "description": "Four jobs",
      "configurePreset": "myConfigure",
      "jobs": 4
    }
  ],
... 

你会注意到,所需的configurePreset字段已设置为指向我们在前一部分定义的myConfigure预设。现在,我们可以继续进行测试预设。

定义测试阶段预设

测试预设位于testPresets数组中。可以通过在命令行中添加--list-presets参数显示它们,特定于测试阶段:

ctest --list-presets 

要使用预设测试项目,请在--preset参数后指定其名称,如下所示:

ctest --preset myTestPreset 

测试预设也有一组独特的可选字段。以下是最重要字段的简化描述:

  • configuration: 一个字符串,决定多配置生成器的构建类型(如DebugRelease等)

  • output: 一个对象,配置输出

  • filter: 一个对象,指定要运行的测试

  • execution: 一个对象,配置测试的执行

每个对象将适当的命令行选项映射到配置值。我们将突出显示一些重要选项,但这并不是详尽无遗的列表。完整参考请查阅进一步阅读部分。

output对象的可选条目包括:

  • shortProgress: 布尔值;进度将在一行内报告

  • verbosity: 一个字符串,将输出详细程度设置为以下级别之一:默认、详细或额外

  • outputOnFailure: 一个布尔值,在测试失败时打印程序输出

  • quiet: 布尔值;抑制所有输出

对于 exclude,一些接受的条目包括:

  • name: 一个字符串,用于排除名称匹配正则表达式模式的测试,并支持宏

  • label: 一个字符串,用于排除标签匹配正则表达式模式的测试,并支持宏

  • fixtures: 一个对象,决定从测试中排除哪些固定装置(更多详情请参阅官方文档)

最后,执行对象接受以下可选条目:

  • outputLogFile: 一个字符串,指定输出日志文件路径,并支持宏

filter对象接受includeexclude键来配置测试用例的过滤;以下是一个部分填充的结构来说明这一点:

 "testPresets": [
    {
      "name": "myTest",
      "configurePreset": "myConfigure",
      "filter": {
        "include": {
                     ... name, label, index, useUnion ...
                   },
        "exclude": {
                     ... name, label, fixtures ...
                   }
      }
    }
  ],
... 

每个键定义了它自己的选项对象:

对于include,条目包括:

  • name: 一个字符串,用于包含名称匹配正则表达式模式的测试,并支持宏

  • label: 一个字符串,用于包含标签匹配正则表达式模式的测试,并支持宏

  • index: 一个对象,选择要运行的测试,并接受startendstride整数,以及一个specificTests整数数组;它支持宏

  • useUnion: 一个布尔值,启用使用由indexname确定的测试的并集,而不是交集

对于exclude,条目包括:

  • name: 一个字符串,用于排除名称匹配正则表达式模式的测试,并支持宏

  • label: 一个字符串,排除与正则表达式匹配的标签的测试,并支持宏

  • fixtures: 一个对象,确定要从测试中排除的固定项(更多详细信息请参见官方文档)

最后,execution对象可以添加到此处:

 "testPresets": [
    {
      "name": "myTest",
      "configurePreset": "myConfigure",
      "execution": {
                   ... stopOnFailure, enableFailover, ...
                   ... jobs, repeat, scheduleRandom,  ...
                   ... timeout, noTestsAction ...
                   }     
    }
  ],
... 

它接受以下可选条目:

  • stopOnFailure: 一个布尔值,启用在任何测试失败时停止测试

  • enableFailover: 一个布尔值,表示是否恢复先前中断的测试

  • jobs: 一个整数,表示并行运行多个测试的数量

  • repeat: 一个对象,用于确定如何重复测试;该对象必须包含以下字段:

    • mode – 一个字符串,具有以下之一的值:until-failuntil-passafter-timeout

    • count – 一个整数,确定重复的次数

  • scheduleRandom: 一个布尔值,启用随机顺序执行测试

  • timeout: 一个整数,设置所有测试总执行时间的限制(以秒为单位)

  • noTestsAction: 一个定义如果未找到测试时采取的行动的字符串,选项包括defaulterrorignore

虽然有许多配置选项,但简单的预设也是可行的:

ch16/01-presets/CMakePresets.json(续)

...
  "testPresets": [
    {
      "name": "myTest",
      "displayName": "Test Preset",
      "description": "Output short progress",
      "configurePreset": "myConfigure",
      "output": {
        "shortProgress": true
      }
    }
  ],
... 

与构建预设一样,我们还需要为新的测试预设设置必需的configurePreset字段,以便将所有内容整齐地连接在一起。让我们来看看最后一种特定于阶段的预设类型——包预设。

定义包阶段预设

包预设在模式版本6中被引入,这意味着你需要至少 CMake 3.25 版本才能使用它们。这些预设应包含在packagePresets数组中。你也可以通过在命令行中添加--list-presets参数来显示它们,这对于特定的测试阶段有效:

cpack --list-presets 

要使用预设创建项目包,指定其名称,方法是在--preset参数后面添加预设名称,如下所示:

cpack --preset myTestPreset 

包预设利用与其他预设相同的共享字段,同时引入一些特定于它自己的可选字段:

  • generators: 一个字符串数组,设置用于创建包的生成器(ZIP7ZDEB等)

  • configuration: 一个字符串数组,确定 CMake 打包时所使用的构建类型列表(DebugRelease等)

  • filter: 一个对象,指定要运行的测试

  • packageNamepackageVersionpackageDirectoryvendorName: 字符串,指定已创建包的元数据

让我们也用一个简洁的包预设来扩展我们的预设文件:

ch16/01-presets/CMakePresets.json(续)

...
  "packagePresets": [
    {
      "name": "myPackage",
      "displayName": "Package Preset",
      "description": "ZIP generator",
      "configurePreset": "myConfigure",
      "generators": [
        "ZIP"
      ]
    }
  ],
... 

这种配置将使我们能够简化项目包的创建,但我们仍然缺少一个关键部分:项目安装。让我们看看如何使它正常工作。

添加安装预设

你可能注意到,CMakePresets.json 对象不支持定义 "installPresets"。通过预设没有明确的方式来安装你的项目,这似乎很奇怪,因为配置预设提供了 installDir 字段!那么,我们是否必须依赖手动安装命令呢?

幸运的是,不需要。我们有一个变通方法,可以通过使用构建预设来实现我们的目标。请看下面:

ch16/01-presets/CMakePresets.json(续)

...
  "buildPresets": [
    {
      "name": "myBuild",
      ...
    },
    {
      "name": "myInstall",
      "displayName": "Installation",
      **"targets"****:****"install"****,**
      "configurePreset": "myConfigure"
    }
  ],
... 

我们可以创建一个构建预设,将 targets 字段设置为 install。当我们正确配置安装时,install 目标由项目隐式定义。使用此预设进行构建将执行必要的步骤,将项目安装到关联配置预设中指定的 installDir(如果 installDir 字段为空,将使用默认位置):

$ cmake --build --preset myInstall
[0/1] Install the project...
-- Install configuration: ""
-- Installing: .../install/include/calc/basic.h
-- Installing: .../install/lib/libcalc_shared.so
-- Installing: .../install/lib/libcalc_static.a
-- Installing: .../install/lib/calc/cmake/CalcLibrary.cmake
-- Installing: .../install/lib/calc/cmake/CalcLibrary-noconfig.cmake
-- Installing: .../install/lib/calc/cmake/CalcConfig.cmake
-- Installing: .../install/bin/calc_console
-- Set non-toolchain portion of runtime path of ".../install/bin/calc_console" to "" 

这个巧妙的技巧可以帮助我们节省一些资源。如果我们能为最终用户提供一个单一命令,处理从配置到安装的所有操作,那将更好。好吧,我们可以,使用工作流预设。让我们看一下。

定义工作流预设

工作流预设是我们项目的终极自动化解决方案。它们允许我们按预定顺序自动执行多个阶段特定的预设。这样,我们几乎可以在一步操作中完成端到端的构建。

要发现项目的可用工作流,我们可以执行以下命令:

cmake --workflow --list-presets 

要选择并应用预设,请使用以下命令:

cmake –workflow --preset <preset-name> 

此外,通过 --fresh 标志,我们可以清除构建树并清除缓存。

定义工作流预设非常简单;我们需要定义一个名称,且可以选择性地提供 displayNamedescription,就像为阶段特定预设定义一样。之后,我们必须列出工作流应该执行的所有阶段特定预设。这是通过提供一个包含 typename 属性的对象数组 steps 来完成的,如下所示:

ch16/01-presets/CMakePresets.json(续)

...
  "workflowPresets": [
    {
      "name": "myWorkflow",
      "steps": [
        {
          "type": "configure",
          "name": "myConfigure"
        },
        {
          "type": "build",
          "name": "myBuild"
        },
        {
          "type": "test",
          "name": "myTest"
        },
        {
          "type": "package",
          "name": "myPackage"
        },
        {
          "type": "build",
          "name": "myInstall"
        }
      ]
... 

steps 数组中的每个对象都引用了我们在本章前面定义的预设,指明其类型(configurebuildtestpackage)以及名称。这些预设共同执行所有必要的步骤,通过一个命令从零开始完全构建和安装项目:

cmake --workflow --preset myWorkflow 

工作流预设是自动化 C++ 构建、测试、打包和安装的终极解决方案。接下来,让我们探索如何通过条件和宏来管理一些边缘情况。

添加条件和宏

当我们讨论每个阶段特定预设的通用字段时,我们提到了 condition 字段。现在是时候回到这个话题了。condition 字段启用或禁用预设,并在与工作流集成时展现其真正的潜力。本质上,它允许我们绕过在某些条件下不适用的预设,并创建适用的替代预设。

条件要求预设架构版本为 3 或更高版本(在 CMake 3.22 中引入),并且是 JSON 对象,用于编码一些简单的逻辑操作,以判断诸如操作系统、环境变量,甚至所选生成器等情况是否符合预设的场景。CMake 通过宏提供这些数据,宏本质上是一组有限的只读变量,可在预设文件中使用。

条件对象的结构根据检查类型有所不同。每个条件必须包含一个 type 字段,并根据类型定义其他字段。已识别的基本类型包括:

  • const:这检查 value 字段中提供的值是否为布尔值 true

  • equalsnotEquals:这些用于将 lhs 字段的值与 rhs 字段中的值进行比较

  • inListnotInList:这些用于检查 string 字段中的值是否出现在 list 字段中的数组中

  • matchesnotMatches:这些用于评估 string 字段的值是否与 regex 字段中定义的模式一致

一个条件示例如下所示:

"condition": {
               "type": "equals",
               "lhs": "${hostSystemName}",
               "rhs": "Windows"
             } 

const 条件的实际用途主要是用于禁用预设,而不将其从 JSON 文件中移除。除了 const 外,所有基本条件都允许在它们引入的字段中使用宏:lhsrhsstringlistregex

高级条件类型,类似于“not”、“and”和“or”操作,使用其他条件作为参数:

  • not:对 condition 字段中提供的条件进行布尔反转

  • anyOfallOf:这些用于检查 conditions 数组中的任意或所有条件是否为 true

例如:

"condition": {
              "type": "anyOf",
              "conditions": [
                              {
                                "type": "equals",
                                "lhs": "${hostSystemName}",
                                "rhs": "Windows"
                              },{
                                "type": "equals",
                                "lhs": "${hostSystemName}",
                                "rhs": "Linux"
                              }
                            ]
             } 

如果系统是 Linux 或 Windows,则此条件评估为 true

通过这些示例,我们介绍了我们的第一个宏:${hostSystemName}。宏遵循简单的语法,并且限于特定实例,如:

  • ${sourceDir}:这是源树的路径

  • ${sourceParentDir}:这是源树父目录的路径

  • ${sourceDirName}:这是项目的目录名称

  • ${presetName}:这是预设的名称

  • ${generator}:这是用于创建构建系统的生成器

  • ${hostSystemName}:这是系统名称:在 Linux 上为 Linux,在 Windows 上为 Windows,在 macOS 上为 Darwin

  • ${fileDir}:这是包含当前预设的文件名(适用于使用 include 数组导入外部预设的情况)

  • ${dollar}:这是转义的美元符号($

  • ${pathListSep}:这是环境特定的路径分隔符

  • $env{<variable-name>}:如果预设中指定了环境变量(区分大小写),则返回该环境变量的值,或返回父环境的值

  • $penv{<variable-name>}:这是从父环境中返回的环境变量

  • $vendor{<macro-name>}:这允许 IDE 供应商引入他们自己的宏

这些宏提供了足够的灵活性,以便在预设及其条件中使用,支持在需要时有效地切换工作流步骤。

摘要

我们刚刚完成了对 CMake 预设的全面概述,这些预设是在 CMake 3.19 中引入的,旨在简化项目管理。预设允许产品作者通过配置项目构建和交付的所有阶段,为用户提供一个精心准备的体验。预设不仅简化了 CMake 的使用,还增强了一致性,并支持环境感知的设置。

我们解释了 CMakePresets.jsonCMakeUserPresets.json 文件的结构和使用,提供了定义各种预设类型的见解,比如配置预设、构建预设、测试预设、打包预设和工作流预设。每种类型都有详细描述:我们了解了常见字段,如何在内部组织预设,如何在它们之间建立继承关系,以及最终用户可用的具体配置选项。

对于配置预设,我们讨论了选择生成器、构建目录和安装目录的重要话题,以及如何通过 configurePreset 字段将预设连接在一起。现在我们知道如何处理构建预设,设置构建作业数量、目标和清理选项。接下来,我们学习了测试预设如何通过广泛的过滤和排序选项、输出格式和执行参数(如超时和容错)来协助测试选择。我们理解了如何通过指定包生成器、过滤器和包元数据来管理打包预设。我们甚至介绍了一种通过专门的构建预设应用执行安装阶段的解决方法。

接下来,我们发现工作流预设如何允许将多个阶段特定的预设进行分组。最后,我们讨论了条件和宏表达式,为项目作者提供了更大的控制力,可以更好地控制各个预设的行为及其在工作流中的集成。

我们的 CMake 之旅已圆满完成!恭喜你——现在你拥有了开发、测试和打包高质量 C++ 软件所需的所有工具。接下来的最佳方式是应用你所学的知识,创建出色的软件供用户使用。祝你好运!

深入阅读

如需更多信息,您可以参考以下资源:

加入我们的社区 Discord

加入我们社区的 Discord 空间,与作者和其他读者进行讨论:

discord.com/invite/vXN53A7ZcA

https://github.com/OpenDocCN/freelearn-c-cpp-pt2-zh/raw/master/docs/mdn-cmk-cpp-2e/img/QR_Code94081075213645359.png

附录

杂项命令

每种语言都包括一些用于各种任务的实用命令,CMake 也不例外。它提供了用于算术运算、按位操作、字符串操作以及列表和文件操作的工具。尽管由于功能增强和多个模块的发展,这些命令的需求有所减少,但在高度自动化的项目中,它们仍然是必不可少的。如今,您可能会发现它们在使用cmake -P <filename>调用的 CMake 脚本中更为有用。

因此,本附录总结了 CMake 命令和其多种模式,作为方便的离线参考或官方文档的简化版。要获取更详细的信息,请查阅提供的链接。

此参考适用于 CMake 3.26.6。

在本附录中,我们将涵盖以下主要内容:

  • string()命令

  • list()命令

  • file()命令

  • math()命令

string()命令

string()命令用于操作字符串。它提供了多种模式,执行不同的操作:搜索和替换、操作、比较、哈希、生成和 JSON 操作(自 CMake 3.19 版本起提供最后一个)。

完整的详细信息可以在在线文档中找到:cmake.org/cmake/help/latest/command/string.html

请注意,接受string()模式的<input>参数将接受多个<input>值,并在执行命令之前将它们连接起来,因此:

string(PREPEND myVariable "a" "b" "c") 

等同于以下内容:

string(PREPEND myVariable "abc") 

可用的string()模式包括搜索和替换、操作、比较、哈希、生成和 JSON。

搜索和替换

以下模式可用:

  • string(FIND <haystack> <pattern> <out> [REVERSE])<haystack>字符串中搜索<pattern>并将找到的位置以整数形式写入<out>变量。如果使用了REVERSE标志,它将从字符串的末尾向前搜索。此操作仅适用于 ASCII 字符串(不支持多字节字符)。

  • string(REPLACE <pattern> <replace> <out> <input>)<input>中的所有<pattern>替换为<replace>,并将结果存储在<out>变量中。

  • string(REGEX MATCH <pattern> <out> <input>)使用正则表达式匹配<input>中第一次出现的<pattern>,并将其存储在<out>变量中。

  • string(REGEX MATCHALL <pattern> <out> <input>)使用正则表达式匹配<input>中所有出现的<pattern>并将其存储在<out>变量中,格式为逗号分隔的列表。

  • string(REGEX REPLACE <pattern> <replace> <out> <input>)正则替换<input>中的所有<pattern>出现,并使用<replace>表达式将它们替换,并将结果存储在<out>变量中。

正则表达式操作遵循 C++ 语法,如 <regex> 头文件中定义的标准库所示。你可以使用捕获组将匹配项添加到 <replace> 表达式中,并使用数字占位符:\\1\\2...(需要使用双反斜杠,以确保参数被正确解析)。

操作

以下模式是可用的:

  • string(APPEND <out> <input>) 通过附加 <input> 字符串来修改存储在 <out> 中的字符串。

  • string(PREPEND <out> <input>) 通过在字符串前添加 <input> 字符串来修改存储在 <out> 中的字符串。

  • string(CONCAT <out> <input>) 将所有提供的 <input> 字符串连接在一起,并将其存储在 <out> 变量中。

  • string(JOIN <glue> <out> <input>) 使用 <glue> 值将所有提供的 <input> 字符串交织在一起,并将其作为连接的字符串存储在 <out> 变量中(不要在列表变量中使用此模式)。

  • string(TOLOWER <string> <out>)<string> 转换为小写并将其存储在 <out> 变量中。

  • string(TOUPPER <string> <out>)<string> 转换为大写并将其存储在 <out> 变量中。

  • string(LENGTH <string> <out>) 计算 <string> 的字节数,并将结果存储在 <out> 变量中。

  • string(SUBSTRING <string> <begin> <length> <out>)<string> 中提取一个子字符串,长度为 <length> 字节,起始位置为 <begin> 字节,并将其存储在 <out> 变量中。提供 -1 作为长度表示“直到字符串的末尾”。

  • string(STRIP <string> <out>) 移除 <string> 的前导和尾随空白字符,并将结果存储在 <out> 变量中。

  • string(GENEX_STRIP <string> <out>) 移除 <string> 中所有使用的生成器表达式,并将结果存储在 <out> 变量中。

  • string(REPEAT <string> <count> <out>) 生成一个包含 <count> 次重复的 <string> 的字符串,并将其存储在 <out> 变量中。

比较

字符串比较采用以下形式:

string(COMPARE <operation> <stringA> <stringB> <out>) 

<operation> 参数是以下之一:

  • LESS

  • GREATER

  • EQUAL

  • NOTEQUAL

  • LESS_EQUAL

  • GREATER_EQUAL

它将用于比较 <stringA><stringB>,并将结果(truefalse)存储在 <out> 变量中。

哈希

哈希模式具有以下签名:

string(<hashing-algorithm> <out> <string>) 

它使用 <hashing-algorithm><string> 进行哈希,并将结果存储在 <out> 变量中。支持以下算法:

  • MD5: 消息摘要算法 5,RFC 1321

  • SHA1: 美国安全哈希算法 1,RFC 3174

  • SHA224: 美国安全哈希算法,RFC 4634

  • SHA256: 美国安全哈希算法,RFC 4634

  • SHA384: 美国安全哈希算法,RFC 4634

  • SHA512: 美国安全哈希算法,RFC 4634

  • SHA3_224: Keccak SHA-3

  • SHA3_256: Keccak SHA-3

  • SHA3_384: Keccak SHA-3

  • SHA3_512: Keccak SHA-3

生成

以下模式是可用的:

  • string(ASCII <number>... <out>) 将给定的 <number> 的 ASCII 字符存储在 <out> 变量中。

  • string(HEX <string> <out>)<string> 转换为其十六进制表示并将其存储在 <out> 变量中(从 CMake 3.18 起)。

  • string(CONFIGURE <string> <out> [@ONLY] [ESCAPE_QUOTES]) 作用与 configure_file() 完全相同,但用于字符串。结果存储在 <out> 变量中。提醒一下,使用 @ONLY 关键字将替换限制为 @VARIABLE@ 形式的变量。

  • string(MAKE_C_IDENTIFIER <string> <out>)<string> 中的非字母数字字符转换为下划线,并将结果存储在 <out> 变量中。

  • string(RANDOM [LENGTH <len>] [ALPHABET <alphabet>] [RANDOM_SEED <seed>] <out>) 生成一个由 <len> 个字符(默认为 5)组成的随机字符串,使用来自随机种子 <seed> 的可选 <alphabet>,并将结果存储在 <out> 变量中。

  • string(TIMESTAMP <out> [<format>] [UTC]) 生成一个表示当前日期和时间的字符串,并将其存储在 <out> 变量中。

  • string(UUID <out> NAMESPACE <ns> NAME <name> TYPE <type>) 生成一个全局唯一标识符。使用此模式稍微复杂一些;你需要提供一个命名空间(必须是 UUID)、一个名称(例如,域名)和一个类型(可以是 MD5SHA1)。

JSON

对 JSON 格式字符串的操作使用以下签名:

string(JSON <out> [ERROR_VARIABLE <error>] <operation + args>) 

有几种操作可以使用。它们都将结果存储在 <out> 变量中,错误存储在 <error> 变量中。操作及其参数如下:

  • GET <json> <member|index>... 返回通过 <member> 路径或 <index><json> 字符串中的一个或多个元素提取值的结果。

  • TYPE <json> <member|index>... 返回通过 <member> 路径或 <index><json> 字符串中的一个或多个元素的类型。

  • MEMBER <json> <member|index>... <array-index> 返回通过 <member> 路径或 <index><json> 字符串中的一个或多个数组类型元素在 <array-index> 位置提取的成员名称。

  • LENGTH <json> <member|index>... 返回通过 <member> 路径或 <index><json> 字符串中的一个或多个数组类型元素的元素数量。

  • REMOVE <json> <member|index>... 返回通过 <member> 路径或 <index><json> 字符串中的一个或多个元素进行移除操作的结果。

  • SET <json> <member|index>... <value> 返回通过 <member> 路径或 <index><json> 字符串中的一个或多个元素进行上插入操作的结果,将 <value> 插入其中。

  • EQUAL <jsonA> <jsonB> 判断 <jsonA><jsonB> 是否相等。

list() 命令

该命令提供基本的列表操作:读取、查找、修改和排序。一些模式会改变列表(修改原始值)。如果之后还需要使用原始值,请确保复制它。

完整的详细信息可以在在线文档中找到:

cmake.org/cmake/help/latest/command/list.html

可用的 list() 模式类别包括读取、搜索、修改和排序。

读取

以下模式可用:

  • list(LENGTH <list> <out>) 计算 <list> 变量中的元素数量,并将结果存储在 <out> 变量中。

  • list(GET <list> <index>... <out>)<list> 中通过 <index> 索引指定的元素复制到 <out> 变量中。

  • list(JOIN <list> <glue> <out>)<list> 元素与 <glue> 分隔符交错连接,并将结果字符串存储在 <out> 变量中。

  • list(SUBLIST <list> <begin> <length> <out>) 的作用类似于 GET 模式,但操作的是范围而非显式索引。如果 <length>-1,则返回从 <begin> 索引到 <list> 变量中提供的列表末尾的所有元素。

搜索

此模式简单地查找 <needle> 元素在 <list> 变量中的索引,并将结果存储在 <out> 变量中(如果元素未找到,则返回 -1):

list(FIND <list> <needle> <out>) 

修改

以下模式可用:

  • list(APPEND <list> <element>...) 将一个或多个 <element> 值添加到 <list> 变量的末尾。

  • list(PREPEND <list> [<element>...]) 的作用类似于 APPEND,但将元素添加到 <list> 变量的开头。

  • list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <pattern>) 根据 <pattern> 值筛选 <list> 变量中的元素,选择 INCLUDEEXCLUDE 匹配的元素。

  • list(INSERT <list> <index> [<element>...]) 将一个或多个 <element> 值添加到 <list> 变量的指定 <index> 位置。

  • list(POP_BACK <list> [<out>...])<list> 变量的末尾移除一个元素,并将其存储在可选的 <out> 变量中。如果提供了多个 <out> 变量,将移除更多的元素以填充它们。

  • list(POP_FRONT <list> [<out>...])POP_BACK 类似,但从 <list> 变量的开头移除一个元素。

  • list(REMOVE_ITEM <list> <value>...)FILTER EXCLUDE 的简写,但不支持正则表达式。

  • list(REMOVE_AT <list> <index>...)<list> 中指定的 <index> 位置移除元素。

  • list(REMOVE_DUPLICATES <list>) 移除 <list> 中的重复元素。

  • list(TRANSFORM <list> <action> [<selector>] [OUTPUT_VARIABLE <out>])<list> 中的元素应用特定的变换。默认情况下,操作应用于所有元素,但我们可以通过添加 <selector> 来限制影响范围。如果没有提供 OUTPUT_VARIABLE 关键字,则提供的列表将被修改(就地改变);如果提供了该关键字,结果将存储在 <out> 变量中。

以下选择器可用:AT <index>FOR <start> <stop> [<step>]REGEX <pattern>

操作包括 APPEND <string>PREPEND <string>TOLOWERTOUPPERSTRIPGENEX_STRIPREPLACE <pattern> <expression>。它们的功能与同名的 string() 模式完全相同。

排序

以下模式可用:

  • list(REVERSE <list>) 简单地反转 <list> 的顺序。

  • list(SORT <list>) 按字母顺序对列表进行排序。

请参考在线手册以获取更多高级选项。

file()命令

该命令提供与文件相关的各种操作:读取、传输、锁定和归档。它还提供检查文件系统和操作表示路径的字符串的模式。

完整的详细信息可以在在线文档中找到:

cmake.org/cmake/help/latest/command/file.html

可用的file()模式类别包括读取、写入、文件系统、路径转换、传输、锁定和归档。

阅读

可用的模式如下:

  • file(READ <filename> <out> [OFFSET <o>] [LIMIT <max>] [HEX])<filename>读取文件到<out>变量中。读取操作可选择从偏移量<o>开始,并遵循可选的<max>字节限制。HEX flag指定输出应转换为十六进制表示。

  • file(STRINGS <filename> <out>)<filename>文件读取字符串并将其存储到<out>变量中。

  • file(<hashing-algorithm> <filename> <out>) 计算来自<filename>文件的<hashing-algorithm>哈希值,并将结果存储到<out>变量中。可用的算法与string()哈希函数相同。

  • file(TIMESTAMP <filename> <out> [<format>]) 生成<filename>文件的时间戳字符串表示,并将其存储到<out>变量中。可选接受一个<format>字符串。

  • file(GET_RUNTIME_DEPENDENCIES [...]) 获取指定文件的运行时依赖项。这是一个高级命令,仅在install(CODE)install(SCRIPT)场景中使用。从 CMake 3.21 版本开始可用。

写入

可用的模式如下:

  • file({WRITE | APPEND} <filename> <content>...) 将所有<content>参数写入或追加到<filename>文件中。如果提供的系统路径不存在,它将被递归创建。

  • file({TOUCH | TOUCH_NOCREATE} [<filename>...]) 更新<filename>的时间戳。如果文件不存在,则仅在TOUCH模式下创建该文件。

  • file(GENERATE OUTPUT <output-file> [...]) 是一个高级模式,它为当前 CMake 生成器的每个构建配置生成一个输出文件。

  • file(CONFIGURE OUTPUT <output-file> CONTENT <content> [...])GENERATE_OUTPUT类似,但还会通过将变量占位符替换为值来配置生成的文件。

文件系统

可用的模式如下:

  • file({GLOB | GLOB_RECURSE} <out> [...] [<globbing-expression>...]) 生成与<globbing-expression>匹配的文件列表,并将其存储在<out>变量中。GLOB_RECURSE模式还会扫描嵌套目录。

  • file(RENAME <oldname> <newname>) 将文件从<oldname>移动到<newname>

  • file({REMOVE | REMOVE_RECURSE } [<files>...]) 删除<files>REMOVE_RECURSE模式还会删除目录。

  • file(MAKE_DIRECTORY [<dir>...]) 创建一个目录。

  • file(COPY <file>... DESTINATION <dir> [...])将文件复制到<dir>目标路径。它提供了过滤、设置权限、符号链接链跟踪等选项。

  • file(COPY_FILE <file> <destination> [...])将单个文件复制到<destination>路径。从 CMake 3.21 版本开始提供。

  • file(SIZE <filename> <out>)读取<filename>的字节大小,并将其存储在<out>变量中。

  • file(READ_SYMLINK <linkname> <out>)读取<linkname>符号链接的目标路径,并将其存储在<out>变量中。

  • file(CREATE_LINK <original> <linkname> [...])<linkname>处创建指向<original>的符号链接。

  • file({CHMOD|CHMOD_RECURSE} <files>... <directories>... PERMISSIONS <permissions>... [...])设置文件和目录的权限。

  • file(GET_RUNTIME_DEPENDENCIES [...])收集各种文件类型的运行时依赖项:可执行文件、库文件和模块。与install(RUNTIME_DEPENDENCY_SET)一起使用。

路径转换

以下模式可用:

  • file(REAL_PATH <path> <out> [BASE_DIRECTORY <dir>])计算从相对路径到绝对路径,并将其存储在<out>变量中。它可以选择性地接受<dir>作为基础目录。从 CMake 3.19 版本开始提供。

  • file(RELATIVE_PATH <out> <directory> <file>)计算<file>相对于<directory>的路径,并将其存储在<out>变量中。

  • file({TO_CMAKE_PATH | TO_NATIVE_PATH} <path> <out>)<path>转换为 CMake 路径(目录以正斜杠分隔),转换为平台的本地路径,并反向转换。结果存储在<out>变量中。

传输

以下模式可用:

  • file(DOWNLOAD <url> [<path>] [...])<url>下载文件并将其存储在<path>中。

  • file(UPLOAD <file> <url> [...])<file>上传到 URL。

锁定

锁定模式对<path>资源加上建议性锁:

file(LOCK <path> [DIRECTORY] [RELEASE]
     [GUARD <FUNCTION|FILE|PROCESS>]
     [RESULT_VARIABLE <out>] [TIMEOUT <seconds>]
) 

此锁可以选择性地限定为FUNCTIONFILEPROCESS,并限制超时时间为<seconds>。要释放锁,请提供RELEASE关键字。结果将存储在<out>变量中。

归档

创建归档提供了以下签名:

file(ARCHIVE_CREATE OUTPUT <destination> PATHS <source>...
  [FORMAT <format>]
  [COMPRESSION <type> [COMPRESSION_LEVEL <level>]]
  [MTIME <mtime>] [VERBOSE]
) 

它将在<destination>路径创建一个包含<source>文件的归档,格式为支持的格式之一:7zipgnutarpaxpaxrrawzip(默认格式为paxr)。如果所选格式支持压缩级别,则可以提供一个单数字符号0-9,其中0为默认值。

提取模式具有以下签名:

file(ARCHIVE_EXTRACT INPUT <archive> [DESTINATION <dir>]
  [PATTERNS <patterns>...] [LIST_ONLY] [VERBOSE]
) 

它从<archive>中提取与可选的<patterns>值匹配的文件到目标<dir>。如果提供了LIST_ONLY关键字,则不会提取文件,而是仅列出文件。

math()命令

CMake 还支持一些简单的算术运算。详细信息请参阅在线文档:

cmake.org/cmake/help/latest/command/math.html

要评估一个数学表达式并将其作为字符串存储在 <out> 变量中,可以选择 <format>HEXADECIMALDECIMAL),使用以下签名:

math(EXPR <out> "<expression>" [OUTPUT_FORMAT <format>]) 

<expression> 值是一个字符串,支持 C 代码中存在的运算符(它们在这里具有相同的含义):

  • 算术运算:+-*/,和 % 取模除法

  • 位运算:| 或,& 与,^ 异或,~ 非,<< 左移,>> 右移

  • 括号 (...)

常量值可以以十进制或十六进制格式提供。

posted @ 2025-03-03 17:58  绝不原创的飞龙  阅读(78)  评论(0)    收藏  举报