为软件包添加依赖项

上一教程部分中,我们为“Hello World”C++ 库创建了一个 Conan 软件包。我们使用了conan.tools.scm.Git()工具从 Git 仓库中检索源文件。到目前为止,该软件包没有任何对其他 Conan 软件包的依赖。下面我们将解释如何以与我们在使用软件包部分中非常相似的方式为我们的软件包添加依赖项。我们将使用fmt库为我们的“Hello World”库添加一些花哨的颜色输出。

请先克隆源代码以重新创建此项目。您可以在 GitHub 上的examples2 仓库中找到它们。

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/creating_packages/add_requires

您会注意到conanfile.py文件与之前的配方有一些变化。让我们检查相关部分

...
from conan.tools.build import check_max_cppstd, check_min_cppstd
...

class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"

    ...
    generators = "CMakeDeps"
    ...

    def validate(self):
        check_min_cppstd(self, "11")
        check_max_cppstd(self, "20")

    def requirements(self):
        self.requires("fmt/8.1.1")

    def source(self):
        git = Git(self)
        git.clone(url="https://github.com/conan-io/libhello.git", target=".")
        # Please, be aware that using the head of the branch instead of an immutable tag
        # or commit is not a good practice in general
        git.checkout("require_fmt")
  • 首先,我们将generators类属性设置为让 Conan 调用CMakeDeps生成器。在之前的配方中不需要这样做,因为我们没有依赖项。CMakeDeps将生成 CMake 查找fmt库所需的所有配置文件。

  • 接下来,我们使用requires()方法向我们的软件包添加fmt依赖项。

  • 请注意,我们在source()方法中添加了额外一行。我们使用Git().checkout()方法检出require_fmt分支中的源代码。此分支包含源代码中的更改,用于向库消息添加颜色,以及CMakeLists.txt中的更改,用于声明我们正在使用fmt库。

  • 最后,请注意我们在配方中添加了validate()方法。我们已经在使用软件包部分中使用了此方法,用于对不支持的配置引发错误。在这里,我们调用函数check_min_cppstd()check_max_cppstd()来验证我们是否在设置中至少使用了 C++11 且最多使用了 C++20 标准。

您可以在require_fmt分支中查看使用 fmt 库的新源文件。您会看到hello.cpp文件为输出消息添加了颜色

#include <fmt/color.h>

#include "hello.h"

void hello(){
    #ifdef NDEBUG
    fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "hello/1.0: Hello World Release!\n");
    #else
    fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "hello/1.0: Hello World Debug!\n");
    #endif
    ...

让我们使用当前的默认配置从源文件构建软件包,然后让test_package文件夹测试软件包。您现在应该能看到带颜色的输出消息

$ conan create . --build=missing
-------- Exporting the recipe ----------
...
-------- Testing the package: Running test() ----------
hello/1.0 (test package): Running test()
hello/1.0 (test package): RUN: ./example
hello/1.0: Hello World Release!
  hello/1.0: __x86_64__ defined
  hello/1.0: __cplusplus 201103
  hello/1.0: __GNUC__ 4
  hello/1.0: __GNUC_MINOR__ 2
  hello/1.0: __clang_major__ 13
  hello/1.0: __clang_minor__ 1
  hello/1.0: __apple_build_version__ 13160021

头文件传递性

默认情况下,Conan 假定所需的依赖项头文件是当前软件包的实现细节,以促进良好的软件工程实践,例如低耦合和封装。在上面的示例中,fmt纯粹是hello/1.0软件包的实现细节。hello/1.0的消费者不会知道任何关于fmt的信息,也无法访问其头文件,如果hello/1.0的消费者尝试添加#include <fmt/color.h>,它将失败,因为无法找到该头文件。

但是,如果hello/1.0软件包的公共头文件包含对fmt头文件的#include,这意味着这些头文件必须传播下去,才能让hello/1.0的消费者成功编译。由于这不是默认的预期行为,配方必须声明为

class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"

    def requirements(self):
        self.requires("fmt/8.1.1", transitive_headers=True)

这将把必要的编译标志和头文件includedirs传播给hello/1.0的消费者。

注意

最佳实践

如果hello/1.0的消费者直接包含了fmt头文件,例如#include <fmt/color.h>,那么,该消费者应该声明自己的self.requires("fmt/8.1.1")要求,因为这是一个直接要求。换句话说,即使从该消费者中删除了对hello/1.0的依赖,它仍然会依赖于fmt,因此它不能滥用hellofmt头文件的传递性,而必须明确声明它们。