💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 8.5 使用超级构建支持项目 **NOTE**:*此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-8/recipe-05 中找到,其中有一个C++示例。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。* `ExternalProject`和`FetchContent`是CMake库中两个非常强大的工具。经过前面的示例,我们应该相信超级构建方法,在管理复杂依赖关系的项目时是多么有用。目前为止,我们已经展示了如何使用`ExternalProject`来处理以下问题: * 存储在源树中的源 * 从在线服务器上,检索/获取可用的存档资源 前面的示例展示了,如何使用`FetchContent`处理开源Git存储库中可用的依赖项。本示例将展示,如何使用`ExternalProject`达到同样的效果。最后,将介绍一个示例,该示例将在第10章第4节中重用。 ## 准备工作 这个超级构建的源代码树现在应该很熟悉了: ```shell . ├── CMakeLists.txt ├── external │ └── upstream │ ├── CMakeLists.txt │ └── message │ └── CMakeLists.txt └── src ├── CMakeLists.txt └── use_message.cpp ``` 根目录有一个`CMakeLists.txt`,我们知道它会配合超级构建。子目录`src`和`external`中是我们自己的源代码,CMake指令需要满足对消息库的依赖,我们将在本例中构建消息库。 ## 具体实施 目前为止,建立超级构建的过程应该已经很熟悉了。让我们再次看看必要的步骤,从根目录的`CMakeLists.txt`开始: 1. 声明一个C++11项目,并对项目构建类型的默认值进行设置。 ```cmake 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`管理所有子项目的布局: ```cmake set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects) ``` 3. 我们设置了`STAGED_INSTALL_PREFIX`。与之前一样,这个位置将作为依赖项的构建树中的安装目录: ```cmake set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage) message(STATUS "${PROJECT_NAME} staged install: ${STAGED_INSTALL_PREFIX}") ``` 4. 将` external/upstream`作为子目录添加: ```cmake add_subdirectory(external/upstream) ``` 5. 添加`ExternalProject_Add`,这样我们的项目也将由超级构建管理: ```cmake 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/upstream`的`CMakeLists.txt`中只包含一条命令: ```cmake add_subdirectory(message) ``` 跳转到`message`文件夹,我们会看到对消息库的依赖的常用命令: 1. 首先,调用`find_package`找到一个合适版本的库: ```cmake find_package(message 1 CONFIG QUIET) ``` 2. 如果找到,会通知用户,并添加一个虚拟`INTERFACE`库: ```cmake 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`: ```cmake message(STATUS "Suitable message could not be located, Building message instead.") ``` 4. 该项目托管在一个公共Git库中,使用`GIT_TAG`选项指定下载哪个分支。和之前一样,将`UPDATE_COMMAND`选项置为空: ```cmake include(ExternalProject) ExternalProject_Add(message_external GIT_REPOSITORY https://github.com/dev-cafe/message.git GIT_TAG master UPDATE_COMMAND "" ``` 5. 外部项目使用CMake配置和构建,传递必要的构建选项: ```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. 项目安装后进行测试: ```cmake TEST_AFTER_INSTALL 1 ``` 7. 我们不希望看到下载进度,也不希望在屏幕上报告配置、构建和安装信息,所以选择关闭`ExternalProject_Add`: ```cmake DOWNLOAD_NO_PROGRESS 1 LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 ) ``` 8. 为了确保子项目在超级构建的其余部分中是可见的,我们设置了`message_DIR`目录: ```cmake 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 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. 项目需要消息库: ```cmake 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. 声明一个可执行目标,并将其链接到消息动态库: ```cmake 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组合在一起。