安装文件

软件通常安装到一个独立于源代码和构建树的目录中。通过这种方式,软件可以以一种干净的形式进行分发,使用户远离构建过程的细节。CMake 提供了 install 命令来指定项目的安装方式。该命令由 CMakeLists 文件中的项目调用,并告知 CMake 如何生成安装脚本。这些脚本在安装时执行,以执行文件的实际安装。对于 Makefile 生成器 (UNIX、NMake、MinGW 等),用户只需运行 make install (或 nmake install),而 make 工具将调用 CMake 的安装模块。在基于 GUI 的系统 (Visual Studio、Xcode 等) 中,用户只需构建名为 INSTALL 的目标即可。

install 命令的每次调用都会定义一些安装规则。在单个 CMakeLists 文件(源代码目录)中,这些规则将按照相应命令调用的顺序来评估。CMake 3.14 中已更改多个目录中的顺序。

install 命令有几个签名,专为常见的安装用例而设计。命令的特定调用将签名指定为第一个参数。这些签名是 TARGETSFILESPROGRAMSDIRECTORYSCRIPTCODEEXPORT

install(TARGETS …)

安装项目内部构建的目标对应的二进制文件。

install(FILES …)

通用的文件安装,通常用于软件所需的标题文件、文档和数据文件。

install(PROGRAMS …)

安装不是由项目构建的可执行文件,例如 shell 脚本。此参数与 install(FILES) 相同,不同之处在于已安装文件的默认权限包含可执行位。`

install(DIRECTORY …)

此参数安装整个目录树。可用于安装具有资源的目录,如图标和图像。

install(SCRIPT …)

指定在安装过程中要执行的用户提供的 CMake 脚本文件。通常用于为其他规则定义安装前或安装后操作。

install(CODE …)

指定要在安装过程中执行的用户提供的 CMake 代码。这类似于 install (SCRIPT),但代码在调用中作为字符串内联提供。

install(EXPORT …)

生成并安装一个 CMake 文件,其中包含从安装树中导入目标到另一个项目的代码。

TARGETSFILESPROGRAMSDIRECTORY 签名都用于为文件创建安装规则。需要安装的目标、文件或目录紧跟在签名名称参数之后。可以利用关键字参数及其相应值指定其他详细信息。以下列出了这些签名中大多数提供的关键字参数。

DESTINATION

该参数指定安装规则将放置文件的位置,后面必须是一个指示位置的目录路径。如果目录指定为绝对路径,则安装时将被评估为一个绝对路径。如果目录指定为相对路径,则安装时将相对于安装前缀进行评估。前缀可以通过缓存变量 CMAKE_INSTALL_PREFIX 由用户设置。特定于平台的默认值由 CMake 提供:UNIX 下为 /usr/local,而 Windows 下为“<SystemDrive>/Program Files/<ProjectName>”,其中 SystemDrive 类似于 C:,而 ProjectName 是赋予最顶层 project 命令的名称。

PERMISSIONS

该参数指定要对已安装的文件设置的文件权限。只有当需要覆盖特定 install 命令签名选定的默认权限时,才需要此选项。有效的权限包括 OWNER_READOWNER_WRITEOWNER_EXECUTEGROUP_READGROUP_WRITEGROUP_EXECUTEWORLD_READWORLD_WRITEWORLD_EXECUTESETUIDSETGID。某些平台不支持所有这些权限;在那些平台上,这些权限名称将被忽略。

CONFIGURATIONS

该参数指定安装规则所适用的构建配置列表(例如 Debug、Release 等)。对于 Makefile 生成器,构建配置由 CMAKE_BUILD_TYPE 缓存变量指定。对于 Visual Studio 和 Xcode 生成器,在构建 install 目标时选择配置。只有当当前安装配置与向该参数提供的列表中的条目匹配时才会评估安装规则。配置名称比较不区分大小写。

COMPONENT

此参数指定应用安装规则的安装组件。某些项目将其安装划分成多个组件,以便单独打包。例如,项目可能定义一个 Runtime 组件,其中包含运行工具所需的文件;一个 Development 组件,其中包含生成工具扩展所需的文件;一个 Documentation 组件,其中包含手册页和其他帮助文件。然后,该项目可针对每个组件单独进行打包,且每次仅安装一个组件。默认情况下,将安装所有组件。特定于组件的安装是一种高级功能,旨在供软件包维护者使用。它要求使用一个参数手动调用安装脚本,该参数定义 COMPONENT 变量,以指定所需的组件。请注意,组件名称不是由 CMake 定义的。每个项目都可以定义自己的组件集合。

可选

此参数指定,如果要安装的输入文件不存在,则它不会导致错误。如果输入文件存在,则将按照请求安装它。如果它不存在,则将静默地不安装它。

安装目标

项目通常会安装其生成过程中创建的一些库和可执行文件。install 命令提供 TARGETS 签名以实现此目的。

紧跟在 TARGETS 关键字后面的是使用 add_executableadd_library 创建的目标列表,这些目标将被安装。与每个目标相应的一个或多个文件都将被安装。

使用此签名安装的文件可以分为以下几类:ARCHIVELIBRARYRUNTIME。这些类别旨在按典型的安装目标对目标文件进行分组。相应的关键字参数是可选的,但如果存在,则指定后面跟在其后的其他参数仅适用于该类型的目标文件。对目标文件分类如下

可执行文件 - RUNTIME

add_executable 创建(在 Windows 上为 .exe,在 UNIX 上没有扩展名)

可加载模块 - LIBRARY

add_library 创建(在 Windows 上为 .dll,在 UNIX 上为 .so),并带有 MODULE 选项

共享库 - LIBRARY

使用 add_library 命令在类 UNIX 平台上使用 SHARED 选项创建(.so 在大多数 UNIX 上,.dylib 在 Mac 上)

动态链接库 - RUNTIME

使用 add_library 命令在 Windows 平台上使用 SHARED 选项创建(.dll)

导入库 - ARCHIVE

可链接文件,由导出符号的动态链接库创建(.lib 在大多数 Windows 上,.dll.a 在 Cygwin 和 MinGW 上)

静态库 - ARCHIVE

使用 add_library 命令使用 STATIC 选项创建(.lib 在 Windows 上,.a 在 UNIX、Cygwin 和 MinGW 上)

考虑一个定义可执行文件 myExecutable 的项目,它链接到共享库 mySharedLib。它还提供了一个静态库 myStaticLib 和一个名为 myPlugin 的可执行文件插件模块,该模块也链接到共享库。可以使用以下命令单独安装可执行文件、静态库和插件文件

install(TARGETS myExecutable DESTINATION bin)
install(TARGETS myStaticLib DESTINATION lib/myproject)
install(TARGETS myPlugin DESTINATION lib)

只有当所链接的共享库也安装时,可执行文件才能在已安装的位置运行。为了支持所有平台,库的安装需要更小心一些。必须将库安装在每个平台的动态链接程序搜索的位置。在类 UNIX 平台上,库通常被安装到 lib 中,而在 Windows 上,应该将它放在 bin 中可执行文件的旁边。另一个挑战是,Windows 上与共享库关联的导入库应该像静态库一样处理,并安装到 lib/myproject 中。换句话说,我们有三个不同种类的文件,它们使用一个单一的 target 名称创建,必须安装到三个不同的目标!幸运的是,可以使用 category 关键字参数解决此问题。可以使用以下命令安装共享库

install(TARGETS mySharedLib
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib/myproject)

这告诉 CMake,RUNTIME 文件(.dll)应安装到 binLIBRARY 文件(.so)应安装到 libARCHIVE(.lib)文件应安装到 lib/myproject。在 UNIX 上,将安装 LIBRARY 文件;在 Windows 上,将安装 RUNTIMEARCHIVE 文件。

如果要将上述示例项目打包到单独的运行时和开发组件中,则我们必须为每个已安装的目标文件分配适当的组件。为了运行应用程序,需要可执行文件、共享库和插件,因此它们属于 Runtime 组件。同时,导入库(对应于 Windows 上的共享库)和静态库仅需要开发应用程序扩展,因此属于 Development 组件。

可以通过将 COMPONENT 参数添加到上述每个命令来指定组件分配。您还可以将所有安装规则合并到一个命令调用中,这相当于上述所有命令都添加了组件。使用它们各自类别的规则安装每个目标生成的文件。

install(TARGETS myExecutable mySharedLib myStaticLib myPlugin
        RUNTIME DESTINATION bin           COMPONENT Runtime
        LIBRARY DESTINATION lib           COMPONENT Runtime
        ARCHIVE DESTINATION lib/myproject COMPONENT Development)

可以指定 NAMELINK_ONLYNAMELINK_SKIP 作为 LIBRARY 选项。在某些平台上,版本化的共享库具有如下符号链接:

lib<name>.so -> lib<name>.so.1

其中 lib<name>.so.1 是库的 soname,lib<name>.so 是在给定 -l<name> 时帮助链接器找到库的“名称链接”。当安装库目标时,NAMELINK_ONLY 选项仅导致安装名称链接。当安装库目标时,NAMELINK_SKIP 选项导致安装除名称链接以外的库文件。当未给出任何选项时,安装这两部分。在版本化的共享库没有名称链接或库未经过版本化的平台上,NAMELINK_SKIP 选项安装库,NAMELINK_ONLY 选项不安装任何内容。有关创建版本化共享库的详细信息,请参阅 VERSIONSOVERSION 目标属性。

安装文件

项目可能会安装除了使用 add_executableadd_library 之外创建的文件,例如头文件或文档。使用 FILES 标志指定通用的文件安装。

紧跟在 FILES 关键字后面的是要安装的文件列表。相对路径是相对于当前源目录评估的。文件将被安装到给定的 DESTINATION 目录。例如,命令

install(FILES my-api.h ${CMAKE_CURRENT_BINARY_DIR}/my-config.h
        DESTINATION include)

从源代码目录中安装文件 my-api.h,从构建目录中安装文件 my-config.h 到安装前缀下的 include 目录。默认情况下,被安装的文件会赋予权限 OWNER_WRITEOWNER_READGROUP_READWORLD_READ,但这可以通过指定 PERMISSIONS 选项来覆盖。考虑想要在 UNIX 系统上安装一个只有其所有者(例如 root)可读的全局配置文件的情况。我们使用命令执行此操作

install(FILES my-rc DESTINATION /etc
        PERMISSIONS OWNER_WRITE OWNER_READ)

它以所有者读/写权限将文件 my-rc 安装到绝对路径 /etc

RENAME 参数为已安装文件指定一个可能与原始文件不同的名称。仅当命令安装单个文件时才允许重命名。例如,命令

install(FILES version.h DESTINATION include RENAME my-version.h)

将把文件 version.h 从源目录安装到安装前缀下的 include/my-version.h

安装程序

项目还可以安装帮助程序,例如实际上未作为目标编译的外壳脚本或 Python 脚本。可以使用 FILES 标志和 PERMISSIONS 选项添加执行权限,使用这两个选项来安装这些程序。但是,很多情况下需要一种更简单的界面。CMake 为此提供了 PROGRAMS 标志。

紧跟在 PROGRAMS 关键字后面的是要安装的脚本列表。此命令与 FILES 标志相同,除了默认权限之外还包括 OWNER_EXECUTEGROUP_EXECUTEWORLD_EXECUTE。例如,我们可以使用以下命令安装一个 Python 实用程序:

install(PROGRAMS my-util.py DESTINATION bin)

该命令将 my-util.py 安装到安装前缀的 bin 目录,并赋予其所有者、组和全世界读/执行权限,以及所有者写入权限。

安装目录

项目还可以提供一个包含大量资源文件的目录,例如图标或 html 文档。可以使用 DIRECTORY 签名安装整个目录。

DIRECTORY 关键字紧跟要安装的目录列表。相对路径根据当前源目录进行评估。每个指定的目录都安装到目标目录。在复制目录时,每个输入目录名称的最后一个组件都会附加到目标目录。例如,此命令

install(DIRECTORY data/icons DESTINATION share/myproject)

data/icons 目录从源目录安装到安装前缀下的 share/myproject/icons 中。尾部斜杠将使最后一个组件为空,并将输入目录的内容安装到目标目录。此命令

install(DIRECTORY doc/html/ DESTINATION doc/myproject)

doc/html 的内容从源目录安装到安装前缀下的 doc/myproject 中。如果没有给出输入目录名称,如

install(DIRECTORY DESTINATION share/myproject/user)

则将创建目标目录,但不会向其中安装任何内容。

通过 DIRECTORY 签名安装的文件与 FILES 签名具有相同的默认权限。通过 DIRECTORY 签名安装的目录与 PROGRAMS 签名具有相同的默认权限。FILE_PERMISSIONSDIRECTORY_PERMISSIONS 选项可用于覆盖这些默认值。考虑一个案例,其中包含大量示例 shell 脚本的目录要安装到所有者和组都可写的目录中。我们可以使用此命令

install(DIRECTORY data/scripts DESTINATION share/myproject
        FILE_PERMISSIONS
          OWNER_READ OWNER_EXECUTE OWNER_WRITE
          GROUP_READ GROUP_EXECUTE
          WORLD_READ WORLD_EXECUTE
        DIRECTORY_PERMISSIONS
          OWNER_READ OWNER_EXECUTE OWNER_WRITE
          GROUP_READ GROUP_EXECUTE GROUP_WRITE
          WORLD_READ WORLD_EXECUTE
        )

将目录 data/scripts 安装到 share/myproject/scripts 中,并设置所需的权限。在某些情况下,项目创建的已完全准备好的输入目录可能已经设置了所需的权限。USE_SOURCE_PERMISSIONS 选项指示 CMake 在安装期间使用输入目录中的文件和目录权限。如果在前一个示例中,输入目录已经使用正确的权限进行了准备,则可以使用以下命令代替

install(DIRECTORY data/scripts DESTINATION share/myproject
        USE_SOURCE_PERMISSIONS)

如果要安装的输入目录在源管理下,输入中可能存在你不想安装的额外子目录。还有可能存在不应该安装或要以不同权限安装的特定文件,而大多数文件将获得默认值。此时可以使用 PATTERNREGEX 选项。 PATTERN 选项首先跟随一个通配模式,然后跟随一个 EXCLUDEPERMISSIONS 选项。 REGEX 选项首先跟随一个正则表达式,然后跟随 EXCLUDEPERMISSIONSEXCLUDE 选项跳过匹配前一个模式或表达式的那些文件或目录的安装,而 PERMISSIONS 选项为它们分配特定的权限。

每个输入文件和目录将针对包含正斜杠的完整路径进行模式或正则表达式测试。一个模式只会匹配在完整路径末尾出现的完整文件或目录名称,而一个正则表达式可以匹配任何部分。例如,模式 foo* 将匹配 .../foo.txt 但不匹配 .../myfoo.txt.../foo/bar.txt; 而正则表达式 foo 将匹配它们全部。

回到上面安装 icons 目录的示例,设想一个输入目录由 git 管理,而且还包含我们不希望安装的一些额外文本文件。此命令

install(DIRECTORY data/icons DESTINATION share/myproject
        PATTERN ".git" EXCLUDE
        PATTERN "*.txt" EXCLUDE)

在安装 icons 目录的同时忽略其中包含的任何 .git 目录或文本文件。使用 REGEX 选项的等效命令是

install(DIRECTORY data/icons DESTINATION share/myproject
        REGEX "/.git$" EXCLUDE
        REGEX "/[^/]*.txt$" EXCLUDE)

它使用“/”和“$”以与模式相同的方式约束匹配。考虑一个类似的情况,其中输入目录包含壳脚本和文本文件,我们希望将它们与其他文件一起安装,但使用不同的权限。此命令

install(DIRECTORY data/other/ DESTINATION share/myproject
        PATTERN ".git" EXCLUDE
        PATTERN "*.txt"
          PERMISSIONS OWNER_READ OWNER_WRITE
        PATTERN "*.sh"
          PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)

将 source 目录中的 data/other 内容安装到 share/myproject,同时忽略 .git 目录,并向 .txt.sh 文件授予特定权限。

安装脚本

项目安装可能需要执行除在安装目录中放置文件之外的其他任务。第三方软件包可能提供其自己的机制来注册新的插件,这些插件必须在项目安装期间调用。 SCRIPT 签名就是为此目的提供的。

SCRIPT 关键字紧跟着一个 CMake 脚本的名称。CMake 将在安装期间执行此脚本。如果给定的文件名是相对路径,它将针对当前源目录进行评估。一个简单的用例是在安装期间打印一个消息。我们首先编写一个包含以下代码的 message.cmake 文件

message("Installing My Project")

然后使用以下命令引用此脚本

install(SCRIPT message.cmake)

主 CMakeLists 文件处理过程中不执行自定义安装脚本;它们在安装过程本身执行。在包含 install (SCRIPT) 调用的代码中定义的变量和宏从脚本中不可访问。但在脚本执行过程中定义了一些变量,可用来了解安装信息。变量 CMAKE_INSTALL_PREFIX 设置为实际的安装前缀。它可能与相应的缓存变量值不同,因为安装脚本可能由使用不同前缀的打包工具执行。环境变量 ENV{DESTDIR} 可由用户或打包工具设置。它的值在前缀和绝对安装路径中添加前缀,以确定安装文件的位置。为了引用磁盘上的安装位置,自定义脚本可以使用 $ENV{DESTDIR}${CMAKE_INSTALL_PREFIX} 作为路径的顶层部分。变量 CMAKE_INSTALL_CONFIG_NAME 设置为当前正在安装的构建配置的名称(比如 Debug、Release 等)。在特定组件的安装过程中,变量 CMAKE_INSTALL_COMPONENT 设置为当前组件的名称。

安装代码

自定义安装脚本,像上面的消息一样简单,更易于使用内联到 install 命令的调用中放置的脚本代码创建。 CODE 签名为此目的提供。

CODE 关键字紧随一个字符串之后,该字符串包含要放置在安装脚本中的代码。可以使用以下命令创建安装时消息

install(CODE "MESSAGE(\"Installing My Project\")")

该命令的效果与 message.cmake 脚本相同,但包含内联代码。

安装必备共享库

可执行文件通常使用共享库作为构建块进行构建。在安装此类可执行文件时,您还必须安装其必需的共享库,称为“必需项”,因为可执行文件需要它们来执行加载并正常运行。共享库的三个主要来源: 操作系统本身、您自己项目的生成结果以及属于外部项目的第三方库。可依赖于操作系统中的库,而无需进行任何安装:它们位于可执行文件运行的基础平台上。您自己的项目中的生成结果可能具有add_libraryCMakeLists 文件中的构建规则,因此,理应可以轻松为它们创建 CMake 安装规则。当数量过少或第三方项目的不同版本中库的设置发生变化时,第三方库通常会成为一项维护困难的项目。可能添加库、重新组织代码,而第三方共享库本身可能实际上具有在乍看之下并不明显的其他必需项。

CMake 提供了一个模块,BundleUtilities,它简化处理所需共享库的工作。此模块提供 fixup_bundle 函数,用于复制并修复必需的共享库,这些库使用相对于可执行文件而明确定义的位置。对于 Mac 捆绑应用程序,它将库内嵌到捆绑包中,并使用 install_name_tool 对其进行修复,以形成自包含的单元。在 Windows 中,它将库复制到与可执行文件相同的目录中,因为可执行文件将在自己的目录中搜索所需的 DLL。

fixup_bundle 函数有助于创建可再定位的安装树。Mac 用户喜欢自包含捆绑应用程序:您可以随意拖动它们,双击它们,但它们依然有效。它们不依赖于操作系统本身以外任何已安装在特定位置的内容。类似地,没有管理权限的 Windows 用户喜欢可再定位的安装树,其中可执行文件和所有必需的 DLL 安装在同一个目录中,这样无论您将其安装在哪里都没关系。您甚至可以在安装后移动内容,但它仍然有效。

要使用 fixup_bundle,首先安装一个可执行目标。然后,配置一个可在安装时调用的 CMake 脚本。在已配置的 CMake 脚本内部,只需 include BundleUtilities 并调用 fixup_bundle 函数,提供适当的参数即可。

在 CMakeLists.txt 中

install(TARGETS myExecutable DESTINATION bin)

# To install, for example, MSVC runtime libraries:
include(InstallRequiredSystemLibraries)

# To install other/non-system 3rd party required libraries:
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/FixBundle.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake
  @ONLY
  )

install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/FixBundle.cmake)

在 FixBundle.cmake.in 中

include(BundleUtilities)

# Set bundle to the full path name of the executable already
# existing in the install tree:
set(bundle
   "${CMAKE_INSTALL_PREFIX}/myExecutable@CMAKE_EXECUTABLE_SUFFIX@")

# Set other_libs to a list of full path names to additional
# libraries that cannot be reached by dependency analysis.
# (Dynamically loaded PlugIns, for example.)
set(other_libs "")

# Set dirs to a list of directories where prerequisite libraries
# may be found:
set(dirs
   "@CMAKE_RUNTIME_OUTPUT_DIRECTORY@"
   "@CMAKE_LIBRARY_OUTPUT_DIRECTORY@"
   )

fixup_bundle("${bundle}" "${other_libs}" "${dirs}")

您有责任验证是否有权复制和分发可执行文件的前提共享库。某些库可能有严格的软件许可证,禁止按 fixup_bundle 的方式复制。