CMake的Importing和Exporting 使用指导

文章目录

  • CMake的Importing和Exporting 使用指导
    • 介绍
    • Importing Targets
      • Importing Executables
      • Importing Libraries
    • Exporting Targets
      • Creating Packages
        • Creating a Package Configuration File
        • Creating a Package Version File
      • Exporting Targets from the Build Tree
      • Building and Installing a Package
    • Creating Relocatable Packages
    • Using the Package Configuration File
    • Adding Components


CMake的Importing和Exporting 使用指导

Importing and Exporting Guide

原文:cmake Importing and Exporting Guide

介绍

In this guide, we will present the concept of IMPORTED targets and demonstrate how to import existing executable or library files from disk into a CMake project. We will then show how CMake supports exporting targets from one CMake-based project and importing them into another. Finally, we will demonstrate how to package a project with a configuration file to allow for easy integration into other CMake projects. This guide and the complete example source code can be found in the Help/guide/importing-exporting directory of the CMake source code tree.

本指南中,我们将描述IMPORTED targets的概念,并且演示如何从硬盘导入已经生成的executable或者library到一个CMake项目。
接下来,我们将展示CMake如何实现从一个基于CMake的项目exporting targets,并且在另外一个基于CMake的项目实现importing
最后,我们将演示如何利用一个configuration filepackage 一个项目,利用此操作允许将此项目容易集成到其他CMake项目。
本指南的源码资源参见目录。

Importing Targets

IMPORTED targets are used to convert files outside of a CMake project into logical targets inside of the project. IMPORTED targets are created using the IMPORTED option of the add_executable() and add_library() commands. No build files are generated for IMPORTED targets. Once imported, IMPORTED targets may be referenced like any other target within the project and provide a convenient, flexible reference to outside executables and libraries.

IMPORTED targets是用于转换一个CMake项目以外的文件变为此项目内部的logical targets参与构建。
IMPORTED targets由命令add_executable()或者add_library()设置IMPORTED选项来创建的。
IMPORTED targets不会产生任何build files
一旦导入,IMPORTED targets可以像项目中任意其他target一样被引用,这个功能为项目提供了一种便于灵活引用外部executableslibraries的方式。

By default, the IMPORTED target name has scope in the directory in which it is created and below. We can use the GLOBAL option to extended visibility so that the target is accessible globally in the build system.

默认地,IMPORTED target的作用域为所属的文件目录以及该目录内的目录。我们可以用GLOBAL来提升可见范围,这样可以确保当前build system中可以全局访问此target

Details about the IMPORTED target are specified by setting properties whose names begin in IMPORTED_ and INTERFACE_. For example, IMPORTED_LOCATION contains the full path to the target on disk.

IMPORTED target的实现细节是为此target设置以IMPORTED_INTERFACE_ 开头的属性,并导入到构建项目中参与构建。例如,IMPORTED_LOCATION包含了某个IMPORTED target所在硬盘的全路径。

Importing Executables

To start, we will walk through a simple example that creates an IMPORTED executable target and then references it from the add_custom_command() command.

我们将以一个简单例子作为开始:先创建一个IMPORTED executable target,再基于命令add_custom_command()引用它。

We’ll need to do some setup to get started. We want to create an executable that when run creates a basic main.cc file in the current directory. The details of this project are not important. Navigate to MyExe, create a build directory, run cmake and build and install the project.

我们想基于main.cc源文件构建一个运行程序,并安装到确定的文件夹。本段例子在资源文件中的 MyExe目录下,在MyExe目录内,创建build文件夹,运行cmake构建并安装项目,具体代码如下:

> cd Help/guide/importing-exporting/MyExe
> mkdir build
> cd build
> cmake ..
> cmake --build .
# : "../InstallMyExe/bin/myexe"
> cmake --install . --prefix <install location>
> <install location>/myexe
> ls
[...] main.cc [...]

Now we can import this executable into another CMake project. The source code for this section is available in Importing. In the CMakeLists file, use the add_executable() command to create a new target called myexe. Use the IMPORTED option to tell CMake that this target references an executable file located outside of the project. No rules will be generated to build it and the IMPORTED target property will be set to true.

现在我们可以将这个executable 导入到其他CMake项目。本段例子在资源文件中的Importing目录下。
在需要该executable 的CMake项目中,在CMakeLists文件中,添加命令add_executable()来创建名为myexe的新target。并且在命令中,需要使用IMPORTED 选项,见如下代码,使得CMake可以知道此CMake项目引用了一个项目外的executable 文件。此CMake项目不会重新构建IMPORTED target ,并且IMPORTED target的属性将设置为true

> add_executable(myexe IMPORTED)

Next, set the IMPORTED_LOCATION property of the target using the set_property() command. This will tell CMake the location of the target on disk. The location may need to be adjusted to the specified in the previous step.

接下来,利用命令set_property()IMPORTED target设置属性IMPORTED_LOCATION,见如下代码。这一操作使得CMake可以识别IMPORTED target在硬盘的位置。此处的位置,就是之前步骤中,为IMPORTED target指定的安装目录。

> set_property(TARGET myexe PROPERTY
             IMPORTED_LOCATION "../InstallMyExe/bin/myexe")

We can now reference this IMPORTED target just like any target built within the project. In this instance, let’s imagine that we want to use the generated source file in our project. Use the IMPORTED target in the add_custom_command() command:

我们现在可以像引用项目内部target一样,引用IMPORTED target
在本段例子中,我们想实现的功能是,利用导入的myexe重新生成main.cc文件,见如下代码:

> add_custom_command(OUTPUT main.cc COMMAND myexe)

As COMMAND specifies an executable target name, it will automatically be replaced by the location of the executable given by the IMPORTED_LOCATION property above.

命令add_custom_command中,COMMAND选项指定了一个executable target的名字,在CMake构建过程中,该名字会自动被上述步骤所设置属性IMPORTED_LOCATION的值所替换。

Finally, use the output from add_custom_command():

最后,利用命令add_custom_command输出文件main.cc构建一个名为mynewexe的应用,代码如下:

> add_executable(mynewexe main.cc)

Importing Libraries

In a similar manner, libraries from other projects may be accessed through IMPORTED targets.
Note: The full source code for the examples in this section is not provided and is left as an exercise for the reader.
In the CMakeLists file, add an IMPORTED library and specify its location on disk:

项目中可以通过IMPORTED targets方法访问其它项目的libraries
注意:不提供本段例子的源码,用户可以参照上述源码进行练习。
在项目的CMakeLists 文件中,增加IMPORTED library并且指定硬盘所在位置的源码如下:

> add_library(foo STATIC IMPORTED)
> set_property(TARGET foo PROPERTY
             IMPORTED_LOCATION "/path/to/libfoo.a")

Then use the IMPORTED library inside of our project:

在项目中使用IMPORTED library的源码如下:

> add_executable(myexe src1.c src2.c)
> target_link_libraries(myexe PRIVATE foo)

On Windows, a .dll and its .lib import library may be imported together:

在Windows平台,.dll的动态库和对应的.lib导入库导入的源码如下:

> add_library(bar SHARED IMPORTED)
> set_property(TARGET bar PROPERTY
             IMPORTED_LOCATION "c:/path/to/bar.dll")
> set_property(TARGET bar PROPERTY
             IMPORTED_IMPLIB "c:/path/to/bar.lib")
> add_executable(myexe src1.c src2.c)
> target_link_libraries(myexe PRIVATE bar)

A library with multiple configurations may be imported with a single target:

存在多个配置的库导入作为一个单一target的源码如下:

> find_library(math_REL NAMES m)
> find_library(math_DBG NAMES md)
> add_library(math STATIC IMPORTED GLOBAL)
> set_target_properties(math PROPERTIES
    IMPORTED_LOCATION "${math_REL}"
    IMPORTED_LOCATION_DEBUG "${math_DBG}"
    IMPORTED_CONFIGURATIONS "RELEASE;DEBUG"
  )
> add_executable(myexe src1.c src2.c)
> target_link_libraries(myexe PRIVATE math)

The generated build system will link myexe to m.lib when built in the release configuration, and md.lib when built in the debug configuration.

上述代码中,生成的构建系统,在release模式下将链接myexe 和 m.lib,在debug模式下将链接myexe 和md.lib。

Exporting Targets

While IMPORTED targets on their own are useful, they still require that the project that imports them knows the locations of the target files on disk. The real power of IMPORTED targets is when the project providing the target files also provides a CMake file to help import them. A project can be setup to produce the necessary information so that it can easily be used by other CMake projects be it from a build directory, a local install or when packaged.

IMPORTED targets的技术是非常有用的,但是上述的使用方式中,需要导入target的项目明确指导target在硬盘上的具体位置。
IMPORTED targets的技术最方便使用的方式是,生成IMPORTED targets的项目不仅仅生成target文件,还生成一个用于辅助导入target的CMake文件,同时使用两者可简化IMPORTED targets使用难度。
一个生成IMPORTED targets的项目可以从build目录、local install目录或者packaged中提取便于其它CMake项目使用的必要的信息。

In the remaining sections, we will walk through a set of example projects step-by-step. The first project will create and install a library and corresponding CMake configuration and package files. The second project will use the generated package.

在接下来的部分中,我们将一步步通过例子深入学习。
第一个例子中,我们将创建安装一个library、相关的CMake configuration文件、相关的package文件。第二个例子我们将使用这些文件。

Let’s start by looking at the MathFunctions project in the MathFunctions directory. Here we have a header file MathFunctions.h that declares a sqrt function:

例子中MathFunctions项目在资源文件中的MathFunctions目录内。目录内有头文件MathFunctions.h,代码如下:

#pragma once

namespace MathFunctions {
  double sqrt(double x);
}

And a corresponding source file MathFunctions.cxx:

对应的MathFunctions.cxx源码如下:

#include "MathFunctions.h"

#include 

namespace MathFunctions {
  double sqrt(double x)
  {
    return std::sqrt(x);
  }
}

Don’t worry too much about the specifics of the C++ files, they are just meant to be a simple example that will compile and run on many build systems.
Now we can create a CMakeLists.txt file for the MathFunctions project. Start by specifying the cmake_minimum_required() version and project() name:

不需要担心这些C++文件的标准规范,当中的源码就是一个可以在很多build systems完成编译和运行的简单例子。
现在为MathFunctions项目创建CMakeLists.txt文件。使用命令cmake_minimum_required() 指定需要CMake工具的版本,使用命令project()指定项目的名字:

> cmake_minimum_required(VERSION 3.15)
> project(MathFunctions)

# make cache variables for install destinations
> include(GNUInstallDirs)

# specify the C++ standard
> set(CMAKE_CXX_STANDARD 11)
> set(CMAKE_CXX_STANDARD_REQUIRED True)

The GNUInstallDirs module is included in order to provide the project with the flexibility to install into different platform layouts by making the directories available as cache variables.
Create a library called MathFunctions with the add_library() command:

GNUInstallDirs模块通过将安装目录作为CMake构建过程的cache变量,为项目提供了不同平台下安装目录布局的灵活性。
利用命令add_library()创建名为MathFunctions的库:

> add_library(MathFunctions STATIC MathFunctions.cxx)

And then use the target_include_directories() command to specify the include directories for the target:

接下来,利用命令target_include_directories()指定target相关的include目录:

> target_include_directories(MathFunctions
                            PUBLIC
                            "$"
                            "$"
 )

We need to tell CMake that we want to use different include directories depending on if we’re building the library or using it from an installed location. If we don’t do this, when CMake creates the export information it will export a path that is specific to the current build directory and will not be valid for other projects. We can use generator expressions to specify that if we’re building the library include the current source directory. Otherwise, when installed, include the include directory. See the Creating Relocatable Packages section for more details.

需要让CMake明确:针对building the library的情况和直接在安装位置使用library的情况,我们需要分别指定不一样的include目录。如果我们不做此处理,当CMake创建export information时,include目录是一个指向当前build目录的路径,这个路径对于其它项目来说是无效的。
我们可以通过generator expressions来指定building the library时的include目录和在安装目录下使用的include目录。可以看Creating Relocatable Packages部分获取更多的细节。

The install(TARGETS) and install(EXPORT) commands work together to install both targets (a library in our case) and a CMake file designed to make it easy to import the targets into another CMake project.

为了安装targets(我们例子中是一个library)和便于其它CMake项目导入target的CMake文件,我们需要联合使用命令install(TARGETS)install(EXPORT)

First, in the install(TARGETS) command we will specify the target, the EXPORT name and the destinations that tell CMake where to install the targets.

首先,利用命令 install(TARGETS),我们将指定target的EXPORT名字,CMake安装target的目标目录:

# ${CMAKE_INSTALL_LIBDIR}: Linux x64:lib64
# ${CMAKE_INSTALL_INCLUDEDIR}: include 
> install(TARGETS MathFunctions
        EXPORT MathFunctionsTargets
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

Here, the EXPORT option tells CMake to create an export called MathFunctionsTargets. The generated IMPORTED targets have appropriate properties set to define their usage requirements, such as INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONSand other relevant built-in INTERFACE_ properties. The INTERFACE variant of user-defined properties listed in COMPATIBLE_INTERFACE_STRING and other Compatible Interface Properties are also propagated to the generated IMPORTED targets. For example, in this case, the IMPORTED target will have its INTERFACE_INCLUDE_DIRECTORIES property populated with the directory specified by the INCLUDES DESTINATION property. As a relative path was given, it is treated as relative to the CMAKE_INSTALL_PREFIX.

例子中,EXPORT选项指定了CMake要为MathFunctions这个target创建一个名为MathFunctionsTargets的export。
生成的IMPORTED targets利用合适的属性定义他们的 usage requirements,例如INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS以及其他相关的内建的INTERFACE_开头的属性。
COMPATIBLE_INTERFACE_STRING清单中,用户定义的
INTERFACE
变量和其他Compatible Interface Properties也传递到了生成的IMPORTED targets中。例如,本例中,通过选项 INCLUDES DESTINATION指定的目录属性值传递作为IMPORTED targetINTERFACE_INCLUDE_DIRECTORIES属性值。当给定的是相对路径时,CMake会自动根据CMAKE_INSTALL_PREFIX匹配出完整的路径。

Note, we have not asked CMake to install the export yet.

注意:目前,我们还未指定CMake安装export的命令。

We don’t want to forget to install the MathFunctions.h header file with the install(FILES) command. The header file should be installed to the include directory, as specified by the target_include_directories() command above.

我们将利用命令install(FILES)安装 MathFunctions.h 头文件。头文件应该安装到上述命令target_include_directories()所指定的include目录中,代码如下:

# ${CMAKE_INSTALL_INCLUDEDIR}: include 
> install(FILES MathFunctions.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

Now that the MathFunctions library and header file are installed, we also need to explicitly install the MathFunctionsTargets export details. Use the install(EXPORT) command to export the targets in MathFunctionsTargets, as defined by the install(TARGETS) command.

到这一步骤,MathFunctions库文件和头文件都已经安装好了,我们还需要安装MathFunctionsTargets export 文件。利用命令install(EXPORT)来安装命令install(TARGETS)定义的名为MathFunctionsTargets的export。见代码如下:

# ${CMAKE_INSTALL_LIBDIR}: Linux x64: lib64
> install(EXPORT MathFunctionsTargets
          FILE MathFunctionsTargets.cmake
          NAMESPACE MathFunctions::
          DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
  )

This command generates the MathFunctionsTargets.cmake file and arranges to install it to ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions. The file contains code suitable for use by downstreams to import all targets listed in the install command from the installation tree.

此命令生成MathFunctionsTargets.cmake文件,并且安装到**${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions目录下。MathFunctionsTargets.cmake文件包含了下游target从installation tree**导入所有target的合适代码。

The NAMESPACE option will prepend MathFunctions:: to the target names as they are written to the export file. This convention of double-colons gives CMake a hint that the name is an IMPORTED target when it is used by downstream projects. This way, CMake can issue a diagnostic message if the package providing it was not found.

NAMESPACE选项为target的名字增加了前缀MathFunctions::,并且写出到export 文件中。通过双冒号的方式约定了名字前缀,当以此名字作为IMPORTED target被下游项目应用时,不会导致命名冲突。这种方式下,如果IMPORTED target提供的package未找到,CMake会提示诊断提示。

The generated export file contains code that creates an IMPORTED library.

生成的export文件会包含创建IMPORTED library的源码:

# Create imported target MathFunctions::MathFunctions
> add_library(MathFunctions::MathFunctions STATIC IMPORTED)

> set_target_properties(MathFunctions::MathFunctions PROPERTIES
    INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
  )

This code is very similar to the example we created by hand in the Importing Libraries section. Note that ${_IMPORT_PREFIX} is computed relative to the file location.

这段代码和我们在Importing Libraries一节中手动创建的例子代码相似。注意:**${_IMPORT_PREFIX}**时根据export文件所在目录位置来计算的。

An outside project may load this file with the include() command and reference the MathFunctions library from the installation tree as if it were built in its own tree. For example:

一个新的项目可以利用include()命令来加载这个export文件,并且此项目在build tree过程中,可以从安装目录来引用MathFunctions 库。例如:

# Step1
> include(GNUInstallDirs)
# Step2 
> include(${INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions/MathFunctionTargets.cmake)
# Step3
> add_executable(myexe src1.c src2.c )
# Step4
> target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

Step2 loads the target CMake file. Although we only exported a single target, this file may import any number of targets. Their locations are computed relative to the file location so that the install tree may be easily moved. Step4 references the imported MathFunctions library. The resulting build system will link to the library from its installed location.

Step2加载了MathFunctions的export文件:MathFunctionTargets.cmake。
虽然我们在MathFunctions项目中只导出了一份target,但是这个export文件可以在任意项目导入作为任意的targets。
导入的targets的位置根据export文件的路径进行计算,这样就可以保证install tree可以任意移动。
Step4 引用了导入的MathFunctions库。此时build system就可以从MathFunctions库的安装位置内,将内容链接到当前项目使用。

Executables may also be exported and imported using the same process.

Executables也是可以在同一进程中导出和导入使用。

Any number of target installations may be associated with the same export name. Export names are considered global so any directory may contribute a target installation. The install(EXPORT) command only needs to be called once to install a file that references all targets. Below is an example of how multiple exports may be combined into a single export file, even if they are in different subdirectories of the project.

任意数量的target可以关联到同一个export名字。export名字的作用域是全局的,因而可以支持关联多个target的安装目录。虽然一个export关联了任意数量的target,但是命令install(EXPORT)只需要调用一次。如下代码所示:

# A/CMakeLists.txt
> add_executable(myexe src1.c)
> install(TARGETS myexe DESTINATION lib/myproj
          EXPORT myproj-targets)

# B/CMakeLists.txt
> add_library(foo STATIC foo1.c)
> install(TARGETS foo DESTINATION lib EXPORTS myproj-targets)

# Top CMakeLists.txt
> add_subdirectory(A)
> add_subdirectory(B)
> install(EXPORT myproj-targets DESTINATION lib/myproj)

Creating Packages

At this point, the MathFunctions project is exporting the target information required to be used by other projects. We can make this project even easier for other projects to use by generating a configuration file so that the CMake find_package() command can find our project.

本节例子中,MathFunctions项目将导出被其他项目引用的必要target information
我们通过生成一个configuration文件,再利用命令find_package()根据configuration文件找到需要导入的target的方式,可以更容易让MathFunctions项目被其他项目引用使用。

To start, we will need to make a few additions to the CMakeLists.txt file. First, include the CMakePackageConfigHelpers module to get access to some helper functions for creating config files.

接下来,我们将在MathFunctions项目中的CMakeLists.txt文件中添加一些指令。首先包含用于访问创建config文件帮助函数的CMakePackageConfigHelpers模块:

> include(CMakePackageConfigHelpers)

Then we will create a package configuration file and a package version file.

接下来,我们将创建一个package configuration文件和一个package version文件。

Creating a Package Configuration File

Use the configure_package_config_file() command provided by the CMakePackageConfigHelpers to generate the package configuration file. Note that this command should be used instead of the plain configure_file() command. It helps to ensure that the resulting package is relocatable by avoiding hardcoded paths in the installed configuration file. The path given to INSTALL_DESTINATION must be the destination where the MathFunctionsConfig.cmake file will be installed. We will examine the contents of the package configuration file in the next section.

利用由CMakePackageConfigHelpers模块提供的configure_package_config_file()命令来生成package configuration文件。注意这里不能使用用于文本文件的configure_file()的命令。利用命令configure_package_config_file()可以确保生成的package路径不会在package configuration文件中以硬编码路径的方式记录,这样就可以实现重定位。
configure_package_config_file()命令中,INSTALL_DESTINATION选项指定的目录路径是MathFunctionsConfig.cmake 文件安装的目标目录。关于configure_package_config_file()使用代码如下所示:

> configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
    "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
  )

Install the generated configuration files with the INSTALL(files) command. Both MathFunctionsConfigVersion.cmake and MathFunctionsConfig.cmake are installed to the same location, completing the package.

利用命令INSTALL(files)来安装生成的configuration文件。只有MathFunctionsConfigVersion.cmakeMathFunctionsConfig.cmake文件都安装到了同一位置,才算完成包的安装,如下代码所示:

> install(FILES
            "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
            "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
          DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
  )

Now we need to create the package configuration file itself. In this case, the Config.cmake.in file is very simple but sufficient to allow downstreams to use the IMPORTED targets.

现在我们需要创建一个Config.cmake.in文件,以便CMake根据此文件自动生成package configuration文件的内容。Config.cmake.in文件内容非常简单,主要是允许下游项目引用此项目作为IMPORTED targets的必要信息。如下所示:

> @PACKAGE_INIT@

> include("${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake")

> check_required_components(MathFunctions)

The first line of the file contains only the string @PACKAGE_INIT@. This expands when the file is configured and allows the use of relocatable paths prefixed with PACKAGE_. It also provides the set_and_check() and check_required_components() macros.

上述代码中第一行仅仅包含字符串:@PACKAGE_INIT@。这个字符串将在Config.cmake.in文件configured期间被替换,这样一来就允许使用**PACKAGE_**开头的可重定向的路径变量。

The check_required_components() helper macro ensures that all requested, non-optional components have been found by checking the __FOUND variables for all required components. This macro should be called at the end of the package configuration file even if the package does not have any components. This way, CMake can make sure that the downstream project hasn’t specified any non-existent components. If check_required_components() fails, the _FOUND variable is set to FALSE, and the package is considered to be not found.

对于所有非可选的required componentscheck_required_components()宏通过核对required components对应的 __FOUND 变量来确保是否可以正确引用package。即使package不存在任何componentscheck_required_components()宏应该在package configuration文件结尾调用。这样一来,CMake可以确保下游项目不用指定任何不存在的components。如果check_required_components()失败,_FOUND 变量将设置为false,即这个package未找到。

The set_and_check() macro should be used in configuration files instead of the normal set() command for setting directories and file locations. If a referenced file or directory does not exist, the macro will fail.

configuration文件中,应该使用set_and_check()宏替换set()命令来设置目录位置和文件位置。如果一个引用文件或者目录不存在,set_and_check()宏将失败。

If any macros should be provided by the MathFunctions package, they should be in a separate file which is installed to the same location as the MathFunctionsConfig.cmake file, and included from there.

MathFunctions package如果包含了任意的宏函数,这些宏函数应该放到一个独立的文件,并且这个文件应与 MathFunctionsConfig.cmake文件在同一目录,其他项目导入的时候,从此目录导入。

All required dependencies of a package must also be found in the package configuration file. Let’s imagine that we require the Stats library in our project. In the CMakeLists file, we would add:

一个package的所有依赖文件必须在package configuration文件中找到。如果在我们项目中使用Stats库,我们需要在CMakeLists添加如下命令:

find_package(Stats 2.6.4 REQUIRED)
target_link_libraries(MathFunctions PUBLIC Stats::Types)

As the Stats::Types target is a PUBLIC dependency of MathFunctions, downstreams must also find the Stats package and link to the Stats::Types library. The Stats package should be found in the configuration file to ensure this.

Stats::Typestarget作为MathFunctions库的PUBLIC 依赖,下游项目必须找到Stats的package,并且链接到Stats::Types库。Stats的package必选在configuration 文件中找到来确保这个流程:

include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)

The find_dependency macro from the CMakeFindDependencyMacro module helps by propagating whether the package is REQUIRED, or QUIET, etc. The find_dependency macro also sets MathFunctions_FOUND to False if the dependency is not found, along with a diagnostic that the MathFunctions package cannot be used without the Stats package.

CMakeFindDependencyMacro模块的find_dependency()宏可以通过package的约束选项REQUIREDQUIET等来决定package是否传递到下游。
如果MathFunctions库的依赖未发现,find_dependency()宏将设置MathFunctions_FOUND为False。

Exercise: Add a required library to the MathFunctions project.

Creating a Package Version File

The CMakePackageConfigHelpers module provides the write_basic_package_version_file() command for creating a simple package version file. This file is read by CMake when find_package() is called to determine the compatibility with the requested version, and to set some version-specific variables such as _VERSION, _VERSION_MAJOR, _VERSION_MINOR, etc. See cmake-packages documentation for more details.

CMakePackageConfigHelpers 模块提供了命令write_basic_package_version_file()来创建简单的package version文件。当find_package()被调用时,CMake将读取package version文件决定package请求版本的兼容性,并且会设置一些version-specific的变量,比如,_VERSION, _VERSION_MAJOR, _VERSION_MINOR等等。可以通过cmake-packages文档获取更多细节。具体代码如下:

> set(version 3.4.1)

> set_property(TARGET MathFunctions PROPERTY VERSION ${version})
> set_property(TARGET MathFunctions PROPERTY SOVERSION 3)
> set_property(TARGET MathFunctions PROPERTY
    INTERFACE_MathFunctions_MAJOR_VERSION 3)
>set_property(TARGET MathFunctions APPEND PROPERTY
    COMPATIBLE_INTERFACE_STRING MathFunctions_MAJOR_VERSION
)

# generate the version file for the config file
> write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
    VERSION "${version}"
    COMPATIBILITY AnyNewerVersion
  )

In our example, MathFunctions_MAJOR_VERSION is defined as a COMPATIBLE_INTERFACE_STRING which means that it must be compatible among the dependencies of any depender. By setting this custom defined user property in this version and in the next version of MathFunctions, cmake will issue a diagnostic if there is an attempt to use version 3 together with version 4. Packages can choose to employ such a pattern if different major versions of the package are designed to be incompatible.

在我们例子中,MathFunctions_MAJOR_VERSION被定义作为一个COMPATIBLE_INTERFACE_STRING ,这意味着MathFunctions_MAJOR_VERSION作为其他项目的依赖包必须是能够兼容的。利用set()定义了当前版本号,当使用MathFunctions的下一版本时,如果同时使用使用版本3和版本4,那么cmake会提示诊断错误信息。如果package不同主版本设置成了不兼容特性,Packages 可以选择这样的方法来实现。

Exporting Targets from the Build Tree

Typically, projects are built and installed before being used by an outside project. However, in some cases, it is desirable to export targets directly from a build tree. The targets may then be used by an outside project that references the build tree with no installation involved. The export() command is used to generate a file exporting targets from a project build tree.

典型地,项目在被其它项目引用时,需要先buildinstall。然而,在一些案例中,希望直接从项目的build tree生成export targets,供项目其他target使用或者其他项目使用。这种情况下,引用方项目不需要安装,直接引用build tree就可以使用被引用方的项目。命令export() 用于从一个项目的build tree生成export targets

If we want our example project to also be used from a build directory we only have to add the following to CMakeLists.txt:

如果我们想我们的例子MathFunctions也可以从build tree直接引用,需要在CMakeLists.txt添加命令:

> export(EXPORT MathFunctionsTargets
         FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/MathFunctionsTargets.cmake"
         NAMESPACE MathFunctions::
  )

Here we use the export() command to generate the export targets for the build tree. In this case, we’ll create a file called MathFunctionsTargets.cmake in the cmake subdirectory of the build directory. The generated file contains the required code to import the target and may be loaded by an outside project that is aware of the project build tree. This file is specific to the build-tree, and is not relocatable.

这里我们使用命令export()build tree生成了export targets。在这个例子中,我们将在build目录的子目录cmake下创建MathFunctionsTargets.cmake文件。这个文件包含了可以让其它项目从当前项目的build tree导入target使用的必须代码。这个文件指向了build tree,是不可重定向的

It is possible to create a suitable package configuration file and package version file to define a package for the build tree which may be used without installation. Consumers of the build tree can simply ensure that the CMAKE_PREFIX_PATH contains the build directory, or set the MathFunctions_DIR to /MathFunctions in the cache.

cmake可以不需要安装,直接根据build tree为项目的package创建合适的package configuration文件和package version文件。引用方可以简单设置CMAKE_PREFIX_PATH 变量包含build目录,或者设置cache中的MathFunctions_DIR 变量值为**/MathFunctions**目录,find_package()会自动搜索到相关配置文件来引用。

An example application of this feature is for building an executable on a host platform when cross-compiling. The project containing the executable may be built on the host platform and then the project that is being cross-compiled for another platform may load it.

这个技术具体应用的一个例子:主机平台交叉编译executable时。 例如,一个项目包含在一个主机平台上编译得到的executable ,然后项目在另外主机平台交叉编译时可能会加载它。

Building and Installing a Package

At this point, we have generated a relocatable CMake configuration for our project that can be used after the project has been installed. Let’s try to build the MathFunctions project:

上述各节内容讲述了如何为我们的项目生成在项目install后可导入使用的可重定向的CMake configuration文件。MathFunctions 项目build流程如下:

> mkdir MathFunctions_build
> cd MathFunctions_build
> cmake ../MathFunctions
> cmake --build .

In the build directory, notice that the file MathFunctionsTargets.cmake has been created in the cmake subdirectory.
Now install the project:

build目录中,cmake子目录中创建出了MathFunctionsTargets.cmake文件。
安装项目的命令如下:

> cmake --install . --prefix "/home/myuser/installdir"

Creating Relocatable Packages

Packages created by install(EXPORT) are designed to be relocatable, using paths relative to the location of the package itself. They must not reference absolute paths of files on the machine where the package is built that will not exist on the machines where the package may be installed.

命令install(EXPORT)创建的packages通过使用基于package本身的相对路径,可实现重定向。其他引用方项目不会引用被引用方项目构建package所在机器的路径,这个路径在package安装机器上是可能不存在的。

When defining the interface of a target for EXPORT, keep in mind that the include directories should be specified as relative paths to the CMAKE_INSTALL_PREFIX but should not explicitly include the CMAKE_INSTALL_PREFIX:

为一个target的EXPORT定义interface 属性时,需要谨记include目录应该设置为基于CMAKE_INSTALL_PREFIX的相对路径,而不是绝对路径。如下代码所示:

> target_include_directories(tgt INTERFACE
    # Wrong, not relocatable:
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/TgtName>
  )

> target_include_directories(tgt INTERFACE
    # Ok, relocatable:
    $<INSTALL_INTERFACE:include/TgtName>
  )

The $ generator expression may be used as a placeholder for the install prefix without resulting in a non-relocatable package. This is necessary if complex generator expressions are used:

$生成器表达式可以用作install目录的前缀的占位符,这样一来就不会导出一个不能重定向的package。具体用法见如下代码:

> target_include_directories(tgt INTERFACE
    # Ok, relocatable:
    $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/TgtName>
  )

This also applies to paths referencing external dependencies. It is not advisable to populate any properties which may contain paths, such as INTERFACE_INCLUDE_DIRECTORIES or INTERFACE_LINK_LIBRARIES, with paths relevant to dependencies. For example, this code may not work well for a relocatable package:

这种方式也应用到引用额外dependencies的路径。不建议传递任何跟dependencies相关的包含路径的属性,像INTERFACE_INCLUDE_DIRECTORIES或者INTERFACE_LINK_LIBRARIES。例如,如下代码可能不会为一个可重定向包得到预期的结果:

> target_link_libraries(MathFunctions INTERFACE
    ${Foo_LIBRARIES} ${Bar_LIBRARIES}
  )
> target_include_directories(MathFunctions INTERFACE
    "$"
  )

The referenced variables may contain the absolute paths to libraries and include directories as found on the machine the package was made on. This would create a package with hard-coded paths to dependencies not suitable for relocation.

package生成时,一些引用变量可能会包含指向libraries文件和include目录的绝对路径。这样一来在创建package时会为dependencies进行硬编码绑定,不适合重定向。

Ideally such dependencies should be used through their own IMPORTED targets that have their own IMPORTED_LOCATION and usage requirement properties such as INTERFACE_INCLUDE_DIRECTORIES populated appropriately. Those imported targets may then be used with the target_link_libraries() command for MathFunctions:

理想情况下,dependencies的使用应该通过导入他们自己的IMPORTED targets来使用,IMPORTED targets中包含了IMPORTED_LOCATION属性、usage requirement属性,例如INTERFACE_INCLUDE_DIRECTORIES。这些 imported targets可以基于命令target_link_libraries()来引用。例如:

> target_link_libraries(MathFunctions INTERFACE Foo::Foo Bar::Bar)

With this approach the package references its external dependencies only through the names of IMPORTED targets. When a consumer uses the installed package, the consumer will run the appropriate find_package() commands (via the find_dependency macro described above) to find the dependencies and populate the imported targets with appropriate paths on their own machine.

仅仅通过IMPORTED targets的名字来引用package相关的dependencies 。当一个引用方项目使用一个安装好的package时,引用方项目通过命令find_package() (内部使用find_dependency()命令)来搜索引用dependencies ,实现不同机器的imported targets合适路径的传递。

Using the Package Configuration File

Now we’re ready to create a project to use the installed MathFunctions library. In this section we will be using source code from Help\guide\importing-exporting\Downstream. In this directory, there is a source file called main.cc that uses the MathFunctions library to calculate the square root of a given number and then prints the results:

本节将创建一个项目来使用安装好的MathFunctions 库。本节代码见资源文件中的Downstream文件夹。其中源文件main.cc内容如下:

// A simple program that outputs the square root of a number
#include 
#include 

#include "MathFunctions.h"

int main(int argc, char* argv[])
{
  if (argc < 2) {
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

  // convert input to double
  const double inputValue = std::stod(argv[1]);

  // calculate square root
  const double sqrt = MathFunctions::sqrt(inputValue);
  std::cout << "The square root of " << inputValue << " is " << sqrt
            << std::endl;

  return 0;
}

As before, we’ll start with the cmake_minimum_required() and project() commands in the CMakeLists.txt file. For this project, we’ll also specify the C++ standard.

和前面例子一样,我们在CMakeLists.txt文件中以命令cmake_minimum_required()和命令project()作为开始。对于这个项目,我们也指定了C++标准:

> cmake_minimum_required(VERSION 3.15)
> project(Downstream)

# specify the C++ standard
> set(CMAKE_CXX_STANDARD 11)
> set(CMAKE_CXX_STANDARD_REQUIRED True)

We can use the find_package() command:

我们使用命令find_package()

> find_package(MathFunctions 3.4.1 EXACT)

Create an executable:

创建一个executable

> add_executable(myexe main.cc)

And link to the MathFunctions library:

链接MathFunctions 库:

> target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

That’s it! Now let’s try to build the Downstream project.

buildDownstream 项目:

> mkdir Downstream_build
> cd Downstream_build
> cmake ../Downstream
> cmake --build .

A warning may have appeared during CMake configuration:

CMake configuration期间会提示如下警告:

CMake Warning at CMakeLists.txt:4 (find_package):
  By not providing "FindMathFunctions.cmake" in CMAKE_MODULE_PATH this
  project has asked CMake to find a package configuration file provided by
  "MathFunctions", but CMake did not find one.

  Could not find a package configuration file provided by "MathFunctions"
  with any of the following names:

    MathFunctionsConfig.cmake
    mathfunctions-config.cmake

  Add the installation prefix of "MathFunctions" to CMAKE_PREFIX_PATH or set
  "MathFunctions_DIR" to a directory containing one of the above files.  If
  "MathFunctions" provides a separate development package or SDK, be sure it
  has been installed.

Set the CMAKE_PREFIX_PATH to where MathFunctions was installed previously and try again. Ensure that the newly created executable runs as expected.

设置CMAKE_PREFIX_PATH 变量指向MathFunctions 的export的安装目录,即可成功构建。

Adding Components

Let’s edit the MathFunctions project to use components. The source code for this section can be found in Help\guide\importing-exporting\MathFunctionsComponents. The CMakeLists file for this project adds two subdirectories: Addition and SquareRoot.

编辑MathFunctions项目按照components模式使用。本节代码在资源文件的MathFunctionsComponents文件夹内。MathFunctionsComponents项目中增加了两个子目录Addition 和SquareRoot。CMakeLists 文件内容如下:

> cmake_minimum_required(VERSION 3.15)
> project(MathFunctionsComponents)

# make cache variables for install destinations
> include(GNUInstallDirs)

# specify the C++ standard
> set(CMAKE_CXX_STANDARD 11)
> set(CMAKE_CXX_STANDARD_REQUIRED True)

> add_subdirectory(Addition)
> add_subdirectory(SquareRoot)

Generate and install the package configuration and package version files:

生成和安装package configuration文件和package version文件。代码如下:

> include(CMakePackageConfigHelpers)

# set version
> set(version 3.4.1)

# generate the version file for the config file
> write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
    VERSION "${version}"
    COMPATIBILITY AnyNewerVersion
  )

# create config file
> configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
    "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
    NO_CHECK_REQUIRED_COMPONENTS_MACRO
  )

# install config files
> install(FILES
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
          "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
         DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
)

If COMPONENTS are specified when the downstream uses find_package(), they are listed in the _FIND_COMPONENTS variable. We can use this variable to verify that all necessary component targets are included in Config.cmake.in. At the same time, this function will act as a custom check_required_components macro to ensure that the downstream only attempts to use supported components.

如果在下游引用方项目使用命令find_package()指定COMPONENTS 选项时,_FIND_COMPONENTS变量给出了COMPONENTS列表。我们可以用这个变量在Config.cmake.in文件中识别判断必须包含的COMPONENTS是否存在。同时,这个函数将扮演check_required_components()宏的角色,确保能够使用支持的COMPONENTS。Config.cmake.in文件代码如下:

> @PACKAGE_INIT@

> set(_MathFunctions_supported_components Addition SquareRoot)

> foreach(_comp ${MathFunctions_FIND_COMPONENTS})
    if (NOT _comp IN_LIST _MathFunctions_supported_components)
      set(MathFunctions_FOUND False)
      set(MathFunctions_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
    endif()
    include("${CMAKE_CURRENT_LIST_DIR}/MathFunctions${_comp}Targets.cmake")
  endforeach()

Here, the MathFunctions_NOT_FOUND_MESSAGE is set to a diagnosis that the package could not be found because an invalid component was specified. This message variable can be set for any case where the _FOUND variable is set to False, and will be displayed to the user.

MathFunctions_NOT_FOUND_MESSAGE变量是记录package中因为指定无效component 时不能引用package的诊断错误信息。这个message 变量可以用在_FOUND 变量为False的场景中,为用户展示必要的错误信息。

The Addition and SquareRoot directories are similar. Let’s look at one of the CMakeLists files:

Addition 和SquareRoot 目录是相似的,我们看SquareRoot目录的CMakeLists 文件内容:

# create library
> add_library(SquareRoot STATIC SquareRoot.cxx)

> add_library(MathFunctions::SquareRoot ALIAS SquareRoot)

# add include directories
> target_include_directories(SquareRoot
                             PUBLIC
                            "$"
                            "$"
  )

# install the target and create export-set
> install(TARGETS SquareRoot
          EXPORT SquareRootTargets
          LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
          ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
          RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
          INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  )

# install header file
> install(FILES SquareRoot.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# generate and install export file
> install(EXPORT SquareRootTargets
          FILE MathFunctionsSquareRootTargets.cmake
          NAMESPACE MathFunctions::
          DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions
  )

Now we can build the project as described in earlier sections. To test using this package, we can use the project in Help\guide\importing-exporting\DownstreamComponents. There’s two differences from the previous Downstream project. First, we need to find the package components. Change the find_package line:

现在我们可以像之前例子一样build项目了。为了测试使用这个package,我们可以使用资源文件夹内的DownstreamComponents项目。这个项目与Downstream 项目的区别如下,首先我们需要搜索package components。命令find_package(使用区别如下):

# Downstream 
# find_package(MathFunctions 3.4.1 EXACT)

# DownstreamComponents
> find_package(MathFunctions 3.4 COMPONENTS Addition SquareRoot)

命令target_link_libraries 使用区别如下:

# Downstream 
# target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)

# DownstreamComponents
> target_link_libraries(myexe PRIVATE MathFunctions::Addition MathFunctions::SquareRoot)

最后build项目DownstreamComponents,即可实现package components自动搜索导入使用。

你可能感兴趣的:(开发环境,c++,CMake,导入和导出)