package_info()

package_info() 方法负责定义包提供给消费者使用的信息,以便这些消费者能够轻松、自动地使用此包。generate() 方法(在消费者端)是将 package_info() 中定义的信息映射到消费者特定构建系统的位置。因此,如果我们希望一个包能够被不同的构建系统消费(就像 ConanCenter 的社区 recipe 那样),那么此信息的完整性非常重要。

重要

此方法定义的信息仅供此包的消费者使用,而非供包本身使用。此方法在二进制包构建和打包后执行。构建过程中需要消费的信息应在 generate() 方法中处理。

cpp_info: 库和构建信息

每个包都必须为其消费者指定某些构建信息。这可以通过 cpp_info 属性完成。

# Binaries to link
self.cpp_info.libs = []  # The libs to link against
self.cpp_info.system_libs = []  # System libs to link against
self.cpp_info.frameworks = []  # OSX frameworks that consumers will link against
self.cpp_info.objects = []  # precompiled objects like .obj .o that consumers will link
# Directories
self.cpp_info.includedirs = ['include']  # Ordered list of include paths
self.cpp_info.libdirs = ['lib']  # Directories where libraries can be found
self.cpp_info.bindirs = ['bin']  # Directories where executables and shared libs can be found
self.cpp_info.resdirs = []  # Directories where resources, data, etc. can be found
self.cpp_info.srcdirs = []  # Directories where sources can be found (debugging, reusing sources)
self.cpp_info.builddirs = []  # Directories where build scripts for consumers can be found
self.cpp_info.frameworkdirs = []  # Directories where OSX frameworks can be found
# Flags
self.cpp_info.defines = []  # preprocessor definitions
self.cpp_info.cflags = []  # pure C flags
self.cpp_info.cxxflags = []  # C++ compilation flags
self.cpp_info.sharedlinkflags = []  # linker flags
self.cpp_info.exelinkflags = []  # linker flags
# Properties
self.cpp_info.set_property("property_name", "property_value")
# Structure
self.cpp_info.components # Dictionary-like structure to define the different components a package may have
self.cpp_info.requires # List of components from requirements that need to be propagated downstream

要链接的二进制文件

  • libs: 消费者应链接的已编译库(包含在包中)的有序列表。默认为空。

  • system_libs: 消费者应链接的系统库(不包含在包中)的有序列表。默认为空。

  • frameworks: 消费者应链接的 macOS 框架(包含或不包含在包中)的有序列表。默认为空。

  • objects: 消费者应链接的包含在包中的预编译对象(.obj, .o)的有序列表。默认为空。

目录

  • includedirs: 头文件所在的目录的相对路径列表(从包根目录开始)。默认初始化为 ['include'],很少更改。

  • libdirs: 查找库对象二进制文件(*.lib, *.a, *.so, *.dylib)的目录的相对路径列表(从包根目录开始)。默认初始化为 ['lib'],很少更改。

  • bindirs: 查找库运行时二进制文件(如 Windows 可执行 .dll)的目录的相对路径列表(从包根目录开始)。默认初始化为 ['bin'],很少更改。

  • resdirs: 查找资源文件(图像、xml 等)的目录的相对路径列表(从包根目录开始)。默认为空。

  • srcdirs: 查找源文件(如 .c, .cpp)的目录的相对路径列表(从包根目录开始)。默认为空。它可用于存储源文件(以便后续对包进行调试,或在其他包中重用这些源文件进行构建)。

  • builddirs: 包含构建脚本的目录的相对路径列表(从包根目录开始),这些脚本可供消费者使用。默认为空。

  • frameworkdirs: 包含 macOS 框架的目录的相对路径列表(从包根目录开始)。

标志 (Flags)

  • defines: 预处理器指令的有序列表。在某些情况下,消费者需要指定一些定义,以便包含库头文件与二进制文件匹配,这很常见。

  • cflags, cxxflags, sharedlinkflags, exelinkflags: 消费者应激活以确保正确行为的标志列表。很少使用。

属性 (Properties)

  • set_property() 允许定义一些内置属性和用户通用属性,通过 cpp_info 模型传播给消费者。它们可能包含特定构建系统的信息。一些内置属性如 cmake_file_name, cmake_target_name, pkg_config_name,可以定义 CMakeDepsPkgConfigDeps 生成器的特定行为。有关这些属性的更多信息,请阅读特定构建系统集成文档。

结构

  • components: 一个字典,以名称作为键,以组件对象作为值,用于建模一个包可能包含的不同组件:库、可执行文件等。

  • requires: 实验性特性 此包(及其消费者)应链接的需求包组件列表。这将由支持组件特性的生成器使用。

不同的配置通常会产生不同的 package_info,例如,库名称在不同的操作系统上可能会改变,或者根据编译器和操作系统会使用不同的 system_libs

settings = "os", "compiler", "arch", "build_type"
options = {"shared": [True, False]}

def package_info(self):
    if not self.settings.os == "Windows":
        self.cpp_info.libs = ["zmq-static"] if not self.options.shared else ["zmq"]
    else:
        ...

    if not self.options.shared:
        self.cpp_info.defines = ["ZMQ_STATIC"]
    if self.settings.os == "Windows" and self.settings.compiler == "msvc":
        self.cpp_info.system_libs.append("ws2_32")

属性 (Properties)

任何 CppInfo 对象都可以声明“属性”,这些属性可以被生成器读取。属性的值可以是任何类型。请查阅每个生成器的参考文档以了解其中使用的属性。

def set_property(self, property_name, value)
def get_property(self, property_name, check_type=None):

示例

def package_info(self):
    self.cpp_info.set_property("cmake_find_mode", "both")
    self.cpp_info.get_property("cmake_find_mode", check_type=str)

组件 (Components)

如果您的包由多个库组成,可以声明组件,这样可以为每个库定义一个 CppInfo 对象,并定义它们之间以及与其他包组件之间的依赖关系(以下示例并非真实情况):

def package_info(self):
    self.cpp_info.components["crypto"].set_property("cmake_file_name", "Crypto")
    self.cpp_info.components["crypto"].libs = ["libcrypto"]
    self.cpp_info.components["crypto"].defines = ["DEFINE_CRYPTO=1"]
    self.cpp_info.components["crypto"].requires = ["zlib::zlib"]  # Depends on all components in zlib package

    self.cpp_info.components["ssl"].set_property("cmake_file_name", "SSL")
    self.cpp_info.components["ssl"].includedirs = ["include/headers_ssl"]
    self.cpp_info.components["ssl"].libs = ["libssl"]
    self.cpp_info.components["ssl"].requires = ["crypto",
                                                "boost::headers"]  # Depends on headers component in boost package

    obj_ext = "obj" if platform.system() == "Windows" else "o"
    self.cpp_info.components["ssl-objs"].objects = [os.path.join("lib", "ssl-object.{}".format(obj_ext))]

可以使用 requires 属性和组件名称来定义组件之间以及与其他需求包组件之间的依赖关系。将计算组件的依赖图,并按正确顺序汇总每个字段的值。

buildenv_info, runenv_info

buildenv_inforunenv_info 属性是 Environment 对象,允许以环境变量的形式为消费者定义信息。它们可以使用 Environment 的任何方法来定义这些信息。

settings = "os", "compiler", "arch", "build_type"

def package_info(self):
    self.buildenv_info.define("MYVAR", "1")
    self.buildenv_info.prepend_path("MYPATH", "my/path")
    if self.settings.os == "Android":
        arch = "myarmarch" if self.settings.arch=="armv8" else "otherarch"
        self.buildenv_info.append("MY_ANDROID_ARCH", f"android-{arch})

    self.runenv_info.append_path("MYRUNPATH", "my/run/path")
    if self.settings.os == "Windows":
        self.runenv_info.define_path("MYPKGHOME", "my/home")

请注意,这些对象不绑定于常规 requirestool_requires,任何包 recipe 都可以同时使用两者。buildenv_inforunenv_info 的区别在于,前者在 Conan 从源代码构建某些内容时应用,例如在 build() 方法中;而后者则在需要激活运行时环境的“host”上下文执行某些内容时使用。

消费者中默认会使用 Conan VirtualBuildEnv 生成器,它收集 buildenv_info(以及“build”上下文中的一些 runenv_info)的信息,创建 conanbuild 环境变量脚本,该脚本在所有 self.run(cmd, env="conanbuild") 调用中默认运行。VirtualRunEnv 生成器也会在消费者中默认使用,收集“host”上下文中的 runenv_info 信息,创建 conanrun 环境变量脚本,该脚本可以显式地与 self.run(<cmd>, env="conanrun") 一起使用。

注意

最佳实践

无需将 bindirs 添加到 PATH 环境变量中,这会由消费者的 VirtualBuildEnvVirtualRunEnv 生成器自动完成。同样,无需将 includedirslibdirs 或其他任何目录添加到环境变量中,因为这些信息通常由其他生成器管理。

conf_info

“build”上下文中的 tool_requires 包可以通过 conf_info 属性将其一些 conf 配置传递给其直接消费者。例如,一个打包 AndroidNDK 的 Conan 包可以这样做:

def package_info(self):
    self.conf_info.define_path("tools.android:ndk_path", "path/to/ndk/in/package")

来自包的 conf_info 仍然可以被配置文件值覆盖,因为用户配置文件的优先级更高。

Conf.define(name, value)

为给定的配置名称定义一个值。

参数
  • name – 配置的名称。

  • value – 配置的值。

def package_info(self):
    # Setting values
    self.conf_info.define("tools.build:verbosity", "verbose")
    self.conf_info.define("tools.system.package_manager:sudo", True)
    self.conf_info.define("tools.microsoft.msbuild:max_cpu_count", 2)
    self.conf_info.define("user.myconf.build:ldflags", ["--flag1", "--flag2"])
    self.conf_info.define("tools.microsoft.msbuildtoolchain:compile_options", {"ExceptionHandling": "Async"})
Conf.append(name, value)

为给定的配置名称追加一个值。

参数
  • name – 配置的名称。

  • value – 要追加的值。

def package_info(self):
    # Modifying configuration list-like values
    self.conf_info.append("user.myconf.build:ldflags", "--flag3")  # == ["--flag1", "--flag2", "--flag3"]
Conf.prepend(name, value)

为给定的配置名称前置一个值。

参数
  • name – 配置的名称。

  • value – 要前置的值。

def package_info(self):
    self.conf_info.prepend("user.myconf.build:ldflags", "--flag0")  # == ["--flag0", "--flag1", "--flag2", "--flag3"]
Conf.update(name, value)

更新给定配置名称的值。

参数
  • name – 配置的名称。

  • value – 配置的值。

def package_info(self):
    # Modifying configuration dict-like values
    self.conf_info.update("tools.microsoft.msbuildtoolchain:compile_options", {"ExpandAttributedSource": "false"})
Conf.remove(name, value)

从给定的配置名称中移除一个值。

参数
  • name – 配置的名称。

  • value – 要移除的值。

def package_info(self):
    # Remove
    self.conf_info.remove("user.myconf.build:ldflags", "--flag1")  # == ["--flag0", "--flag2", "--flag3"]
Conf.unset(name)

清除变量,相当于执行 unset 或 set XXX=

参数

name – 配置的名称。

def package_info(self):
    # Unset any value
    self.conf_info.unset("tools.microsoft.msbuildtoolchain:compile_options")

可以在作为 tool_requires 的包中定义配置。例如,假设有一个包捆绑了 AndroidNDK,它可以将该 NDK 的位置定义到 tools.android:ndk_path 配置中,如下所示:

import os
from conan import ConanFile

class Pkg(ConanFile):
    name = "android_ndk"

    def package_info(self):
        self.conf_info.define("tools.android:ndk_path", os.path.join(self.package_folder, "ndk"))

请注意,这仅从 recipe 的直接、立即 tool_requires 包传播信息。

generator_info

警告

此功能是实验性的,可能会有重大变更。更多信息请参见Conan 稳定性部分。

构建上下文中的 tool_requires 包可以通过在 package_info 方法内的 generator_info 列表中添加生成器,将生成器注入到 recipe 中。这对于将自定义生成器注入到 recipe 中非常有用,这些生成器将供包的消费者使用,就像它们在其 generators 属性中声明一样。

class MyGenerator:
    def __init__(self, conanfile):
        self._conanfile = conanfile

    def generate(self):
        self.output.info(f"Calling custom generator for {conanfile}")

def package_info(self):
    self.generator_info.append(MyGenerator)

请注意,这仅从 recipe 的直接、立即 tool_requires 包传播信息。

注意

最佳实践

  • 如果存在其他方式为消费者提供信息,package_info() 方法并非严格必需。例如,如果您的包在构建时创建 xxx-config.cmake 文件并将其放入最终的包中,则可能完全不需要定义 package_info(),并且在消费者端也不需要 CMakeDeps,因为 CMakeToolchain 能够在包内部注入用于查找 xxx-config.cmake 文件的路径。这种方法适用于 Conan 的私有使用,尽管 CMake 存在一些限制,例如无法管理多配置项目(如 Visual Studio 在 IDE 中切换 Debug/Release,这可以通过 CMakeDeps 提供),以及在某些交叉构建场景中,使用既是库又是构建工具的包时存在的限制(如 protobuf,这也可以由 CMakeDeps 处理)。

  • 如果消费者可以使用不同的构建系统,例如在 ConanCenter 中,提供 package_info() 是非常必要的。在这种情况下,需要进行一些重复工作,编写 package_info() 可能会感觉像复制包的 xxx-config.cmake 信息,但目前无法自动从 CMake 中提取这些信息。

  • 如果您计划使用可编辑包 (editables) 或本地开发流程,需要检查 layout() 并为 self.cpp.buildself.cpp.source 定义信息。

  • 无需将 bindirs 添加到 PATH 环境变量中,这会由消费者的 VirtualBuildEnvVirtualRunEnv 生成器自动完成。

  • package_info() 中定义的路径不应转换为任何特定格式(例如 Windows 子系统所需的格式)。相反,消费者有责任将这些路径转换为适当的格式。

另请参阅

更多信息请参见定义包信息教程