cmake:将 FetchContent 与 find_package() 集成

mal*_*lat 3 cmake

我试图理解以下文档:

所以我尝试了一个简单的 cmakelists.txt 文件,例如:

% cat CMakeLists.txt
cmake_minimum_required(VERSION 3.24)
project(p)

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
  FIND_PACKAGE_ARGS NAMES gtest
)

# This will try calling find_package()
FetchContent_MakeAvailable(googletest)
find_package(GTest)
message(${GTest_DIR})
Run Code Online (Sandbox Code Playgroud)

如果我运行它,我会得到以下结果:

$ cmake ..
[...]
/usr/lib/x86_64-linux-gnu/cmake/GTest
Run Code Online (Sandbox Code Playgroud)

由于某种原因,find_package(GTest)仍然尝试检查系统中的 GTest,而不是 FetchContent 代码块中声明的 GTest。

我从 cmake 文档中误解了什么?我认为该功能FetchContent 正是针对用户无法使用的情况,ExternalProject因为它find_package.

以供参考:

% cmake --version
cmake version 3.24.1
Run Code Online (Sandbox Code Playgroud)

Tsy*_*rev 5

以非常简单的形式:该选项FIND_PACKAGE_ARGS使用FetchContent的结果find_package。但要让 makefind_package使用 的结果FetchContent,您需要传递另一个选项:OVERRIDE_FIND_PACKAGE


FetchContentfind_package旨在引入一个 3d 方项目,以便在主项目的构建过程中使用它。然而,这两种方法使用不同的机制:

  1. FetchContent从源代码构建 3d 方项目以及主项目,但是
  2. find_package适用于已安装的 3d 方项目。

FetchContentCMake 提供了和之间集成的两个“方向” find_package;每个“方向”都有其自己独立的目的。

1. 编写一个新项目,在源和安装中均消耗 3d 方项目。

假设您编写一个项目,该项目使用一些 3d 方项目。假设您想支持两种变体:

  1. 使用 3d 方项目的已安装变体(如果已可用),或者
  2. 从源代码中与主项目一起构建 3d 方项目。

参数FIND_PACKAGE_ARGSforFetchContent_Declare告诉 CMake 用于find_package查找已安装的 3d 方项目,并且如果可用,请勿从源构建它。

仅当主项目通过接口使用 3d 方项目(3d 方项目源和find_package.

例如,GTest 的源在命名空间中创建ALIAS 目标GTest(如GTest::gtest_main),并使用相同的命名空间find_package(GTest) 创建IMPORTED 目标。因此,外部项目可以安全地链接GTest::gtest_main,并且只要 GTest 已经安装或正在从源代码构建,这就会起作用。

2. 使现有项目能够与从源代码构建的 3d 方项目一起使用

假设您正在创建一个超级项目,它结合了几个已经存在的子项目。该子项目之一find_package为 3d 方项目执行并使用其结果。假设您希望从源代码构建 3d 方项目。

所使用的大多数特定于项目的脚本find_package仅适用于已安装的软件包,不适用于当前正在构建的软件包。需要一些特殊的力量..

忽略“find_package”

参数OVERRIDE_FIND_PACKAGEforFetchContent_Declare意味着忽略进一步的find_package调用,因为 3d 方项目是从源构建的。

如果子项目仅使用来自 的结果find_package(也可以从 3d 方项目的源中获得),则此方法有效。GTest::gtest_main例如,如果子项目使用目标进行链接,它将起作用。但如果子项目使用GTEST_MAIN_LIBRARIES变量,而 GTest 源中未设置该变量,则它将不起作用。

更改“find_package”执行的脚本

但是,如果子项目使用部分接口,该部分接口只能从find_package(及其特定于项目的脚本)获得,但不能从 3D 方项目的源中获得,该怎么办?在这种情况下,可以创建一个假脚本,该脚本将由find_package,该脚本将代替正常脚本

file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-extra.cmake
[=[
  set(GTEST_MAIN_LIBRARIES GTest::gtest_main)
]=])
Run Code Online (Sandbox Code Playgroud)

[=[和之间的线]=]成为创建的脚本的一部分,它定义了 GTEST_MAIN_LIBRARIES适合子项目中链接的变量。

(请注意,仅当为 指定参数时,该文件gtest-extra.cmake才有意义。)OVERRIDE_FIND_PACKAGEFetchContent_Declare