generate()

此方法将在依赖关系图的计算和安装之后运行。这意味着它将在 conan install 命令之后运行,或者当在缓存中构建包时,它将在调用 build() 方法之前运行。

generate() 的目的是准备构建,生成必要的文件。这些文件通常是

  • 包含用于定位依赖项的信息的文件,例如 xxxx-config.cmake CMake 配置脚本,或 xxxx.props Visual Studio 属性文件。

  • 环境激活脚本,例如 conanbuild.batconanbuild.sh,它们定义了构建所需的所有必要的环境变量。

  • 工具链文件,例如 conan_toolchain.cmake,它包含当前 Conan 设置和选项与构建系统特定语法之间的映射。CMakePresets.json 适用于使用现代版本的 CMake 用户。

  • 通用构建信息,例如 conanbuild.conf 文件,它可以包含一些工具链(如 autotools)的信息,以便在 build() 方法中使用。

  • 特定的构建系统文件,例如 conanvcvars.bat,它包含在使用 Microsoft 编译器编译时,某些构建系统(如 Ninja)所需的 Visual Studio vcvars.bat 调用。

其理念是 generate() 方法实现所有必要的逻辑,使得用户在 conan install 之后的手动构建非常简单明了,并且也使 build() 方法逻辑更简单。用户在其本地流程中产生的构建应该与在缓存中使用 conan create 完成的构建完全相同,而无需额外努力。

文件的生成发生在当前布局定义的 generators_folder 中。

在许多情况下,generate() 方法可能不是必要的,声明 generators 属性可能就足够了

from conan import ConanFile

class Pkg(ConanFile):
    generators = "CMakeDeps", "CMakeToolchain"

但是 generate() 方法可以显式地实例化这些生成器,有条件地使用它们(例如在 Windows 中使用一个构建系统,而在其他平台中使用另一个构建系统集成),自定义它们,或提供完全自定义的生成。

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain

class Pkg(ConanFile):

    def generate(self):
        tc = CMakeToolchain(self)
        # customize toolchain "tc"
        tc.generate()
        # Or provide your own custom logic

generate() 方法的当前工作目录将是当前布局中定义的 self.generators_folder

对于自定义集成,将代码放在通用的 python_require 中将是避免在多个 recipe 中重复的好方法

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain

class Pkg(ConanFile):

    python_requires = "mygenerator/1.0"

    def generate(self):
        mygen = self.python_requires["mygenerator"].module.MyGenerator(self)
        # customize mygen behavior, like mygen.something= True
        mygen.generate()

如果需要从依赖项中收集或复制某些文件,也可以在 generate() 方法中执行此操作,访问 self.dependencies。列出依赖项 “mydep” 中的不同 include 目录、lib 目录可以像这样

from conan import ConanFile

class Pkg(ConanFile):

    def generate(self):
        info = self.dependencies["mydep"].cpp_info
        self.output.info("**includedirs:{}**".format(info.includedirs))
        self.output.info("**libdirs:{}**".format(info.libdirs))
        self.output.info("**libs:{}**".format(info.libs))

并将 Windows 和 OSX 中的共享库复制到当前构建文件夹,可以像这样完成

from conan import ConanFile

class Pkg(ConanFile):

    def generate(self):
        # NOTE: In most cases it is not necessary to copy the shared libraries
        # of dependencies to use them. Conan environment generators that create
        # environment scripts allow to use the shared dependencies without copying
        # them to the current location
        for dep in self.dependencies.values():
            # This code assumes dependencies will only have 1 libdir/bindir, if for some
            # reason they have more than one, it will fail. Use ``dep.cpp_info.libdirs``
            # and ``dep.cpp_info.bindirs`` lists for those cases.
            copy(self, "*.dylib", dep.cpp_info.libdir, self.build_folder)
            # In Windows, dlls are in the "bindir", not "libdir"
            copy(self, "*.dll", dep.cpp_info.bindir, self.build_folder)

注意

最佳实践

  • generate() 中将共享库复制到当前项目在大多数情况下不是必要的,并且不应作为通用方法来完成。相反,默认启用的 Conan 环境生成器将自动生成环境脚本,例如 conanbuild.bat|.shconanrun.bat|.sh,其中包含必要的环境变量(PATHLD_LIBRARY_PATH 等),以便在运行时正确地定位和使用依赖项的共享库。

  • 访问依赖项 self.dependencies["mydep"].package_folder 是可能的,但是当依赖项 “mydep” 处于 “editable” 模式时,它将为 None。如果您计划使用可编辑的包,请确保始终引用 cpp_info.xxxdirs

另请参阅

self.dependencies

Conan recipe 通过 self.dependencies 属性访问其依赖项。此属性通常被生成器(如 CMakeDepsMSBuildDeps)用来生成构建所需的必要文件。

本节记录 self.dependencies 属性,因为它可能被用户直接在 recipe 中使用,或者间接地用于创建自定义构建集成和生成器。

依赖项接口

可以使用以下语法访问当前 recipe 的每个单独的依赖项

class Pkg(ConanFile):
    requires = "openssl/0.1"

    def generate(self):
        openssl = self.dependencies["openssl"]
        # access to members
        openssl.ref.version
        openssl.ref.revision # recipe revision
        openssl.options
        openssl.settings

        if "zlib" in self.dependencies:
            # do something

一些 重要 事项

  • 所有信息都是 只读 的。任何修改依赖项信息的尝试都是错误的,并且可能在任何时候引发,即使它尚未引发。

  • 也不可能调用任何方法或通过此机制重用来自依赖项的任何代码。

  • 此信息在某些 recipe 方法中不存在,仅在那些在完整依赖关系图计算完成后评估的方法中存在。它不会存在于 configure()config_optionsexport()export_source()set_name()set_version()requirements()build_requirements()system_requirements()source()init()layout() 中。任何尝试在这些方法中使用它都可能在任何时候引发错误。

  • 目前,此信息应仅在 generate()validate() 方法中使用。对于任何其他用途,请提交 Github issue。

并非所有依赖项 conanfile 的字段都暴露出来,当前的字段是

  • package_folder: 依赖项包二进制文件的文件夹位置

  • recipe_folder: 包含依赖项的 conanfile.py(和其他导出的文件)的文件夹

  • recipe_metadata_folder: 包含依赖项的可选 recipe 元数据文件的文件夹

  • package_metadata_folder: 包含依赖项的可选包元数据文件的文件夹

  • immutable_package_folder: 当 finalize() 方法存在时,包含不可变工件的文件夹

  • ref: 一个包含 nameversionuserchannelrevision (recipe 修订版本) 的对象

  • pref: 一个包含 refpackage_idrevision (包修订版本) 的对象

  • buildenv_info: 包含构建所需环境信息的 Environment 对象

  • runenv_info: 包含运行应用程序所需环境信息的 Environment 对象

  • cpp_info: 依赖项的 includedirs、libdirs 等。

  • settings: 此依赖项的实际设置值

  • settings_build: 此依赖项的实际构建设置值

  • options: 此依赖项的实际选项值

  • context: 此依赖项的上下文 (build, host)

  • conf_info: 此依赖项的配置信息,旨在应用于使用者。

  • dependencies: 此依赖项的传递依赖项

  • is_build_context: 如果 context == "build",则返回 True

  • conan_data: 来自依赖项的 conandata.yml 文件的 conan_data 属性

  • license: 依赖项的 license 属性

  • description: 依赖项的 description 属性

  • homepage: 依赖项的 homepage 属性

  • url: 依赖项的 url 属性

  • package_type: 依赖项的 package_type

  • languages: 依赖项的 languages

  • extension_properties: 依赖项的 extension_properties。应视为只读。

迭代依赖项

可以以类似字典的方式迭代 recipe 的所有依赖项。请注意,self.dependencies 包含当前的所有依赖项,包括直接依赖项和传递依赖项。当前依赖项的每个上游依赖项,只要对它有影响,都将在 self.dependencies 中有一个条目。

迭代依赖项可以像这样完成

requires = "zlib/1.2.11", "poco/1.9.4"

def generate(self):
    for require, dependency in self.dependencies.items():
        self.output.info("Dependency is direct={}: {}".format(require.direct, dependency.ref))

将输出

conanfile.py (hello/0.1): Dependency is direct=True: zlib/1.2.11
conanfile.py (hello/0.1): Dependency is direct=True: poco/1.9.4
conanfile.py (hello/0.1): Dependency is direct=False: pcre/8.44
conanfile.py (hello/0.1): Dependency is direct=False: expat/2.4.1
conanfile.py (hello/0.1): Dependency is direct=False: sqlite3/3.35.5
conanfile.py (hello/0.1): Dependency is direct=False: openssl/1.1.1k
conanfile.py (hello/0.1): Dependency is direct=False: bzip2/1.0.8

其中 require 字典键是一个 “requirement”,可以包含当前 recipe 和依赖项之间关系的说明符。目前它们可以是

  • require.direct: 布尔值,如果是直接依赖项则为 True,如果是传递依赖项则为 False

  • require.build: 布尔值,如果它是构建上下文中的 build_require,例如 cmake,则为 True

  • require.test: 布尔值,如果它是主机上下文中的 build_require (使用 self.test_requires() 定义),例如 gtest,则为 True

dependency 字典值是上面描述的只读对象,用于访问依赖项属性。

self.dependencies 包含一些辅助方法来根据某些条件进行过滤

  • self.dependencies.host: 将过滤掉 build=True 的 require,留下常规依赖项,例如 zlibpoco

  • self.dependencies.direct_host: 将过滤掉 build=Truedirect=False 的 require

  • self.dependencies.build: 将过滤掉 build=False 的 require,仅留下构建上下文中的 tool_requires,例如 cmake

  • self.dependencies.direct_build: 将过滤掉 build=Falsedirect=False 的 require

  • self.dependencies.test: 将过滤掉 build=Truetest=False 的 require,仅留下主机上下文中的测试需求,例如 gtest

它们可以以相同的方式使用

requires = "zlib/1.2.11", "poco/1.9.4"

def generate(self):
    cmake = self.dependencies.direct_build["cmake"]
    for require, dependency in self.dependencies.build.items():
        # do something, only build deps here

依赖项 cpp_info 接口

cpp_info 接口被构建系统大量使用以访问数据。此对象定义全局和按组件属性,以访问诸如 include 文件夹之类的信息

def generate(self):
    cpp_info = self.dependencies["mydep"].cpp_info
    cpp_info.includedirs
    cpp_info.libdirs

    cpp_info.components["mycomp"].includedirs
    cpp_info.components["mycomp"].libdirs

cppinfo 对象中声明的所有路径(例如 cpp_info.includedirs)都是绝对路径,并且无论依赖项是在缓存中还是 可编辑包 中都有效。

另请参阅