准备构建

上一个教程章节中,我们为我们的 Conan 包添加了 fmt 需求,以便为我们的“Hello World” C++ 库提供彩色输出。在本节中,我们重点关注 recipe 的 generate() 方法。此方法的目的是生成在运行构建步骤时可能需要的所有信息。这意味着诸如:

  • 写入在构建步骤中使用的文件,例如注入环境变量的脚本、传递给构建系统的文件等。

  • 配置工具链以根据设置和选项提供额外信息,或者从 Conan 默认生成但可能不适用于某些情况的工具链中删除信息。

我们基于上一个教程章节的简单示例来解释如何使用此方法。我们向 recipe 添加了一个 with_fmt 选项,根据其值来决定是否需要 fmt 库。我们使用 generate() 方法修改工具链,使其将一个变量传递给 CMake,以便我们可以在源代码中根据条件添加该库并使用 fmt 或不使用。

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

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

您会注意到 conanfile.py 文件与之前的 recipe 相比发生了一些变化。让我们检查相关部分:

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

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

    ...
    options = {"shared": [True, False],
               "fPIC": [True, False],
               "with_fmt": [True, False]}

    default_options = {"shared": False,
                       "fPIC": True,
                       "with_fmt": True}
    ...

    def validate(self):
        if self.options.with_fmt:
            check_min_cppstd(self, "11")
            check_max_cppstd(self, "14")

    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("optional_fmt")

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

    def generate(self):
        tc = CMakeToolchain(self)
        if self.options.with_fmt:
            tc.variables["WITH_FMT"] = True
        tc.generate()

    ...

如您所见

  • 我们声明了一个新的 with_fmt 选项,默认值设置为 True

  • 根据 with_fmt 选项的值

    • 我们有条件地安装 fmt/8.1.1 Conan 包。

    • 我们有条件地要求 C++ 标准的最小值和最大值,因为 fmt 库至少需要 C++11,如果我们尝试使用高于 C++14 的标准,它将无法编译(只是一个例子,fmt 实际上可以与更现代的标准一起构建)。

    • 我们有条件地将值为 TrueWITH_FMT 变量注入到 CMakeToolchain 中,以便我们可以在 hello 库的 CMakeLists.txt 中使用它来添加 CMake fmt::fmt 目标。

  • 我们正在克隆库的另一个分支。optional_fmt 分支包含代码中的一些更改。让我们看看 CMake 方面有哪些变化:

CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(hello CXX)

add_library(hello src/hello.cpp)
target_include_directories(hello PUBLIC include)
set_target_properties(hello PROPERTIES PUBLIC_HEADER "include/hello.h")

if (WITH_FMT)
    find_package(fmt)
    target_link_libraries(hello fmt::fmt)
    target_compile_definitions(hello PRIVATE USING_FMT=1)
endif()

install(TARGETS hello)

如您所见,我们使用了注入到 CMakeToolchain 中的 WITH_FMT。根据该值,我们将尝试查找 fmt 库并将其与我们的 hello 库链接。另外,请检查我们是否添加了编译定义 USING_FMT=1,我们根据是否选择添加对 fmt 的支持而在源代码中使用它。

hello.cpp
#include <iostream>
#include "hello.h"

#if USING_FMT == 1
#include <fmt/color.h>
#endif

void hello(){
    #if USING_FMT == 1
        #ifdef NDEBUG
        fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "hello/1.0: Hello World Release! (with color!)\n");
        #else
        fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "hello/1.0: Hello World Debug! (with color!)\n");
        #endif
    #else
        #ifdef NDEBUG
        std::cout << "hello/1.0: Hello World Release! (without color)" << std::endl;
        #else
        std::cout << "hello/1.0: Hello World Debug! (without color)" << std::endl;
        #endif
    #endif
}

我们首先使用 with_fmt=True 从源代码构建包,然后使用 with_fmt=False。当 test_package 运行时,它将根据选项的值显示不同的消息。

$ conan create . --build=missing -o with_fmt=True
-------- 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! (with color!)

$ conan create . --build=missing -o with_fmt=False
-------- 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! (without color)

这只是一个简单示例,展示了如何使用 generate() 方法根据一个选项的值来自定义工具链,但您还可以在 generate() 方法中执行许多其他操作,例如:

  • 根据您的需求创建完整的自定义工具链以用于构建。

  • 访问有关包依赖项的某些信息,例如:

    • 通过 conf_info 进行配置。

    • 依赖项的选项。

    • 使用 copy 工具从依赖项导入文件。您还可以导入文件以创建包清单,收集所有依赖项的版本和许可证。

  • 使用 环境工具生成系统环境信息。

  • 除了 ReleaseDebug 之外,添加自定义配置,同时考虑设置,例如 ReleaseSharedDebugShared