第 9 步:安装命令与概念¶
项目不仅仅需要构建和测试代码,还需要将代码提供给使用者。构建树中的文件布局不适合其他项目使用,可执行文件位于意想不到的位置,头文件散落在源代码树的远处,并且没有明确的方法来发现项目提供的目标或如何使用它们。
这种将构建树中的构件移动到适合使用者使用的最终布局的操作称为安装。CMake 支持一个完整的安装工作流程,作为项目描述的一部分,它控制着安装树中构件的布局,并为希望使用安装树提供的库的其他 CMake 项目重建目标。
背景¶
所有 CMake 安装都通过一个单一命令 install() 来完成,该命令又细分为负责安装过程各个方面的多个子命令。对于基于目标的 CMake 工作流程,通常足以依赖安装目标本身,使用 install(TARGETS),而不是 resorting to manually moving files with install(FILES) or install(DIRECTORY)。
注意
这就是为什么我们需要向将要安装的头文件集合添加 FILES。当安装与文件关联的目标时,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 <set-name>与目标关联的文件集。这是安装头文件的常用方式。
大多数重要的构件类型都有已知的目标位置,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。
当构件类型被命名时,可以为其指定各种选项,例如目标位置。
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
安装文件夹应该已正确填充了我们的构件。
解决方案¶
首先,我们为条件构建(因此条件安装)的 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 不会更新未更改的文件,只会安装构建和源树中的新文件或更新的文件。
安装文件夹应该已经正确填充了我们新生成的导出文件。我们将在下一步演示如何使用这些文件。
解决方案¶
首先,我们将 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::
)
然后我们安装我们的“config”文件,我们将使用它来包含我们的目标导出文件。
TODO 7 点击显示/隐藏答案
install(
FILES
cmake/TutorialConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
)
最后,我们可以将必要的 include() 命令添加到 config 文件中。
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
安装文件夹应该已经正确填充了我们新生成的安装版本文件。
解决方案¶
首先,我们向 project() 命令添加一个 VERSION 参数。
TODO 9 点击显示/隐藏答案
project(Tutorial
VERSION 1.0.0
)
接下来,我们包含 CMakePackageConfigHelpers 模块,并使用它来生成 config 版本文件。
TODO 10-11 点击显示/隐藏答案
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
COMPATIBILITY ExactVersion
)
最后,我们将 config 版本文件添加到要安装的文件列表中。
TODO 12 点击显示/隐藏答案
install(
FILES
cmake/TutorialConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/TutorialConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Tutorial
)