面向 C++ 的现代 CMake 教程(四)(3)

简介: 面向 C++ 的现代 CMake 教程(四)

面向 C++ 的现代 CMake 教程(四)(2)https://developer.aliyun.com/article/1526940

第十一章:安装和打包

我们的项目已经构建、测试并文档化。现在,终于到了将其发布给用户的时候。本章主要介绍我们将要采取的最后两个步骤:安装和打包。这些都是建立在迄今为止我们所学习的一切之上的高级技术:管理目标和它们的依赖关系、瞬态使用需求、生成器表达式等等。

安装使我们的项目能够在系统范围内被发现和访问。在本章中,我们将介绍如何导出目标,以便另一个项目可以在不安装的情况下使用它们,以及如何安装我们的项目,以便它们可以很容易地被系统上的任何程序使用。特别是,我们将学习如何配置我们的项目,使其可以自动将不同类型的工件放入正确的目录中。为了处理更高级的场景,我们将介绍用于安装文件和目录的低级命令,以及用于执行自定义脚本和 CMake 命令的命令。

接下来,我们将学习如何设置可重用的 CMake 包,以便它们可以被其他项目通过调用find_package()发现。具体来说,我们将解释如何确保目标和它们的定义不会固定在文件系统的特定位置。我们还将讨论如何编写基本和高级的配置文件,以及与包关联的版本文件

然后,为了使事情模块化,我们将简要介绍组件的概念,包括 CMake 包和install()命令。所有这些准备将为本章我们将要涵盖的最后方面铺平道路:使用 CPack 生成各种包管理器在不同操作系统中认识的归档文件、安装程序、捆绑包和包。这些可以用来携带预构建的工件、可执行文件和库。这是最终用户开始使用我们的软件的最简单方法。

在本章中,我们将涵盖以下主要主题:

  • 无需安装导出
  • 在系统上安装项目
  • 创建可重用的包
  • 定义组件
  • 使用 CPack 打包

技术要求

您可以在 GitHub 上找到本章的代码文件:github.com/PacktPublishing/Modern-CMake-for-Cpp/tree/main/examples/chapter11

要构建本书中提供的示例,请始终使用推荐命令:

cmake -B <build tree> -S <source tree>
cmake --build <build tree>

请确保将占位符替换为合适的路径。作为提醒:构建树是目标/输出目录的路径,源树是您的源代码所在的路径。

无需安装导出

我们如何使项目A的目标对消费项目B可用?通常,我们会使用find_package()命令,但这意味着我们需要创建一个包并在系统上安装它。这种方法很有用,但需要一些工作。有时,我们只是需要一种快速的方法来构建一个项目,并使其目标对其他项目可用。

我们可以通过包含A的主列表文件来节省一些时间:它已经包含了所有的目标定义。不幸的是,它也可能包含很多其他内容:全局配置、需求、具有副作用的 CMake 命令、附加依赖项,以及我们可能不想在B中出现的目标(如单元测试)。所以,我们不要这样做。更好的方法是提供B,并通过include()命令包含:

cmake_minimum_required(VERSION 3.20.0)
project(B)
include(/path/to/project-A/ProjectATargets.cmake)

执行此操作将为A的所有目标提供正确的属性集定义(如add_library()add_executable()等命令)。

当然,我们不会手动写这样的文件——这不会是一个非常 DRY 的方法。CMake 可以用export()命令为我们生成这些文件,该命令具有以下签名:

export(TARGETS [target1 [target2 [...]]] 
  [NAMESPACE <namespace>] [APPEND] FILE <path>
  [EXPORT_LINK_INTERFACE_LIBRARIES])

我们必须提供所有我们想要导出的目标,在TARGET关键字之后,并提供目标文件名在FILE之后。其他参数是可选的:

  • NAMESPACE建议作为一个提示,说明目标已经从其他项目中导入。
  • APPEND告诉 CMake 在写入文件之前不要擦除文件的内容。
  • EXPORT_LINK_INTERFACE_LIBRARIES将导出目标链接依赖(包括导入和配置特定的变体)。

让我们用我们示例中的 Calc 库来看看这个功能,它提供了两个简单的方法:

chapter-11/01-export/src/include/calc/calc.h

#pragma once
int Sum(int a, int b);
int Multiply(int a, int b);

我们这样声明它的目标:

chapter-11/01-export/src/CMakeLists.txt

add_library(calc STATIC calc.cpp)
target_include_directories(calc INTERFACE include)

然后,我们要求 CMake 使用export(TARGETS)命令生成导出文件:

chapter-11/01-export/CMakeLists.txt(片段)

cmake_minimum_required(VERSION 3.20.0)
project(ExportCalcCXX)
add_subdirectory(src bin)
set(EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake")
export(TARGETS calc
  FILE "${EXPORT_DIR}/CalcTargets.cmake"
  NAMESPACE Calc::
)
...

在前面的代码中,我们可以看到EXPORT_DIR变量已被设置为构建树中的cmake子目录(按照.cmake文件的约定)。然后,我们导出目标声明文件CalcTargets.cmake,其中有一个名为calc的单一目标,对于将包含此文件的工程项目,它将作为Calc::calc可见。

请注意,这个导出文件还不是包。更重要的是,这个文件中的所有路径都是绝对的,且硬编码到构建树中。换句话说,它们是不可移动的(我们将在理解可移动目标的问题部分讨论这个问题)。

export()命令还有一个更短的版本:

export(EXPORT <export> [NAMESPACE <namespace>] [FILE
  <path>])

然而,它需要一个名称,而不是一个导出的目标列表。这样的实例是由install(TARGETS)定义的目标的命名列表(我们将在安装逻辑目标部分介绍这个命令)。以下是一个演示如何在实际中使用这种简写法的微型示例:

chapter-11/01-export/CMakeLists.txt(续)

...
install(TARGETS calc EXPORT CalcTargets)
export(EXPORT CalcTargets
  FILE "${EXPORT_DIR}/CalcTargets2.cmake"
  NAMESPACE Calc::
)

前面的代码与之前的代码完全一样,但现在,export()install() 命令之间的单个目标列表被共享。

生成导出文件的两个方法会产生相同的结果。它们将包含一些模板代码和几行定义目标的内容。将 /tmp/b 设置为构建树路径时,它们看起来像这样:

/tmp/b/cmake/CalcTargets.cmake(片段)

# Create imported target Calc::calc
add_library(Calc::calc STATIC IMPORTED)
set_target_properties(Calc::calc PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES
  "/root/examples/chapter11/01-export/src/include"
)
# Import target "Calc::calc" for configuration ""
set_property(TARGET Calc::calc APPEND PROPERTY
  IMPORTED_CONFIGURATIONS NOCONFIG
)
set_target_properties(Calc::calc PROPERTIES
  IMPORTED_LINK_INTERFACE_LANGUAGES_NOCONFIG "CXX"
  IMPORTED_LOCATION_NOCONFIG "/tmp/b/libcalc.a"
)

通常,我们不会编辑这个文件,甚至不会打开它,但我想要强调这个生成文件中的硬编码路径。以其当前形式,这个包是不可移动的。如果我们想要改变这一点,我们首先需要做一些跳跃。我们将在下一节探讨为什么这很重要。

在系统上安装项目

在第章 1 CMake 初学者中,我们提到 CMake 提供了一个命令行模式,可以在系统上安装构建好的项目:

cmake --install <dir> [<options>]

是生成构建树的目标路径(必需)。我们的 如下:

  • --config :这对于多配置生成器,选择构建配置。
  • --component :这限制了安装到给定组件。
  • --default-directory-permissions :这设置了安装目录的默认权限(在 格式中)。
  • --prefix :这指定了非默认的安装路径(存储在 CMAKE_INSTALL_PREFIX 变量中)。对于类 Unix 系统,默认为 /usr/local,对于 Windows,默认为 c:/Program Files/${PROJECT_NAME}
  • -v, --verbose:这会使输出详细(这也可以通过设置 VERBOSE 环境变量来实现)。

安装可以由许多步骤组成,但它们的本质是将生成的工件和必要的依赖项复制到系统上的某个目录中。使用 CMake 进行安装不仅为所有 CMake 项目引入了一个方便的标准,而且还做了以下事情:

  • 为根据它们的类型提供特定于平台的安装路径(遵循GNU 编码标准
  • 通过生成目标导出文件,增强安装过程,允许项目目标直接被其他项目重用
  • 通过配置文件创建可发现的包,这些文件封装了目标导出文件以及作者定义的特定于包的 CMake 宏和函数

这些功能非常强大,因为它们节省了很多时间,并简化了以这种方式准备的项目使用。执行基本安装的第一步是将构建好的工件复制到目标目录。

这让我们来到了 install() 命令及其各种模式:

  • install(TARGETS):这会安装输出工件,如库和可执行文件。
  • install(FILES|PROGRAMS):这会安装单个文件并设置它们的权限。
  • install(DIRECTORY): 这会安装整个目录。
  • install(SCRIPT|CODE):在安装期间运行 CMake 脚本或代码段。
  • install(EXPORT):这生成并安装一个目标导出文件。

将这些命令添加到您的列表文件中将生成一个cmake_install.cmake文件在您的构建树中。虽然可以手动调用此脚本使用cmake -P,但不建议这样做。这个文件是用来在执行cmake --install时由 CMake 内部使用的。

注意

即将推出的 CMake 版本还将支持安装运行时工件和依赖集合,因此请务必查阅最新文档以了解更多信息。

每个install()模式都有一组广泛的选项。其中一些是共享的,并且工作方式相同:

  • DESTINATION:这指定了安装路径。相对路径将前缀CMAKE_INSTALL_PREFIX,而绝对路径则直接使用(并且cpack不支持)。
  • PERMISSIONS:这设置了支持它们的平台上的文件权限。可用的值有OWNER_READOWNER_WRITEOWNER_EXECUTEGROUP_READGROUP_WRITEGROUP_EXECUTEWORLD_READWORLD_WRITEWORLD_EXECUTESETUIDSETGID。在安装期间创建的目录的默认权限可以通过指定CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS变量来设置。
  • CONFIGURATIONS:这指定了一个配置列表(DebugRelease)。此命令中跟随此关键字的所有选项仅当当前构建配置在此列表中时才会被应用。
  • OPTIONAL:这禁用了在安装的文件不存在时引发错误。

在组件特定安装中还使用了两个共享选项:COMPONENTEXCLUDE_FROM_ALL。我们将在定义组件部分详细讨论这些内容。

让我们看看第一个安装模式:install(TARGETS)

安装逻辑目标

add_library()add_executable()定义的目标可以很容易地使用install(TARGETS)命令安装。这意味着将构建系统产生的工件复制到适当的目标目录并将它们的文件权限设置为合适。此模式的通用签名如下:

install(TARGETS <target>... [EXPORT <export-name>]
        [<output-artifact-configuration> ...]
        [INCLUDES DESTINATION [<dir> ...]]
        )

在初始模式指定符 – 即TARGETS – 之后,我们必须提供一个我们想要安装的目标列表。在这里,我们可以选择性地将它们分配给EXPORT选项,该选项可用于export(EXPORT)install(EXPORT)以生成目标导出文件。然后,我们必须配置输出工件(按类型分组)的安装。可选地,我们可以提供一系列目录,这些目录将添加到每个目标在其INTERFACE_INCLUDE_DIRECTORIES属性中的目标导出文件中。

[...] 提供了一个配置块列表。单个块的完整语法如下:

<TYPE> [DESTINATION <dir>] [PERMISSIONS permissions...]
       [CONFIGURATIONS [Debug|Release|...]]
       [COMPONENT <component>]
       [NAMELINK_COMPONENT <component>]
       [OPTIONAL] [EXCLUDE_FROM_ALL]
       [NAMELINK_ONLY|NAMELINK_SKIP]

每个输出工件块都必须以开头(这是唯一必需的元素)。CMake 识别它们中的几个:

  • ARCHIVE:静态库(.a)和基于 Windows 系统的 DLL 导入库(.lib)。
  • LIBRARY:共享库(.so),但不包括 DLL。
  • RUNTIME:可执行文件和 DLL。
  • OBJECTS:来自OBJECT库的对象文件
  • FRAMEWORK:设置了FRAMEWORK属性的静态和共享库(这使它们不属于ARCHIVELIBRARY)。这是 macOS 特定的。
  • BUNDLE:标记有MACOSX_BUNDLE的可执行文件(也不是RUNTIME的一部分)。
  • PUBLIC_HEADERPRIVATE_HEADERRESOURCE:在目标属性中指定相同名称的文件(在苹果平台上,它们应该设置在FRAMEWORKBUNDLE目标上)。

CMake 文档声称,如果你只配置了一种工件类型(例如,LIBRARY),只有这种类型将被安装。对于 CMake 3.20.0 版本,这并不正确:所有工件都将以默认选项配置的方式安装。这可以通过为所有不需要的工件类型指定 EXCLUDE_FROM_ALL来解决。

注意

单个install(TARGETS)命令可以有多个工件配置块。但是,请注意,每次调用您可能只能指定每种类型的一个。也就是说,如果您想要为DebugRelease配置指定不同位置的ARCHIVE工件,那么您必须分别进行两次install(TARGETS ... ARCHIVE)调用。

你也可以省略类型名称,为所有工件指定选项:

install(TARGETS executable, static_lib1
  DESTINATION /tmp
)

安装过程将会对所有这些目标生成的文件进行,不论它们的类型是什么。

另外,你并不总是需要为DESTINATION提供安装目录。让我们看看原因。

为不同平台确定正确的目的地

目标路径的公式如下所示:

${CMAKE_INSTALL_PREFIX} + ${DESTINATION}

如果未提供DESTINATION,CMake 将使用每个类型的内置默认值:

虽然默认路径有时很有用,但它们并不适用于每种情况。例如,默认情况下,CMake 会“猜测”库的DESTINATION应该是lib。所有类 Unix 系统上的库的完整路径将被计算为/usr/local/lib,而在 Windows 上则是类似于C:\Program Files (x86)\<项目名称>\lib。这对于支持多架构的 Debian 来说不会是一个很好的选择,当INSTALL_PREFIX/usr时,它需要一个特定架构(例如i386-linux-gnu)的路径。为每个平台确定正确的路径是类 Unix 系统的一个常见问题。为了做到正确,我们需要遵循GNU 编码标准(在进一步阅读部分可以找到这个链接)。

在采用“猜测”之前,CMake 将检查是否为这种工件类型设置了CMAKE_INSTALL_DIR变量,并使用从此处开始的路径。我们需要的是一个算法,能够检测平台并填充安装目录变量以提供适当的路径。CMake 通过提供GNUInstallDirs实用模块简化了此操作,该模块处理大多数平台并相应地设置安装目录变量。在调用任何install()命令之前只需include()它,然后你就可以正常使用了。

需要自定义配置的用户可以通过命令行使用-DCMAKE_INSTALL_BINDIR=/path/in/the/system提供安装目录变量。

然而,安装库的公共头文件可能会有些棘手。让我们来看看原因。

处理公共头文件

install(TARGETS)文档建议我们在库目标的PUBLIC_HEADER属性中(用分号分隔)指定公共头文件:

chapter-11/02-install-targets/src/CMakeLists.txt

add_library(calc STATIC calc.cpp)
target_include_directories(calc INTERFACE include)
set_target_properties(calc PROPERTIES
  PUBLIC_HEADER src/include/calc/calc.h
)

如果我们使用 Unix 的默认“猜测”方式,文件最终会出现在/usr/local/include。这并不一定是最佳实践。理想情况下,我们希望能够将这些公共头文件放在一个能表明它们来源并引入命名空间的目录中;例如,/usr/local/include/calc。这将允许我们在这个系统上的所有项目中使用它们,如下所示:

#include <calc/calc.h>

大多数预处理器将尖括号中的指令识别为扫描标准系统目录的请求。这就是我们之前提到的GNUInstallDirs模块的作用。它为install()命令定义了安装变量,尽管我们也可以显式使用它们。在这种情况下,我们想要在公共头文件的目的地calc前加上CMAKE_INSTALL_INCLUDEDIR

chapter-11/02-install-targets/CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
project(InstallTargets CXX) 
add_subdirectory(src bin)
include(GNUInstallDirs)
install(TARGETS calc
  ARCHIVE
  PUBLIC_HEADER
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/calc
)

在从src包含列表文件,定义了我们的calc目标之后,我们必须配置静态库及其公共头文件的安装。我们已经包含了GNUInstallDirs模块,并明确指定了DESTINATIONPUBLIC_HEADERS。以安装模式运行cmake将按预期工作:

# cmake -S <source-tree> -B <build-tree>
# cmake --build <build-tree>
# cmake --install <build-tree>
-- Install configuration: ""
-- Installing: /usr/local/lib/libcalc.a
-- Installing: /usr/local/include/calc/calc.h

这种方式对于这个基本案例来说很好,但有一个轻微的缺点:以这种方式指定的文件不保留它们的目录结构。它们都将被安装在同一个目的地,即使它们嵌套在不同的基本目录中。

计划在新版本中(CMake 3.23.0)使用FILE_SET关键字更好地管理头文件:

target_sources(<target>
  [<PUBLIC|PRIVATE|INTERFACE>
   [FILE_SET <name> TYPE <type> [BASE_DIR <dir>] FILES]
   <files>...
  ]...
)

有关官方论坛上的讨论,请参阅进一步阅读部分中的链接。在发布该选项之前,我们可以使用此机制与PRIVATE_HEADERRESOURCE工件类型。但我们如何指定更复杂的安装目录结构呢?

低级安装

现代 CMake 正在逐步放弃直接操作文件的概念。理想情况下,我们总是将它们添加到一个逻辑目标中,并使用这个更高层次的抽象来表示所有底层资产:源文件、头文件、资源、配置等等。主要优点是代码的简洁性:通常,我们添加一个文件到目标时不需要更改多于一行代码。

不幸的是,将每个已安装的文件添加到目标上并不总是可能的或方便的。对于这种情况,有三种选择可用:install(FILES)install(PROGRAMS)install(DIRECTORY)

使用 install(FILES|PROGRAMS) 安装文件集

FILESPROGRAMS 模式非常相似。它们可以用来安装公共头文件、文档、shell 脚本、配置文件,以及所有种类的资产,包括图像、音频文件和将在运行时使用的数据集。

以下是命令签名:

install(<FILES|PROGRAMS> files...
        TYPE <type> | DESTINATION <dir>
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])

FILESPROGRAMS 之间的主要区别是新复制文件的默认文件权限设置。install(PROGRAMS) 也会为所有用户设置 EXECUTE 权限,而 install(FILES) 不会(两者都会设置 OWNER_WRITEOWNER_READGROUP_READWORLD_READ)。你可以通过提供可选的 PERMISSIONS 关键字来改变这种行为,然后选择领先的关键字作为安装内容的指示器:FILESPROGRAMS。我们已经讨论了 PERMISSIONSCONFIGURATIONSOPTIONAL 如何工作。COMPONENTEXCLUDE_FROM_ALL定义组件 部分中稍后讨论。

在初始关键字之后,我们需要列出所有想要安装的文件。CMake 支持相对路径、绝对路径以及生成器表达式。只需记住,如果你的文件路径以生成器表达式开始,那么它必须是绝对的。

下一个必需的关键字是 TYPEDESTINATION。我们可以显式提供 DESTINATION 路径,或者要求 CMake 为特定 TYPE 文件查找它。与 install(TARGETS) 不同,TYPE 并不声称选择性地将要安装的文件子集安装到指定位置。然而,计算安装路径遵循相同的模式(+ 符号表示平台特定的路径分隔符):

${CMAKE_INSTALL_PREFIX} + ${DESTINATION}

同样,每个 TYPE 都会有内置猜测:

这里的行为遵循在 为不同平台计算正确的目的地 部分描述的相同原则:如果此 TYPE 文件没有设置安装目录变量,CMake 将退回到默认的“猜测”路径。再次,我们可以使用 GNUInstallDirs 模块以提高可移植性。

表中一些内置猜测的前缀是安装目录变量:

  • $LOCALSTATECMAKE_INSTALL_LOCALSTATEDIR 或默认为 var
  • $DATAROOTCMAKE_INSTALL_DATAROOTDIR 或默认为 share

install(TARGETS)类似,如果包含了GNUInstallDirs模块,它将提供特定于平台的安装目录变量。让我们来看一个例子:

chapter-11/03-install-files/CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
project(InstallFiles CXX)
include(GNUInstallDirs)
install(FILES
  src/include/calc/calc.h
  src/include/calc/nested/calc_extended.h
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/calc
)

在这种情况下,CMake 将在系统级include 目录的项目特定子目录中安装两个头文件库——即calc.hnested/calc_extended.h

注意

GNUInstallDirs源文件中我们知道CMAKE_INSTALL_INCLUDEDIR对所有支持的平台都包含相同的路径。然而,为了可读性和与更动态的变量保持一致,仍然建议使用它。例如,CMAKE_INSTALL_LIBDIR将根据架构和发行版而变化——liblib64lib/

CMake 3.20 还向install(FILES|PROGRAMS)命令添加了相当有用的RENAME关键字,后跟新文件名(仅当files...列表包含单个文件时才有效)。

本节中的示例展示了安装文件到适当目录是多么简单。不过有一个问题——看看安装输出:

# cmake -S <source-tree> -B <build-tree>
# cmake --build <build-tree>
# cmake --install <build-tree>
-- Install configuration: ""
-- Installing: /usr/local/include/calc/calc.h
-- Installing: /usr/local/include/calc/calc_extended.h

两个文件都被安装在同一个目录中,无论嵌套与否。有时,这可能不是我们想要的。在下一节中,我们将学习如何处理这种情况。

处理整个目录

如果你不想将单个文件添加到安装命令中,你可以选择更广泛的方法,而是处理整个目录。install(DIRECTORY)模式就是为了这个目的而创建的。它将列表中的目录原样复制到所选的目标位置。让我们看看它看起来像什么:

install(DIRECTORY dirs...
        TYPE <type> | DESTINATION <dir>
        [FILE_PERMISSIONS permissions...]
        [DIRECTORY_PERMISSIONS permissions...]
        [USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>] [EXCLUDE_FROM_ALL]
        [FILES_MATCHING]
        [[PATTERN <pattern> | REGEX <regex>] [EXCLUDE] 
        [PERMISSIONS permissions...]] [...])

正如你所看到的,许多选项是从install(FILES|PROGRAMS)重复的。它们的工作方式是一样的。有一个值得注意的细节:如果在与DIRECTORY关键字提供的路径不以/结尾,路径的最后目录将被添加到目的地,如下所示:

install(DIRECTORY a DESTINATION /x)

这将创建一个名为/x/a的目录并将a的内容复制到其中。现在,看看以下代码:

install(DIRECTORY a/ DESTINATION /x)

这将直接将a的内容复制到/x

install(DIRECTORY)还引入了其他对文件不可用的机制:

  • 静默输出
  • 扩展权限控制
  • 文件/目录过滤

让我们先从静默输出选项MESSAGE_NEVER开始。它禁用了安装过程中的输出诊断。当我们有很多要安装的目录中的文件,打印它们所有人会太吵时,这个功能非常有用。

接下来是权限。这个install()模式支持设置权限的三个选项:

  • USE_SOURCE_PERMISSIONS按预期工作——它设置了遵循原始文件权限的安装文件权限。只有当FILE_PERMISSIONS未设置时,这才会起作用。
  • FILE_PERMISSIONS也非常容易解释。它允许我们指定想要设置在安装的文件和目录上的权限。默认的权限有OWNER_WRITEOWNER_READGROUP_READWORLD_READ
  • DIRECTORY_PERMISSIONS与前面选项的工作方式类似,但它将为所有用户设置额外的EXECUTE权限(这是因为 Unix-like 系统将目录上的EXECUTE理解为列出其内容的权限)。

请注意,CMake 将在不支持它们的平台上忽略权限选项。通过在每一个过滤表达式之后添加PERMISSIONS关键字,可以实现更多的权限控制:任何被它匹配的文件或目录都将接收到在此关键字之后指定的权限。

让我们来谈谈过滤器或“通配符”表达式。你可以设置多个过滤器,控制从源目录安装哪些文件/目录。它们有以下语法:

PATTERN <p> | REGEX <r> [EXCLUDE] [PERMISSIONS
  <permissions>]

有两种匹配方法可以选择:

  • 使用PATTERN,这是更简单的选项,我们可以提供一个带有?占位符(匹配任何字符)和通配符,*(匹配任何字符串)的模式。只有以结尾的路径才会被匹配。
  • 另一方面,REGEX选项更高级——它支持正则表达式。它还允许我们匹配路径的任何部分(我们仍然可以使用^$锚点来表示路径的开始和结束)。

可选地,我们可以在第一个过滤器之前设置FILES_MATCHING关键字,这将指定任何过滤器都将应用于文件,而不是目录。

记住两个注意事项:

  • FILES_MATCHING需要一个包含性过滤器,也就是说,你可以排除一些文件,但除非你也添加一个表达式来包含其中的一些,否则没有文件会被复制。然而,无论过滤与否,所有目录都会被创建。
  • 所有子目录默认都是被过滤进去的;你只能进行排除。

对于每种过滤方法,我们可以选择EXCLUDE匹配的路径(这只有在没有使用FILES_MATCHING时才有效)。

我们可以通过在任何一个过滤器之后添加PERMISSIONS关键字和一个所需权限的列表,为所有匹配的路径设置特定的权限。让我们试试看。在这个例子中,我们将以三种不同的方式安装三个目录。我们将有一些在运行时使用的静态数据文件:

data
- data.csv

我们还需要一些位于src目录中的公共头文件,以及其他不相关的文件:

src
- include
  - calc
    - calc.h
    - ignored
      - empty.file
    - nested
      - calc_extended.h

最后,我们需要两个嵌套级别的配置文件。为了使事情更有趣,我们将使得/etc/calc/的内容只能被文件所有者访问:

etc
- calc
  - nested.conf
- sample.conf

要安装具有静态数据文件的目录,我们将使用install(DIRECTORY)命令的最基本形式开始我们的项目:

chapter-11/04-install-directories/CMakeLists.txt(片段)

cmake_minimum_required(VERSION 3.20.0)
project(InstallDirectories CXX)
install(DIRECTORY data/ DESTINATION share/calc)
...

这个命令将简单地取我们data目录下的所有内容并将其放入${CMAKE_INSTALL_PREFIX}share/calc。请注意,我们的源路径以一个/符号结束,以表示我们不想复制data目录本身,只想它的内容。

第二个案例正好相反:我们不添加尾随的/,因为目录应该被包含。这是因为我们依赖于GNUInstallDirs提供的特定于系统的INCLUDE文件类型路径(注意INCLUDEEXCLUDE关键词代表无关的概念):

第十一章/04-install-directories/CMakeLists.txt(片段)

...
include(GNUInstallDirs)
install(DIRECTORY src/include/calc TYPE INCLUDE
  PATTERN "ignored" EXCLUDE
  PATTERN "calc_extended.h" EXCLUDE
)
...

此外,我们已经将这两个路径从这个操作中排除:整个ignored目录和所有以calc_extended.h结尾的文件(记得PATTERN是如何工作的)。

第三个案例安装了一些默认的配置文件并设置了它们的权限:

第十一章/04-install-directories/CMakeLists.txt(片段)

...
install(DIRECTORY etc/ TYPE SYSCONF
  DIRECTORY_PERMISSIONS 
    OWNER_READ OWNER_WRITE OWNER_EXECUTE
  PATTERN "nested.conf"
    PERMISSIONS OWNER_READ OWNER_WRITE
)

再次说明,我们不关心从源路径中添加etcSYSCONF类型的路径(这已经由包含GNUInstallDirs提供),因为我们会把文件放在/etc/etc中。此外,我们必须指定两个权限规则:

  • 子目录只能由所有者编辑和列出。
  • nested.conf结尾的文件只能由所有者编辑。

安装目录处理了很多不同的用例,但对于真正高级的安装场景(如安装后配置),我们可能需要使用外部工具。我们应该如何做到这一点?

面向 C++ 的现代 CMake 教程(四)(4)https://developer.aliyun.com/article/1526942

相关文章
|
6天前
|
C++
Clion CMake C/C++程序输出乱码
Clion CMake C/C++程序输出乱码
9 0
|
7天前
|
存储 算法 编译器
C++ 函数式编程教程
C++ 函数式编程学习
|
7天前
|
存储 编译器 开发工具
C++语言教程分享
C++语言教程分享
|
7天前
|
存储 编译器 C++
|
28天前
|
C++ 存储 索引
面向 C++ 的现代 CMake 教程(一)(5)
面向 C++ 的现代 CMake 教程(一)
46 0
|
28天前
|
缓存 存储 C++
面向 C++ 的现代 CMake 教程(一)(4)
面向 C++ 的现代 CMake 教程(一)
45 0
|
28天前
|
C++ 缓存 存储
面向 C++ 的现代 CMake 教程(一)(3)
面向 C++ 的现代 CMake 教程(一)
43 0
|
28天前
|
缓存 C++ Windows
面向 C++ 的现代 CMake 教程(一)(2)
面向 C++ 的现代 CMake 教程(一)
57 0
|
28天前
|
C++ 容器 Docker
面向 C++ 的现代 CMake 教程(一)(1)
面向 C++ 的现代 CMake 教程(一)
67 0
|
Java 编译器 Linux
【CMake】CMake 引入 ( Android Studio 创建 Native C++ 工程 | C/C++ 源码编译过程 | Makefile 工具 | CMake 引入 )(二)
【CMake】CMake 引入 ( Android Studio 创建 Native C++ 工程 | C/C++ 源码编译过程 | Makefile 工具 | CMake 引入 )(二)
280 0
【CMake】CMake 引入 ( Android Studio 创建 Native C++ 工程 | C/C++ 源码编译过程 | Makefile 工具 | CMake 引入 )(二)