第 9 步:安装命令与概念¶
项目不仅需要构建和测试代码,还需要将其提供给使用者。构建目录中的文件布局不适合其他项目直接使用:二进制文件位置不符合预期、头文件位于远离源文件的树状结构中,且没有明确的方法来发现提供了哪些目标或如何使用它们。
这种将构件(artifacts)从源目录和构建目录移动到适合使用的最终布局的过程,被称为安装(installation)。作为项目描述的一部分,CMake 支持一套完整的安装工作流,既控制构件在安装目录中的布局,也为想要使用这些安装目录提供库的其他 CMake 项目重建目标。
背景¶
所有 CMake 安装过程都通过单个命令 install() 完成,该命令分为许多子命令,负责安装过程的各个方面。对于基于目标的 CMake 工作流,通常只需使用 install(TARGETS) 安装目标本身,而无需手动使用 install(FILES) 或 install(DIRECTORY) 来移动文件。
注意
这就是为什么我们需要将 FILES 添加到打算安装的头文件集(header sets)中。当关联的目标被安装时,CMake 需要能够定位这些文件。
CMake 将基于目标的安装分为不同的构件类型。可用的构件类型(在 CMake 3.23 中)包括:
ARCHIVE静态库(
.a/.lib)、DLL 导入库(.lib)以及少数其他“类似存档”的对象。LIBRARY共享库(
.so)、模块和其他可动态加载的对象。不包括 Windows 的 DLL 文件(.dll)或 MacOS 框架。RUNTIME除 MacOS bundle 外的所有类型可执行文件;以及 Windows 的 DLL(
.dll)。OBJECT来自
OBJECT库的对象。FRAMEWORK静态和共享的 MacOS 框架。
BUNDLEMacOS bundle 可执行文件。
PUBLIC_HEADER/PRIVATE_HEADER/RESOURCE由
PUBLIC_HEADER、PRIVATE_HEADER和RESOURCE目标属性描述的文件,通常与 MacOS 框架一起使用。FILE_SET <集合名称>与目标关联的文件集。这是头文件通常的安装方式。
大多数重要的构件类型都有已知的默认目标位置,除非另有说明,否则 CMake 将默认使用这些位置。例如,RUNTIME 将安装到 CMAKE_INSTALL_BINDIR 所指定的路径(如果该变量可用),否则默认指向 bin。
构件类型默认目标路径的完整列表如下表所示。
目标类型 |
变量 |
内置默认值 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在大多数情况下,项目应该保留这些默认设置,除非它们需要安装到默认位置的特定子目录中。
CMake 默认不定义 CMAKE_INSTALL_<dir> 变量。如果项目希望指定安装到这些位置的子目录,则必须包含 GNUInstallDirs 模块,该模块将为所有尚未定义的 CMAKE_INSTALL_<dir> 变量提供值。
练习 1 - 安装构件¶
对于现代的、基于目标的 CMake 项目,构件的安装非常简单,只需调用一次 install(targets) 即可。
install(
TARGETS MyApp MyLib
FILE_SET HEADERS
FILE_SET anotherHeaderFileSet
)
大多数构件类型默认会被安装,不需要在 install() 命令中列出。但是,必须指定 FILE_SET 的名称以告知 CMake 您想要安装它们。在上面的示例中,我们安装了两个文件集,一个名为 HEADERS,另一个名为 anotherHeaderFileSet。
当指定名称时,构件类型可以被赋予各种选项,例如目的地(destination)。
include(GNUInstallDirs)
install(
TARGETS MyApp MyLib
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}/Subfolder
FILE_SET HEADERS
)
这将把 MyApp 目标安装到 bin/Subfolder(假设打包人员没有更改 CMAKE_INSTALL_BINDIR)。
重要提示:如果 OBJECT 构件类型从未被指定目的地,它将表现得像一个 INTERFACE 库,只安装其头文件。
目标¶
安装教程项目中描述的库和可执行文件(测试除外)的构件。
有用资源¶
要编辑的文件¶
CMakeLists.txt
开始¶
Help/guide/tutorial/Step9 目录包含了 Step8 的完整推荐解决方案。完成 TODO 1 和 TODO 2。
构建并运行¶
无需特殊配置,按常规进行配置和构建即可。
cmake --preset tutorial
cmake --build build
我们可以使用 cmake --install 验证安装是否正确。
cmake --install build --prefix install
install 文件夹应当正确填充了我们的构件。
解决方案¶
首先,我们为有条件构建(因此是有条件安装)的 Tutorial 可执行文件添加一个 install(TARGETS)。
TODO 1 点击显示/隐藏答案
if(TUTORIAL_BUILD_UTILITIES)
add_subdirectory(Tutorial)
install(
TARGETS Tutorial
)
endif()
然后我们可以安装其余的目标。
TODO 2 点击显示/隐藏答案
install(
TARGETS MathFunctions OpAdd OpMul OpSub MathLogger SqrtTable
FILE_SET HEADERS
)
注意
我们可以在定义目标的每个子文件夹中本地添加 install(TARGETS) 命令。这在大型项目中很常见,因为在这种情况下跟踪所有可安装目标比较困难。
安装 SqrtTable 和 MathLogger 似乎是不必要的,在这一阶段确实如此。由于 CMake 对目标关系的建模方式,在下一次练习中重建目标模型时,我们将需要这些目标可用。
练习 2 - 导出目标¶
这些原始的已安装文件集合是一个好的开始,但我们丢失了 CMake 目标模型。它们实际上并不比我们在 Step 4 中讨论的预编译供应商库更好。我们需要一种方法让其他项目能够根据我们在安装目录中提供的内容重建我们的目标。
CMake 提供解决此问题的机制是一个名为“目标导出文件”的 CMake 语言文件。它由 install(EXPORT) 命令创建。
install(
TARGETS MyApp MyLib
EXPORT MyProjectTargets
)
include(GNUInstallDirs)
install(
EXPORT MyProjectTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
NAMESPACE MyProject::
)
上述示例包含多个部分。首先,install(TARGETS) 命令接受一个导出名称,本质上是一个用于添加已安装目标的列表。
稍后,install(EXPORT) 命令会消耗此目标列表来生成目标导出文件。这将是一个名为 <ExportName>.cmake 的文件,位于提供的 DESTINATION 中。此示例中提供的 DESTINATION 是惯用位置,但任何由 find_package() 命令搜索的位置都是有效的。
最后,由目标导出文件创建的目标将以 NAMESPACE 字符串作为前缀,即它们将采用 <NAMESPACE><TargetName> 的形式。惯例是将项目名称后跟两个冒号作为命名空间。
出于在后续步骤中会变得更加明显的原因,我们通常不会直接使用该文件。相反,我们使用名为 <ProjectName>Config.cmake 的文件,通过 include() 来调用它。
include(${CMAKE_CURRENT_LIST_DIR}/MyProjectTargets.cmake)
注意
CMAKE_CURRENT_LIST_DIR 变量命名当前正在运行的 CMake 语言文件所在的目录,无论该文件是如何被包含或启动的。
然后,该文件通过 install(FILES) 与目标导出文件一起安装。
install(
FILES
cmake/MyProjectConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
)
注意
该文件的名称及其位置由 find_package() 命令的发现语义决定,我们将在下一步中进行详细讨论。
目标¶
导出 Tutorial 项目目标,以便其他项目可以使用它们。
有用资源¶
要编辑的文件¶
CMakeLists.txtcmake/TutorialConfig.cmake
开始¶
继续编辑 Help/guide/tutorial/Step9 目录中的文件。完成 TODO 3 到 TODO 8。
构建并运行¶
构建命令足以重新配置项目。
cmake --build build
我们可以使用 cmake --install 验证安装是否正确。
注意
与 CTest 一样,当使用多配置生成器(例如 Visual Studio)时,需要使用 cmake --install --config <config> <remaining flags> 指定配置,其中 <config> 是诸如 Debug 或 Release 的值。在使用多配置生成器时情况总是如此,在未来的命令中将不再专门说明。
cmake --install build --prefix install
注意
CMake 不会更新未更改的文件,只会从构建和源目录安装新的或已更新的文件。
install 文件夹应该已经正确填充了我们的构件和导出文件。我们将在下一步演示如何使用这些文件。
解决方案¶
首先,我们将 Tutorial 目标添加到 TutorialTargets 导出中。
TODO 3 点击以显示/隐藏答案
install(
TARGETS Tutorial
EXPORT TutorialTargets
)
很快我们将需要访问 CMAKE_INSTALL_<dir> 变量,因此接下来我们包含 GNUInstallDirs 模块。
TODO 4 点击以显示/隐藏答案
include(GNUInstallDirs)
现在我们将其余目标添加到 TutorialTargets 导出中。
TODO 5 点击以显示/隐藏答案
install(
TARGETS MathFunctions OpAdd OpMul OpSub MathLogger SqrtTable
EXPORT TutorialTargets
FILE_SET HEADERS
)
接下来,我们安装导出本身,以生成我们的目标导出文件。
TODO 6 点击以显示/隐藏答案
install(
EXPORT TutorialTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
NAMESPACE Tutorial::
)
然后我们安装我们的“配置”文件,我们将使用它来包含我们的目标导出文件。
TODO 7 点击以显示/隐藏答案
install(
FILES
cmake/TutorialConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
)
最后,我们可以在配置文件中添加必要的 include() 命令。
TODO 8 点击以显示/隐藏答案
include(${CMAKE_CURRENT_LIST_DIR}/TutorialTargets.cmake)
练习 3 - 导出版本文件¶
当从目标导出文件导入 CMake 目标时,无法“中止”或“撤消”该操作。如果发现包的版本与我们请求的版本错误或不兼容,我们将不得不承受在了解版本信息时所产生的任何副作用。
CMake 为此问题提供的答案是一个轻量级的版本文件,它仅描述版本兼容性信息,可以在 CMake 提交完全导入该文件之前进行检查。
CMake 提供了用于生成这些版本文件的辅助模块和脚本,即 CMakePackageConfigHelpers 模块。
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake
COMPATIBILITY ExactVersion
)
可用的版本检查模式包括:
AnyNewerVersionSameMajorVersionSameMinorVersionExactVersion
此外,包可以将自己标记为 ARCH_INDEPENDENT,适用于不发布与特定机器架构绑定的二进制文件的包。
默认情况下,write_basic_package_version_file() 使用的 VERSION 是传递给 project() 命令的 VERSION 数。
目标¶
为 Tutorial 项目导出版本文件。
有用资源¶
要编辑的文件¶
CMakeLists.txt
入门¶
继续编辑 Help/guide/tutorial/Step9 目录中的文件。完成 TODO 9 到 TODO 12。
构建和运行¶
像之前一样重新构建并安装。
cmake --build build
cmake --install build --prefix install
install 文件夹应当正确填充了我们新生成和安装的版本文件。
解决方案¶
首先,我们为 project() 命令添加一个 VERSION 参数。
TODO 9 点击以显示/隐藏答案
project(Tutorial
VERSION 1.0.0
)
接下来,我们包含 CMakePackageConfigHelpers 模块,并使用它生成配置版本文件。
TODO 10-11 点击以显示/隐藏答案
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
COMPATIBILITY ExactVersion
)
最后,我们将配置版本文件添加到待安装文件列表中。
TODO 12 点击以显示/隐藏答案
install(
FILES
cmake/TutorialConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
)