CMake 秘籍(六)(2)

简介: CMake 秘籍(六)

CMake 秘籍(六)(1)https://developer.aliyun.com/article/1525057

它是如何工作的

这个配方展示了如何为共享库设置符号的可见性。最佳实践是默认隐藏所有符号,只明确暴露我们希望被库依赖者使用的那些符号。这通过两个步骤实现。首先,我们需要指示编译器隐藏符号。当然,不同的编译器将有不同的选项可用,直接在我们的CMakeLists.txt中手动设置这些将不是跨平台的。CMake 提供了一种设置符号可见性的健壮且跨平台的方法,即通过在共享库目标上设置两个属性:

  • CXX_VISIBILITY_PRESET hidden:这将隐藏所有符号,除非明确标记为其他。当使用 GNU 编译器时,这为目标添加了-fvisibility=hidden标志。
  • VISIBILITY_INLINES_HIDDEN 1:这将隐藏内联函数的符号。如果使用 GNU 编译器,这对应于-fvisibility-inlines-hidden

在 Windows 上,这是默认行为。实际上,回想一下,在前一个配方中,我们需要通过将WINDOWS_EXPORT_ALL_SYMBOLS属性设置为ON来覆盖它。

我们如何标记我们希望可见的符号?这是由预处理器决定的,因此我们需要提供预处理器宏,这些宏扩展为给定编译器在所选平台上将理解的可见性属性。再次,CMake 通过GenerateExportHeader.cmake模块文件来救援。该模块定义了generate_export_header函数,我们按如下方式调用它:

include(GenerateExportHeader)
generate_export_header(message-shared
  BASE_NAME "message"
  EXPORT_MACRO_NAME "message_EXPORT"
  EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
  DEPRECATED_MACRO_NAME "message_DEPRECATED"
  NO_EXPORT_MACRO_NAME "message_NO_EXPORT"
  STATIC_DEFINE "message_STATIC_DEFINE"
  NO_DEPRECATED_MACRO_NAME "message_NO_DEPRECATED"
  DEFINE_NO_DEPRECATED
  )

该函数生成包含所需预处理器宏的messageExport.h头文件。文件在目录${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}中生成,如通过EXPORT_FILE_NAME选项所请求。如果此选项留空,头文件将在当前二进制目录中生成。该函数的第一

  • BASE_NAME:这设置生成的头文件和宏的基本名称为传入的值。
  • EXPORT_MACRO_NAME:这设置导出宏的名称。
  • EXPORT_FILE_NAME:这设置生成的导出头文件的名称。
  • DEPRECATED_MACRO_NAME:这设置废弃宏的名称。这用于标记废弃代码,如果客户端使用它,编译器将发出废弃警告。
  • NO_EXPORT_MACRO_NAME:这设置不导出宏的名称。
  • STATIC_DEFINE:这是用于当也从相同源代码编译静态库时使用的宏的名称。
  • NO_DEPRECATED_MACRO_NAME:这设置用于排除编译中废弃代码的宏的名称。
  • DEFINE_NO_DEPRECATED:这指示 CMake 生成预处理器代码,以排除编译中的废弃代码。

在 GNU/Linux 上使用 GNU 编译器时,CMake 将生成以下messageExport.h导出头文件:

#ifndef message_EXPORT_H
#define message_EXPORT_H
#ifdef message_STATIC_DEFINE
#  define message_EXPORT
#  define message_NO_EXPORT
#else
#  ifndef message_EXPORT
#    ifdef message_shared_EXPORTS
        /* We are building this library */
#      define message_EXPORT __attribute__((visibility("default")))
#    else
        /* We are using this library */
#      define message_EXPORT __attribute__((visibility("default")))
#    endif
#  endif
#  ifndef message_NO_EXPORT
#    define message_NO_EXPORT __attribute__((visibility("hidden")))
#  endif
#endif
#ifndef message_DEPRECATED
#  define message_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef message_DEPRECATED_EXPORT
#  define message_DEPRECATED_EXPORT message_EXPORT message_DEPRECATED
#endif
#ifndef message_DEPRECATED_NO_EXPORT
#  define message_DEPRECATED_NO_EXPORT message_NO_EXPORT message_DEPRECATED
#endif
#if 1 /* DEFINE_NO_DEPRECATED */
#  ifndef message_NO_DEPRECATED
#    define message_NO_DEPRECATED
#  endif
#endif
#endif

我们可以通过在类和函数前加上message_EXPORT宏来向用户公开它们。通过在前面加上message_DEPRECATED宏可以实现废弃。

静态库由相同的源代码构建。然而,所有符号都应在静态档案中可见,并且从messageExport.h头文件的内容可以看出,message_STATIC_DEFINE宏来救援。一旦目标被声明,我们就将其设置为编译定义。静态库上的额外目标属性如下:

  • ARCHIVE_OUTPUT_NAME "message":这将确保库文件的名称只是 message,而不是 message-static。
  • DEBUG_POSTFIX "_sd":这将给定的后缀附加到库。这独特地将库标识为在Debug配置中的静态
  • RELEASE_POSTFIX "_s":这与前面的属性类似,但仅在目标在Release配置中构建时附加后缀给静态库。

还有更多内容

在构建共享库时隐藏内部符号是一种良好的实践。这意味着库的尺寸会缩小,因为你向用户暴露的内容少于库中实际拥有的内容。这定义了应用程序二进制接口(ABI),大多数情况下应该与应用程序编程接口(API)一致。这分为两个阶段进行:

  1. 我们使用适当的编译器标志。
  2. 我们使用预处理器变量(在我们的例子中是message_EXPORT)来标记要导出的符号。在编译时,这些符号(如类和函数)的隐藏将被解除。

静态库只是对象文件的存档。因此,首先将源代码编译成对象文件,然后存档器将它们捆绑成一个存档。这里没有 ABI 的概念:所有符号默认都是可见的,编译器的可见性标志不影响静态存档。然而,如果你打算从相同的源文件构建共享库和静态库,你需要一种方法来赋予message_EXPORT预处理器变量在代码中两种情况下出现的意义。这就是GenerateExportHeader.cmake模块的作用。它将定义一个包含所有逻辑的头文件,用于给出这个预处理器变量的正确定义。对于共享库,它将根据平台和编译器的组合提供所需的内容。请注意,意义也会根据我们是构建还是使用共享库而改变。幸运的是,CMake 为我们处理了这一点,无需进一步干预。对于静态库,它将扩展为一个空字符串,做我们期望的事情:什么都不做。

细心的读者会注意到,按照这里所示构建静态库和共享库实际上需要编译源代码两次。对于我们简单的例子来说,这不是一个昂贵的操作,但对于比我们例子稍大的项目来说,这显然可能会变得相当繁重。为什么我们选择这种方法而不是在第 3 个菜谱中展示的使用OBJECT库的方法,即“构建和链接静态和共享库”,在第一章“从简单的可执行文件到库”中?OBJECT库负责编译库的第一步:从源代码到对象文件。在这一步中,预处理器介入并评估message_EXPORT。由于OBJECT库的编译只发生一次,message_EXPORT要么被评估为与构建共享库或静态库兼容的值。因此,为了避免歧义,我们选择了更稳健的方法,即编译两次,让预处理器正确评估可见性变量。

关于动态共享对象、静态存档和符号可见性的更多详细信息,我们建议阅读这篇文章:people.redhat.com/drepper/dsohowto.pdf

导出你的目标

本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-10/recipe-03找到,并包含一个 C++示例。该食谱适用于 CMake 版本 3.6(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

我们可以想象我们的消息库在开源社区中取得了巨大的成功。人们非常喜欢它,并在自己的项目中使用它来将消息打印到屏幕上。用户特别喜欢每条打印的消息都有一个唯一标识符的事实。但用户也希望库在编译和安装到他们的系统后更容易被发现。本食谱将展示如何使用 CMake 导出我们的目标,以便使用 CMake 的其他项目可以轻松地获取它们。

准备工作

源代码与前一个食谱相比未更改,项目的结构如下:

.
├── cmake
│   └── messageConfig.cmake.in
├── CMakeLists.txt
├── src
│   ├── CMakeLists.txt
│   ├── hello-world.cpp
│   ├── Message.cpp
│   └── Message.hpp
└── tests
    ├── CMakeLists.txt
    └── use_target
        ├── CMakeLists.txt
        └── use_message.cpp

请注意,我们添加了一个包含messageConfig.cmake.in文件的cmake子目录。该文件将包含我们导出的目标。我们还添加了一个测试,以检查项目的安装和导出是否按预期工作。

如何操作

再次,根CMakeLists.txt文件与前一个食谱相比未更改。转到包含我们源文件的叶目录src

  1. 我们需要找到 UUID 库,我们可以重用之前食谱中使用的代码:
# Search for pkg-config and UUID
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
  pkg_search_module(UUID uuid IMPORTED_TARGET)
  if(TARGET PkgConfig::UUID)
    message(STATUS "Found libuuid")
    set(UUID_FOUND TRUE)
  endif()
endif()
  1. 接下来,我们设置我们的共享库目标并生成导出头文件,如前一个食谱所示:
add_library(message-shared SHARED "")
include(GenerateExportHeader)
generate_export_header(message-shared
  BASE_NAME "message"
  EXPORT_MACRO_NAME "message_EXPORT"
  EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
  DEPRECATED_MACRO_NAME "message_DEPRECATED"
  NO_EXPORT_MACRO_NAME "message_NO_EXPORT"
  STATIC_DEFINE "message_STATIC_DEFINE"
  NO_DEPRECATED_MACRO_NAME "message_NO_DEPRECATED"
  DEFINE_NO_DEPRECATED
  )
target_sources(message-shared
  PRIVATE
    ${CMAKE_CURRENT_LIST_DIR}/Message.cpp
  )
  1. 我们为目标设置PUBLICINTERFACE编译定义。注意后者使用$生成器表达式:
target_compile_definitions(message-shared
  PUBLIC
    $<$<BOOL:${UUID_FOUND}>:HAVE_UUID>
  INTERFACE
    $<INSTALL_INTERFACE:USING_message>
  )
  1. 接下来,设置包含目录。再次注意使用$$生成器表达式。我们将在后面对此进行评论:
target_include_directories(message-shared
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}>
    $<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}>
  )
  1. 我们通过列出链接库和目标属性来完成共享库目标。这些与前一个食谱中未更改:
target_link_libraries(message-shared
  PUBLIC
    $<$<BOOL:${UUID_FOUND}>:PkgConfig::UUID>
  )
set_target_properties(message-shared
  PROPERTIES
    POSITION_INDEPENDENT_CODE 1
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN 1
    SOVERSION ${PROJECT_VERSION_MAJOR}
    OUTPUT_NAME "message"
    DEBUG_POSTFIX "_d"
    PUBLIC_HEADER "Message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
    MACOSX_RPATH ON
  )

同样,对于message-static库目标也是如此:

  1. 我们首先声明它并列出其源文件:
add_library(message-static STATIC "")
target_sources(message-static
  PRIVATE
    ${CMAKE_CURRENT_LIST_DIR}/Message.cpp
  )
  1. 我们给出PUBLICINTERFACE编译定义,就像在前一个食谱中一样,但现在使用$生成器表达式:
target_compile_definitions(message-static
  PUBLIC
    message_STATIC_DEFINE
    $<$<BOOL:${UUID_FOUND}>:HAVE_UUID>
  INTERFACE
    $<INSTALL_INTERFACE:USING_message>
  )
  1. 我们使用与共享目标相同的命令列出包含目录:
target_include_directories(message-static
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}>
    $<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}>
  )
  1. 链接库和目标属性与前一个食谱相比未更改:
target_link_libraries(message-static
  PUBLIC
    $<$<BOOL:${UUID_FOUND}>:PkgConfig::UUID>
  )
set_target_properties(message-static
  PROPERTIES
    POSITION_INDEPENDENT_CODE 1
    ARCHIVE_OUTPUT_NAME "message"
    DEBUG_POSTFIX "_sd"
    RELEASE_POSTFIX "_s"
    PUBLIC_HEADER "Message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
  )
  1. 使用与前一个食谱中完全相同的命令生成可执行文件:
add_executable(hello-world_wDSO hello-world.cpp)
target_link_libraries(hello-world_wDSO
  PUBLIC
    message-shared
  )
# Prepare RPATH
file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
if(APPLE)
  set(_rpath "@loader_path/${_rel}")
else()
  set(_rpath "\$ORIGIN/${_rel}")
endif()
file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH)
set_target_properties(hello-world_wDSO
  PROPERTIES
    MACOSX_RPATH ON
    SKIP_BUILD_RPATH OFF
    BUILD_WITH_INSTALL_RPATH OFF
    INSTALL_RPATH "${message_RPATH}"
    INSTALL_RPATH_USE_LINK_PATH ON
  )
add_executable(hello-world_wAR hello-world.cpp)
target_link_libraries(hello-world_wAR
  PUBLIC
    message-static
  )

我们现在准备查看安装规则:

  1. 我们将所有目标的安装规则列在一起,因为 CMake 可以正确地将每个目标放置在适当的目的地。这次,我们添加了EXPORT关键字,以便 CMake 将为我们导出的目标生成一个导出的目标文件:
install(
  TARGETS
    message-shared
    message-static
    hello-world_wDSO
    hello-world_wAR
  EXPORT
    messageTargets
  ARCHIVE
    DESTINATION ${INSTALL_LIBDIR}
    COMPONENT lib
  RUNTIME
    DESTINATION ${INSTALL_BINDIR}
    COMPONENT bin
  LIBRARY
    DESTINATION ${INSTALL_LIBDIR}
    COMPONENT lib
  PUBLIC_HEADER
    DESTINATION ${INSTALL_INCLUDEDIR}/message
    COMPONENT dev
  )
  1. 自动生成的导出目标文件名为messageTargets.cmake,我们需要为它明确指定安装规则。该文件的目的地是在根CMakeLists.txt文件中定义的INSTALL_CMAKEDIR
install(
  EXPORT
    messageTargets
  NAMESPACE
    "message::"
  DESTINATION
    ${INSTALL_CMAKEDIR}
  COMPONENT
    dev
  )
  1. 最后,我们需要生成适当的 CMake 配置文件。这些文件将确保下游项目能够找到由 message 库导出的目标。为此,我们首先包含CMakePackageConfigHelpers.cmake标准模块:
include(CMakePackageConfigHelpers)
  1. 我们让 CMake 生成一个包含我们库版本信息的文件:
write_basic_package_version_file(
  ${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
VERSION ${PROJECT_VERSION}
  COMPATIBILITY SameMajorVersion
  )
  1. 使用configure_package_config_file函数,我们生成实际的 CMake 配置文件。这是基于模板cmake/messageConfig.cmake.in文件:
configure_package_config_file(
  ${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
  INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
  )
  1. 作为最后一步,我们为这两个自动生成的配置文件设置安装规则:
install(
  FILES
    ${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
    ${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
  DESTINATION
    ${INSTALL_CMAKEDIR}
  )

cmake/messageConfig.cmake.in模板文件的内容是什么?该文件的头部作为对其用户的文档。让我们看看实际的 CMake 命令:

  1. 我们从一个占位符开始,该占位符将被configure_package_config_file命令替换:
@PACKAGE_INIT@
  1. 我们包含目标的自动生成的导出文件:
include("${CMAKE_CURRENT_LIST_DIR}/messageTargets.cmake")
  1. 然后我们使用 CMake 提供的check_required_components函数检查静态库、共享库以及两个“Hello, World”可执行文件是否存在:
check_required_components(
  "message-shared"
  "message-static"
"message-hello-world_wDSO"
  "message-hello-world_wAR"
  )
  1. 我们检查目标PkgConfig::UUID是否存在。如果不存在,我们再次搜索 UUID 库,但仅限于不在 Windows 系统上时:
if(NOT WIN32)
  if(NOT TARGET PkgConfig::UUID)
    find_package(PkgConfig REQUIRED QUIET)
    pkg_search_module(UUID REQUIRED uuid IMPORTED_TARGET)
  endif()
endif()

让我们尝试一下:

$ mkdir -p build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/Software/recipe-03 ..
$ cmake --build . --target install

安装树具有以下结构:

$HOME/Software/recipe-03/
├── bin
│   ├── hello-world_wAR
│   └── hello-world_wDSO
├── include
│   └── message
│       ├── messageExport.h
│       └── Message.hpp
├── lib64
│   ├── libmessage_s.a
│   ├── libmessage.so -> libmessage.so.1
│   └── libmessage.so.1
└── share
    └── cmake
        └── recipe-03
            ├── messageConfig.cmake
            ├── messageConfigVersion.cmake
            ├── messageTargets.cmake
            └── messageTargets-release.cmake

您会注意到出现了一个share子目录,其中包含了所有我们要求 CMake 自动生成的文件。从现在开始,使用我们的message库的用户可以在他们自己的CMakeLists.txt文件中通过设置message_DIRCMake 变量指向安装树中的share/cmake/message目录来定位message库:

find_package(message 1 CONFIG REQUIRED)

它是如何工作的

这个配方涵盖了很多内容;让我们来理解它。CMake 目标是对构建系统将要执行的操作非常有用的抽象。使用PRIVATEPUBLICINTERFACE关键字,我们可以设置同一项目内的目标将如何相互作用。实际上,这让我们定义了目标 A 的依赖项将如何影响依赖于 A 的目标 B。当其他项目想要将一个库作为依赖项使用时,可以充分体会到这种机制的强大之处。如果库维护者提供了适当的 CMake 配置文件,那么所有依赖项都可以很容易地用很少的 CMake 命令来解决。

这个问题可以通过遵循message-staticmessage-sharedhello-world_wDSOhello-world_wAR目标的配方中概述的模式来解决。我们将单独分析message-shared目标的 CMake 命令,但这里的讨论是通用的:

  1. 在项目构建中生成目标并布置其依赖项。对于message-shared,链接 UUID 库是一个PUBLIC要求,因为它将用于构建项目内的目标以及下游项目中的目标。编译定义和包含目录需要在PUBLICINTERFACE级别设置。其中一些将用于构建项目内的目标,而其他一些仅与下游项目相关。此外,其中一些仅在项目安装后才相关。这就是$$生成器表达式发挥作用的地方。只有message库外部的下游目标才需要这些,也就是说,只有在目标安装后它们才会变得可见。在我们的示例中,以下适用:
  • $仅在message-shared库目标在我们的项目内使用时,才会扩展为${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}
  • $仅在message-shared库目标作为另一个构建树内的导出目标使用时,才会扩展为${INSTALL_INCLUDEDIR}
  1. 描述目标的安装规则,包括 CMake 将生成的EXPORT文件的名称。
  2. 描述 CMake 生成的导出文件的安装规则。messageTargets.cmake文件将安装到INSTALL_CMAKEDIR。目标导出文件的安装规则的NAMESPACE选项将在目标名称前加上给定的字符串。这有助于避免来自不同项目的目标之间的潜在名称冲突。INSTALL_CMAKEDIR变量在根CMakeLists.txt文件中设置:
if(WIN32 AND NOT CYGWIN)
  set(DEF_INSTALL_CMAKEDIR CMake)
else()
  set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
endif()
set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")

我们CMakeLists.txt的最后一部分生成配置文件。在包含CMakePackageConfigHelpers.cmake模块之后,这分为三个步骤完成:

  1. 我们调用write_basic_package_version_file CMake 函数来生成一个包版本文件。宏的第一个参数是版本文件的路径:messageConfigVersion.cmake。然后,我们使用PROJECT_VERSION CMake 变量以 Major.Minor.Patch 格式指定版本。还可以指定与库的新版本的兼容性。在我们的例子中,我们保证当库具有相同的 major 版本时兼容,因此使用了SameMajorVersion参数。
  2. 接下来,我们配置模板文件messageConfig.cmake.in;该文件位于项目的cmake子目录中。
  3. 最后,我们为新生成的文件设置安装规则。两者都将安装在INSTALL_CMAKEDIR下。

还有更多内容

消息库的客户端现在非常满意,因为他们终于可以在自己的系统上安装该库,并且让 CMake 为他们发现它,而无需对其自己的CMakeLists.txt进行太多修改。

find_package(message VERSION 1 REQUIRED)

客户端现在可以按以下方式配置他们的项目:

$ cmake -Dmessage_DIR=/path/to/message/share/cmake/message ..

我们示例中包含的测试展示了如何检查目标的安装是否按计划进行。查看tests文件夹的结构,我们注意到use_target子目录:

tests/
├── CMakeLists.txt
└── use_target
    ├── CMakeLists.txt
    └── use_message.cpp

该目录包含一个使用导出目标的小型项目。有趣的部分在于指定测试的CMakeLists.txt文件:

  1. 我们测试小型项目是否可以配置为使用已安装的库。这是使用目标测试夹具的设置步骤,如第四章,创建和运行测试,食谱 10,使用测试夹具所示:
add_test(
  NAME use-target_configure
  COMMAND
    ${CMAKE_COMMAND} -H${CMAKE_CURRENT_LIST_DIR}/use_target
                     -B${CMAKE_CURRENT_BINARY_DIR}/build_use-target
                     -G${CMAKE_GENERATOR}
                     -Dmessage_DIR=${CMAKE_INSTALL_PREFIX}/${
                     INSTALL_CMAKEDIR}
                     -DCMAKE_BUILD_TYPE=$<CONFIGURATION>
  )
set_tests_properties(use-target_configure
  PROPERTIES
    FIXTURES_SETUP use-target
  )
  1. 我们测试小型项目是否可以构建:
add_test(
  NAME use-target_build
  COMMAND
    ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/build_use-target
                     --config $<CONFIGURATION>
  )
set_tests_properties(use-target_build
  PROPERTIES
    FIXTURES_REQUIRED use-target
  )
  1. 小型项目的测试也会运行:
set(_test_target)
if(MSVC)
  set(_test_target "RUN_TESTS")
else()
  set(_test_target "test")
endif()
add_test(
  NAME use-target_test
  COMMAND
    ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/build_use-target
                     --target ${_test_target}
                     --config $<CONFIGURATION>
  )
set_tests_properties(use-target_test
  PROPERTIES
    FIXTURES_REQUIRED use-target
  )
unset(_test_target)
  1. 最后,我们拆卸夹具:
add_test(
  NAME use-target_cleanup
  COMMAND
    ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/build_use-target
  )
set_tests_properties(use-target_cleanup
  PROPERTIES
    FIXTURES_CLEANUP use-target
  )

请注意,这些测试只能在项目安装之后运行。

安装超级构建

本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-10/recipe-04找到,并包含一个 C++示例。该食谱适用于 CMake 版本 3.6(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。

我们的示例message库取得了巨大成功,许多其他程序员都在使用它,并且非常满意。您也想在自己的项目中使用它,但不确定如何正确管理依赖关系。您可以将message库的源代码与您自己的代码一起打包,但如果该库已经在系统上安装了呢?第八章,超级构建模式,展示了这是一个典型的超级构建场景,但您不确定如何安装这样的项目。本食谱将引导您了解安装超级构建的细节。

CMake 秘籍(六)(3)https://developer.aliyun.com/article/1525060

相关文章
|
5月前
|
编译器 Linux C语言
CMake 秘籍(二)(2)
CMake 秘籍(二)
45 2
|
5月前
|
编译器 Shell
CMake 秘籍(八)(3)
CMake 秘籍(八)
35 2
|
5月前
|
编译器 Shell 开发工具
CMake 秘籍(八)(5)
CMake 秘籍(八)
32 2
|
5月前
|
Linux iOS开发 C++
CMake 秘籍(六)(3)
CMake 秘籍(六)
32 1
|
5月前
|
编译器 开发工具 git
CMake 秘籍(八)(1)
CMake 秘籍(八)
23 1
|
5月前
|
Linux C++ iOS开发
CMake 秘籍(三)(4)
CMake 秘籍(三)
31 1
|
5月前
|
编译器 Linux 开发工具
CMake 秘籍(四)(2)
CMake 秘籍(四)
20 0
|
5月前
|
Linux C++ iOS开发
CMake 秘籍(四)(1)
CMake 秘籍(四)
23 0
|
5月前
|
Linux C++ iOS开发
CMake 秘籍(四)(3)
CMake 秘籍(四)
17 0
|
5月前
|
编译器 开发工具
CMake 秘籍(八)(2)
CMake 秘籍(八)
29 0