返回介绍

8.5 使用超级构建支持项目

发布于 2025-05-06 21:45:58 字数 6519 浏览 0 评论 0 收藏

NOTE : 此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-8/recipe-05 中找到,其中有一个 C++示例。该示例在 CMake 3.5 版(或更高版本) 中是有效的,并且已经在 GNU/Linux、macOS 和 Windows 上进行过测试。

ExternalProjectFetchContent 是 CMake 库中两个非常强大的工具。经过前面的示例,我们应该相信超级构建方法,在管理复杂依赖关系的项目时是多么有用。目前为止,我们已经展示了如何使用 ExternalProject 来处理以下问题:

  • 存储在源树中的源
  • 从在线服务器上,检索/获取可用的存档资源

前面的示例展示了,如何使用 FetchContent 处理开源 Git 存储库中可用的依赖项。本示例将展示,如何使用 ExternalProject 达到同样的效果。最后,将介绍一个示例,该示例将在第 10 章第 4 节中重用。

准备工作

这个超级构建的源代码树现在应该很熟悉了:

.
├── CMakeLists.txt
├── external
│    └── upstream
│        ├── CMakeLists.txt
│        └── message
│            └── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── use_message.cpp

根目录有一个 CMakeLists.txt ,我们知道它会配合超级构建。子目录 srcexternal 中是我们自己的源代码,CMake 指令需要满足对消息库的依赖,我们将在本例中构建消息库。

具体实施

目前为止,建立超级构建的过程应该已经很熟悉了。让我们再次看看必要的步骤,从根目录的 CMakeLists.txt 开始:

  1. 声明一个 C++11 项目,并对项目构建类型的默认值进行设置。
    cmake_minimum_required(VERSION 3.6 FATAL_ERROR)
    ​
    project(recipe-05 LANGUAGES CXX)
    ​
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    ​
    if(NOT DEFINED CMAKE_BUILD_TYPE OR "${CMAKE_BUILD_TYPE}" STREQUAL "")
        set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
    endif()
    ​
    message(STATUS "Build type set to ${CMAKE_BUILD_TYPE}")
  2. 设置 EP_BASE 目录属性。这将固定 ExternalProject 管理所有子项目的布局:
    set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)
  3. 我们设置了 STAGED_INSTALL_PREFIX 。与之前一样,这个位置将作为依赖项的构建树中的安装目录:
    set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage)
    message(STATUS "${PROJECT_NAME} staged install: ${STAGED_INSTALL_PREFIX}")
  4. external/upstream 作为子目录添加:
    add_subdirectory(external/upstream)
  5. 添加 ExternalProject_Add ,这样我们的项目也将由超级构建管理:
    include(ExternalProject)
    ExternalProject_Add(${PROJECT_NAME}_core
      DEPENDS
          message_external
      SOURCE_DIR
          ${CMAKE_CURRENT_SOURCE_DIR}/src
      CMAKE_ARGS
        -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
        -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
        -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
        -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
        -DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
        -Dmessage_DIR=${message_DIR}
        CMAKE_CACHE_ARGS
        -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
        -DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH}
      BUILD_ALWAYS
          1
      INSTALL_COMMAND
          ""
      )

external/upstreamCMakeLists.txt 中只包含一条命令:

add_subdirectory(message)

跳转到 message 文件夹,我们会看到对消息库的依赖的常用命令:

  1. 首先,调用 find_package 找到一个合适版本的库:
    find_package(message 1 CONFIG QUIET)
  2. 如果找到,会通知用户,并添加一个虚拟 INTERFACE 库:
    get_property(_loc TARGET message::message-shared PROPERTY LOCATION)
    message(STATUS "Found message: ${_loc} (found version ${message_VERSION})")
    add_library(message_external INTERFACE) # dummy
  3. 如果没有找到,再次通知用户并继续使用 ExternalProject_Add :
    message(STATUS "Suitable message could not be located, Building message instead.")
  4. 该项目托管在一个公共 Git 库中,使用 GIT_TAG 选项指定下载哪个分支。和之前一样,将 UPDATE_COMMAND 选项置为空:
    include(ExternalProject)
    ExternalProject_Add(message_external
      GIT_REPOSITORY
          https://github.com/dev-cafe/message.git
      GIT_TAG
          master
      UPDATE_COMMAND
          ""
  5. 外部项目使用 CMake 配置和构建,传递必要的构建选项:
      CMAKE_ARGS
        -DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
        -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
        -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
        -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
        -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
        -DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
      CMAKE_CACHE_ARGS
          -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
  6. 项目安装后进行测试:
        TEST_AFTER_INSTALL
            1
  7. 我们不希望看到下载进度,也不希望在屏幕上报告配置、构建和安装信息,所以选择关闭 ExternalProject_Add :
      DOWNLOAD_NO_PROGRESS
          1
      LOG_CONFIGURE
          1
      LOG_BUILD
          1
      LOG_INSTALL
          1
    )
  8. 为了确保子项目在超级构建的其余部分中是可见的,我们设置了 message_DIR 目录:
    if(WIN32 AND NOT CYGWIN)
        set(DEF_message_DIR ${STAGED_INSTALL_PREFIX}/CMake)
    else()
        set(DEF_message_DIR ${STAGED_INSTALL_PREFIX}/share/cmake/message)
    endif()
    ​
    file(TO_NATIVE_PATH "${DEF_message_DIR}" DEF_message_DIR)
    set(message_DIR ${DEF_message_DIR}
    CACHE PATH "Path to internally built messageConfig.cmake" FORCE)

最后,来看一下 src 目录上的 CMakeLists.txt

  1. 同样,声明一个 C++11 项目:
    cmake_minimum_required(VERSION 3.6 FATAL_ERROR)
    project(recipe-05_core
    LANGUAGES CXX
    )
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
  2. 项目需要消息库:
    find_package(message 1 CONFIG REQUIRED)
    get_property(_loc TARGET message::message-shared PROPERTY LOCATION)
    message(STATUS "Found message: ${_loc} (found version ${message_VERSION})")
  3. 声明一个可执行目标,并将其链接到消息动态库:
    add_executable(use_message use_message.cpp)
    ​
    target_link_libraries(use_message
      PUBLIC
          message::message-shared
      )

工作原理

示例展示了 ExternalProject_Add 的一些新选项:

  1. GIT_REPOSITORY :这可以用来指定包含依赖项源的存储库的 URL。CMake 还可以使用其他版本控制系统,比如 CVS (CVS_REPOSITORY)、SVN (SVN_REPOSITORY) 或 Mercurial (HG_REPOSITORY)。
  2. GIT_TAG :默认情况下,CMake 将检出给定存储库的默认分支。然而,最好依赖于一个稳定的版本。这可以通过这个选项指定,它可以接受 Git 将任何标识符识别为“版本”信息,例如:Git 提交 SHA、Git 标记或分支名称。CMake 所理解的其他版本控制系统也可以使用类似的选项。
  3. TEST_AFTER_INSTALL :依赖项很可能有自己的测试套件,您可能希望运行测试套件,以确保在超级构建期间一切顺利。此选项将在安装步骤之后立即运行测试。

ExternalProject_Add 可以理解的其他测试选项如下:

  • TEST_BEFORE_INSTALL :将在安装步骤之前运行测试套件
  • TEST_EXCLUDE_FROM_MAIN :可以从测试套件中,删除对外部项目的主要目标的依赖

这些选项都假定外部项目使用 CTest 管理测试。如果外部项目不使用 CTest 来管理测试,我们可以通过 TEST_COMMAND 选项来执行测试。

即使是为属于自己项目的模块引入超级构建模式,也需要引入额外的层,重新声明小型 CMake 项目,并通过 ExternalProject_Add 显式地传递配置设置。引入这个附加层的好处是,清晰地分离了变量和目标范围,这可以帮助管理由多个组件组成的项目中的复杂性、依赖性和名称空间,这些组件可以是内部的,也可以是外部的,并由 CMake 组合在一起。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。