第 10 步:查找依赖项¶
在 C/C++ 软件开发中,管理构建依赖项始终是现代开发者面临的最高优先级挑战之一。CMake 提供了一个广泛的工具集,用于发现和验证各种类型的依赖项。
但是,对于正确打包的项目,无需使用这些高级工具。如今,许多流行的库和实用程序项目都会生成正确的安装树,就像我们在第 9 步中设置的那样,这些安装树易于集成到 CMake 中。
在这种最佳场景下,我们只需要find_package() 将依赖项导入到我们的项目中。
背景¶
CMake 中用于发现依赖项的命令共有五个,前四个是:
find_file()查找并报告指定文件的完整路径,这是
find命令中最灵活的。find_library()查找并报告静态归档或共享对象的完整路径,适用于与
target_link_libraries()一起使用。find_path()查找并报告*包含*文件的目录的完整路径。这通常与
target_include_directories()结合用于查找头文件。find_program()查找并报告程序的可调用名称或路径。通常与
execute_process()或add_custom_command()结合使用。
这些命令应被视为“备用”选项,当主要的查找命令不适用时使用。主要的查找命令是find_package()。它使用全面的内置启发式方法和上游提供的打包文件,为请求的依赖项提供最佳接口。
练习 1 - 使用find_package()¶
find_package() 使用的搜索路径和行为在其文档中有详细描述,但此处过于冗长无法复制。可以肯定地说,它搜索了众所周知、鲜为人知、晦涩难懂以及用户提供的位置,试图找到满足其要求的包。
find_package(ForeignLibrary)
使用find_package() 的最佳方法是确保所有依赖项在构建之前都已安装到单个安装树中,然后通过CMAKE_PREFIX_PATH变量将该安装树的位置告知find_package()。
注意
构建和安装依赖项本身可能是一项繁重的工作。尽管本教程会为说明目的这样做,但**强烈**建议使用包管理器进行项目本地依赖项管理。
find_package() 除了要查找的包之外,还接受几个参数。最值得注意的是:
一个位置参数
<version>,用于描述要与包的配置版本文件进行比较的版本。这应该谨慎使用,最好通过包管理器控制正在安装的依赖项的版本,而不是可能因无伤大碍的版本更新而中断构建。如果已知该包依赖于旧版本的依赖项,则可以使用版本要求。
REQUIRED用于非可选依赖项,如果找不到,则应中止构建。QUIET用于可选依赖项,如果找不到,则不应向用户报告任何内容。
find_package() 通过<PackageName>_FOUND变量报告其结果,对于找到和未找到的包,该变量将分别设置为真或假值。
目标¶
将外部安装的测试框架集成到 Tutorial 项目中。
有用资源¶
要编辑的文件¶
TutorialProject/CMakePresets.jsonTutorialProject/Tests/CMakeLists.txtTutorialProject/Tests/TestMathFunctions.cxx
开始¶
与前面的步骤不同,Step10文件夹的组织方式也不同。我们需要编辑的教程项目位于Step10/TutorialProject下。现在存在另一个项目SimpleTest,以及一个部分填充的安装树,我们将在后续练习中使用它。您不需要为这个练习编辑这些其他目录中的任何内容,所有TODO和解决方案步骤都适用于TutorialProject。
SimpleTest包提供了两个有用的构造:要链接到测试二进制文件的SimpleTest::SimpleTest目标,以及用于自动将测试添加到 CTest 的simpletest_discover_tests函数。
与其他测试框架类似,simpletest_discover_tests只需要传入包含测试的可执行目标的名称。
simpletest_discover_tests(MyTestExe)
TestMathFunctions.cxx文件已更新,以采用SimpleTest框架,类似于 GoogleTest 或 Catch2。请执行TODO 1到TODO 5以使用新的测试框架。
注意
不言而喻,SimpleTest是一个非常糟糕的测试框架,它只是表面上像一个功能性测试库。尽管本教程中的大部分 CMake 代码可以在其他项目中 unaltered 使用,但在本教程之外,您不应使用SimpleTest,也不应尝试从其提供的 CMake 代码中学习。
构建并运行¶
首先,我们必须安装SimpleTest框架。导航到Help/guide/Step10/SimpleTest目录并运行以下命令:
cmake --preset tutorial
cmake --install build
注意
该SimpleTest预设配置了安装SimpleTest所需的一切,以供教程使用。由于原因超出了本教程的范围,因此无需为SimpleTest构建或提供任何其他配置。
我们可以看到Step10/install目录现在已由SimpleTest头文件和包文件填充。
现在,我们可以像往常一样配置和构建 Tutorial 项目,导航到Help/guide/Step10/TutorialProject并运行:
cmake --preset tutorial
cmake --build build
通过运行 CTest 测试来验证SimpleTest框架是否已正确使用。
解决方案¶
首先,我们调用find_package()来发现SimpleTest包。我们使用REQUIRED来实现这一点,因为没有SimpleTest,测试就无法构建。
TODO 1 点击显示/隐藏答案
find_package(SimpleTest REQUIRED)
接下来,我们将SimpleTest::SimpleTest目标添加到TestMathFunctions中。
TODO 2 点击显示/隐藏答案
target_link_libraries(TestMathFunctions
PRIVATE
MathFunctions
SimpleTest::SimpleTest
)
现在,我们可以用对simpletest_discover_tests的调用替换我们的测试描述代码。
TODO 3 点击显示/隐藏答案
simpletest_discover_tests(TestMathFunctions)
我们通过将安装树添加到CMAKE_PREFIX_PATH来确保find_package()可以发现SimpleTest。
TODO 4 点击显示/隐藏答案
"cacheVariables": {
"CMAKE_PREFIX_PATH": "${sourceParentDir}/install",
"TUTORIAL_USE_STD_SQRT": "OFF",
"TUTORIAL_ENABLE_IPO": "OFF"
}
最后,我们通过删除占位符并包含相应的头文件来更新测试,使其使用SimpleTest提供的宏。
TODO 5 点击显示/隐藏答案
#include <MathFunctions.h>
#include <SimpleTest.h>
TEST("add")
{
练习 2 - 传递性依赖¶
库经常相互构建。多媒体应用程序可能依赖于提供各种容器格式支持的库,而该库又可能依赖于一个或多个其他库来进行压缩算法。
我们需要在安装树中的包配置文件中表达这些传递性需求。我们通过CMakeFindDependencyMacro模块来实现这一点,该模块提供了一种安全机制,供已安装的包递归地发现彼此。
include(CMakeFindDependencyMacro)
find_dependency(zlib)
find_dependency()还会转发来自顶级find_package()调用的参数。如果find_package()使用QUIET或REQUIRED调用,find_dependency()也将使用QUIET和/或REQUIRED。
目标¶
向SimpleTest添加一个依赖项,并确保依赖于SimpleTest的包也能发现此传递性依赖。
有用资源¶
要编辑的文件¶
SimpleTest/CMakeLists.txtSimpleTest/cmake/SimpleTestConfig.cmake
开始¶
在此步骤中,我们仅编辑SimpleTest项目。传递性依赖项TransitiveDep是一个虚拟依赖项,不提供任何行为。但是 CMake 不知道这一点,如果 CMake 找不到所有必需的依赖项,TutorialProject测试将无法配置和构建。
TransitiveDep包已安装到Step10/install树中。我们无需像SimpleTest那样安装它。
完成TODO 6到TODO 8。
构建并运行¶
我们需要重新安装 SimpleTest 框架。导航到Help/guide/Step10/SimpleTest目录并运行与之前相同的命令。
cmake --preset tutorial
cmake --install build
现在,我们可以重新配置和重建TutorialProject,导航到Help/guide/Step10/TutorialProject并执行常规步骤。
cmake --preset tutorial
cmake --build build
如果构建成功,我们很可能已成功传播了传递性依赖项。通过在TutorialProject的CMakeCache.txt中搜索名为TransitiveDep_DIR的条目来验证这一点。这表明TutorialProject搜索并找到了TransitiveDep,即使它没有直接要求它。
解决方案¶
首先,我们调用find_package()来发现TransitiveDep包。我们使用REQUIRED来验证我们已找到TransitiveDep。
TODO 6 点击显示/隐藏答案
find_package(TransitiveDep REQUIRED)
接下来,我们将TransitiveDep::TransitiveDep目标添加到SimpleTest中。
TODO 7 点击显示/隐藏答案
target_link_libraries(SimpleTest
INTERFACE
TransitiveDep::TransitiveDep
)
注意
如果此时我们构建了TutorialProject,我们预计配置将失败,因为TransitiveDep::TransitiveDep目标在TutorialProject中不可用。
最后,我们在SimpleTest包配置文件中包含CMakeFindDependencyMacro并调用find_dependency(),以传播传递性依赖项。
TODO 8 点击显示/隐藏答案
include(CMakeFindDependencyMacro)
find_dependency(TransitiveDep)
练习 3 - 查找其他类型的文件¶
在一个完美的世界里,我们关心的每一个依赖项都会被正确打包,或者至少有其他开发者写了一个模块来发现它们。我们生活在一个不完美的世界,有时我们不得不亲自动手,手动发现构建需求。
为此,我们有前面步骤中列出的其他查找命令,例如find_path()。
find_path(PackageIncludeFolder Package.h REQUIRED
PATH_SUFFIXES
Package
)
target_include_directories(MyApp
PRIVATE
${PackageIncludeFolder}
)
目标¶
将一个未打包的头文件添加到TutorialProject的Tutorial可执行文件中。
有用资源¶
要编辑的文件¶
TutorialProject/Tutorial/CMakeLists.txtTutorialProject/Tutorial/Tutorial.cxx
入门¶
在此步骤中,我们仅编辑TutorialProject项目。未打包的头文件Unpackaged/Unpackaged.h已安装到Step10/install树中。
完成TODO 9到TODO 11。
构建和运行¶
此练习没有特殊的构建步骤,导航到Help/guide/Step10/TutorialProject并执行常规的构建步骤。
cmake --build build
如果构建成功,我们已成功将Unpackaged include 目录添加到项目中。
解决方案¶
首先,我们调用find_path()来发现Unpackaged include 目录。我们使用REQUIRED,因为如果找不到Unpackaged.h头文件,构建Tutorial将会失败。
TODO 9 点击显示/隐藏答案
find_path(UnpackagedIncludeFolder Unpackaged.h REQUIRED
PATH_SUFFIXES
Unpackaged
)
接下来,我们使用target_include_directories()将发现的路径添加到Tutorial中。
TODO 10 点击显示/隐藏答案
target_include_directories(Tutorial
PRIVATE
${UnpackagedIncludeFolder}
)
最后,我们编辑Tutorial.cxx以包含发现的头文件。
TODO 11 点击显示/隐藏答案
#include <MathFunctions.h>
#include <Unpackaged.h>