安装和测试过程中,我们增加了 CMake 安装库文件和项目依赖的头文件(bin、lib、include)的能力。在打包可安装程序的过程中,我们增加了打包这些信息的能力,这样它就可以分发给其他人使用

接下来是如何添加必要的信息,以便其他 CMake 项目可以使用我们的项目,无论是在构建目录、本地安装还是打包的时候

第一步是更新我们的 install(TARGETS) 命令,不仅指定 DESTINATION,还指定 EXPORTEXPORT 关键字生成一个 CMake 文件,其中包含从安装树导入安装命令中列出的所有目标的代码。

修改 MathFunctions/CMakeLists.txt 文件代码:


# install rules
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
  list(APPEND installable_libs SqrtLibrary)
endif()

install(TARGETS ${installable_libs} 
        EXPORT MathFunctionsTargets
        DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)

第二步我们还需要显式地安装生成的 MathFunctionsTargets.cmake 文件。在顶层的 CMakeLists.txt 文件底部添加如下代码:

install(EXPORT MathFunctionsTargets
  FILE MathFunctionsTargets.cmake
  DESTINATION lib/cmake/MathFunctions
)

此时构建会有下面的错误:

CMakeExportDirError

这是因为在生成导出信息期间,它将导出内在绑定到当前计算机项目的路径,在其他计算机上路径无效。

第三步更新 MathFunctions/CMakeLists.txt 中的 target_include_directories() 用来设置在 build 时和在 install/package 时需要使用不同的 INTERFACE locations

# 将当前二进制目录添加到包含目录列表中
target_include_directories(MathFunctions
            # INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
            INTERFACE
            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
            $<INSTALL_INTERFACE:include>
)

第四步我们还需要生成一个 MathFunctionsConfig.cmake,这样 CMake 的 find_package() 命令就可以找到我们的项目。在顶层的项目目录添加 Config.cmake.in 配置文件,内容如下:

@PACKAGE_INIT@

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

为了正确地配置和安装该文件,将以下内容添加到顶层 CMakeLists.txt 文件的底部


include(CMakePackageConfigHelpers)

然后,执行 configure_package_config_file() 命令,此命令会将 Config.cmake.in 配置文件中的 @PACKAGE_INIT@ 变量替换为一个代码块,该代码块将设置的值转换为相对路径

include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION "lib/cmake/example"
  NO_SET_AND_CHECK_MACRO
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
)

MathFunctionsConfig.cmake 文件内容:

####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was Config.cmake.in                            ########

get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)

####################################################################################

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

第五步生成 MathFunctionsConfigVersion.cmake 文件,该文件被 find_package 所使用,用于记录所需库安装包的版本和兼容性。通过添加 write_basic_package_version_file() 命令生成此文件

write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  # 版本
  VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
  # 指示此版本或任何更高版本与请求的版本兼容
  COMPATIBILITY AnyNewerVersion
)

最后安装生成的配置文件:

install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake
  DESTINATION lib/cmake/MathFunctions
)

如果我们希望我们的项目也从构建目录中使用,我们只需要在顶层 CMakeLists.txt 的底部添加以下内容

export(EXPORT MathFunctionsTargets
  FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)

顶层 CMakeLists.txt 完整代码如下:

# 设置CMake版本最低要求
cmake_minimum_required(VERSION 3.15)

# 设置项目名称和版本
project(Tutorial VERSION 3.1)

# 指定 C++ 标准
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)

# add compiler warning flags just when building this project via
# the BUILD_INTERFACE genex
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
  "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
  "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)

# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
# 指定静态库和动态库的生成路径
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

# 生成共享库选项
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

# 生成一个头文件,传递 CMake 的一些设置到源代码
configure_file(TutorialConfig.h.in TutorialConfig.h)

# 添加编译子模块 MathFunctions library
add_subdirectory(MathFunctions)

# 添加源码文件和生成的目标文件的名称
add_executable(Tutorial main.cpp)

# 依赖共享库 MathFunctions
target_link_libraries(Tutorial PUBLIC MathFunctions)

# 添加头文件查找路径
# target_include_directories(Tutorial PUBLIC 
#                             "${PROJECT_BINARY_DIR}"
#                             "${PROJECT_SOURCE_DIR}/MathFunctions"
#                         )
target_include_directories(Tutorial PUBLIC 
                            "${PROJECT_BINARY_DIR}"
)

# enable dashboard scripting
include(CTest)

# does the application run
add_test(NAME Runs COMMAND Tutorial 25)

# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
  PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
  )

# define a function to simplify adding tests
function(do_test target arg result)
  add_test(NAME Comp${arg} COMMAND ${target} ${arg})
  set_tests_properties(Comp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result}
    )
endfunction()

# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is (-nan|nan|0)")
do_test(Tutorial 0.0001 "0.0001 is 0.01")

## Packaging an Installer
# 此模块将打包项目当前平台所需的任何运行时库
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set(CPACK_SOURCE_GENERATOR "TGZ")
include(CPack)

## 安装
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  DESTINATION include
)
install(EXPORT MathFunctionsTargets
  FILE MathFunctionsTargets.cmake
  DESTINATION lib/cmake/MathFunctions
)

include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
# 生成 cmake 配置文件
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
  INSTALL_DESTINATION "lib/cmake/example"
  NO_SET_AND_CHECK_MACRO
  NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
  # 版本
  VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
  # 指示此版本或任何更高版本与请求的版本兼容
  COMPATIBILITY AnyNewerVersion
)
install(FILES
  ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake
  DESTINATION lib/cmake/MathFunctions
)
# If we want our project to also be used from a build directory 
export(EXPORT MathFunctionsTargets
  FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)

MathFunctions/CMakeLists.txt 完整代码:

# add the library that runs
add_library(MathFunctions MathFunctions.cxx)

# 生成导出库的宏定义文件
# include(GenerateExportHeader)
# generate_export_header(MathFunctions)

# 将当前二进制目录添加到包含目录列表中
target_include_directories(MathFunctions
            # INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
            INTERFACE
            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
            $<INSTALL_INTERFACE:include>
)
# 通过 tutorial_compiler_flags 指定 MathFunctions 动态库的编译参数设置
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)

# should we use our own math functions
# 是否使用 USE_MYMATH 选项
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
  # 设置程序中使用到的宏定义
  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")

  # first we add the executable that generates the table
  add_executable(MakeTable MakeTable.cxx)
  # 通过 tutorial_compiler_flags 指定 MakeTable 可执行程序的编译参数设置
  target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)

  # add the command to generate the source code
  add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    DEPENDS MakeTable
    )

  # 添加静态库
  # library that just does sqrt
  add_library(SqrtLibrary STATIC
              mysqrt.cxx
              ${CMAKE_CURRENT_BINARY_DIR}/Table.h
              )

  # state that we depend on our binary dir to find Table.h
  target_include_directories(SqrtLibrary 
                             PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
                             )
    
  # state that SqrtLibrary need PIC(position independent code) when the default is shared libraries
  # 如果将一个没有位置独立代码(position independent code)的静态库与一个有位置独立代码的库组合在一起使用,
  # 就要显示设置目标的属性,否则会有链接错误
  set_target_properties(SqrtLibrary PROPERTIES
                        POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
                        )
  
  # 通过 tutorial_compiler_flags 指定 SqrtLibrary 静态库的编译参数设置
  target_link_libraries(SqrtLibrary PRIVATE tutorial_compiler_flags)
  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()

# define the symbol stating we are using the declspec(dllexport) when
# building on windows
# 声明dll导出宏定义
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")

# install rules
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
  list(APPEND installable_libs SqrtLibrary)
endif()

install(TARGETS ${installable_libs} 
        # generates a CMake file containing code to import all targets listed in the install command from the installation tree
        EXPORT MathFunctionsTargets
        DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)