自定义二进制兼容性

默认的二进制兼容性要求 settings 和 options 几乎完全匹配,以及依赖项版本按版本匹配,如前一节关于依赖项的内容中所述。

总而言之,安装依赖项时,所需的二进制文件 package_id 默认应匹配

  • 除了 compiler.cppstd 之外,package_id 中的所有 settings 都应与输入 profile 中提供的 settings 完全匹配,包括编译器版本。因此,compiler.version=9compiler.version=9.1 不同。

  • 默认行为将假定 C++ 软件包的不同 compiler.cppstd 值之间具有二进制兼容性,如果输入 profile 所需的 cppstd 不存在,则能够回退到其他值。这由 compatibility.py 插件控制,用户可以自定义该插件。

  • package_id 中的所有 options 都应与输入 profile 中提供的 options 完全匹配。

  • 依赖项的版本应匹配

    • 对于“嵌入依赖项”,应匹配确切的版本,包括 recipe-revision 和依赖项 package_idpackage_revision 永远不会被包括在内,因为它被认为对于同一个 package_id 拥有多个 package_revision 是不合理的。

    • 对于“非嵌入依赖项”,依赖项的版本应匹配到 minor 版本,patchrecipe_revision 和更多信息不予考虑。

    • 对于“工具依赖项”,默认情况下,依赖项的版本完全不影响 consumer 的 package_id

可以使用不同的方法自定义和更改这些规则,具体取决于需求,如下节所述

自定义 settings 和 options 的二进制兼容性

package_id() 方法中的信息擦除

Recipe 可以使用其 package_id() 方法从其 package_id擦除信息。例如,仅包含可执行文件的软件包可以决定从其 package_id 中删除 settings.compilersettings.build_type 中的信息,假设用任何编译器构建的可执行文件都有效,并且没有必要存储用不同编译器构建的不同二进制文件

def package_id(self):
    del self.info.settings.compiler
    del self.info.settings.build_type

也可以为给定的 setting 分配一个值,例如,如果我们希望对于 [>=5 <7>] 范围内的所有 gcc 版本都只有一个二进制文件,我们可以这样做

def package_id(self):
    if self.info.settings.compiler == "gcc":
        version = Version(self.info.settings.compiler.version)
        if version >= "5.0" and version < "7.0":
            self.info.settings.compiler.version = "gcc5-6"

注意

最佳实践

请注意,package_id() 中的信息擦除意味着 1 个 package_id 将代表整个不同 settings 范围,但用于创建二进制文件的确切 setting 的信息将丢失,并且只能为该范围创建一个二进制文件。使用该范围内不同的 settings 重新创建软件包将创建一个新的二进制文件,该文件将覆盖前一个二进制文件(使用新的 package-revision)。

如果我们希望能够为不同的输入 settings 创建、存储和管理不同的二进制文件,则不能使用信息擦除,建议使用以下 compatibility 方法。

compatibility() 方法

Recipe 可以使用其 compatibility() 方法定义其二进制兼容性规则。例如,如果我们希望将使用 gcc 版本 4.8、4.7 和 4.6 构建的二进制文件视为与使用 4.9 编译的二进制文件兼容,我们可以声明一个如下所示的 compatibility() 方法

def compatibility(self):
    if self.settings.compiler == "gcc" and self.settings.compiler.version == "4.9":
        return [{"settings": [("compiler.version", v)]}
                for v in ("4.8", "4.7", "4.6")]

阅读有关 compatibility() 方法的更多信息,请参阅 compatibility() 方法参考

compatibility.py 插件

可以通过 compatibility.py 插件全局定义兼容性,就像 compatibility() 方法对一个 recipe 所做的那样,但适用于所有软件包。

查看二进制兼容性 compatibility.py 扩展

自定义依赖项版本的二进制兼容性

全局默认 package_id 模式

global.conf 中定义的 core.package_id:default_xxx 配置可用于全局更改依赖项如何影响其 consumer 的默认值

core.package_id:default_build_mode: By default, 'None'
core.package_id:default_embed_mode: By default, 'full_mode'
core.package_id:default_non_embed_mode: By default, 'minor_mode'
core.package_id:default_python_mode: By default, 'minor_mode'
core.package_id:default_unknown_mode: By default, 'semver_mode'

这些 confs 影响 package id 的计算方式,因此更改它们将影响您生成的二进制文件。因此,建议它们在您的组织中保持一致。

注意

最佳实践

强烈建议 core.package_id:default_xxx 在组织中应该是全局的、一致的和不可变的。为不同的项目或团队更改这些默认值可能会造成混淆,因为它会导致二进制文件丢失。

如果生成的软件包在组织外部共享,则它也应与这些软件包的 consumer 保持一致,在这种情况下,建议通过 conan config install 共享 global.conf 文件。

考虑使用 Conan 默认值,它们应在效率和安全性之间取得良好的平衡,确保嵌入情况下的精确重建,以及通过版本对非嵌入情况进行良好的控制。

Recipe consumer 的自定义 package_id 模式

Recipe 可以通过某些 package_id_xxxx_mode 属性定义其对其 consumer 的默认影响。

package_id_embed_mode, package_id_non_embed_mode, package_id_unknown_mode 是可以在 recipe 中定义的类属性,用于定义当它们作为 requires 被消费时,它们对 consumer package_id 的影响。build_mode (实验性)是一个类属性,当 consumer 将其用作 tool_requires 时,它会影响软件包 consumer。可以声明为

from conan import ConanFile

class Pkg(ConanFile):
    ...
    package_id_embed_mode = "full_mode"
    package_id_non_embed_mode = "patch_mode"
    package_id_unknown_mode = "minor_mode"
    build_mode = "patch_mode"  # when this is used with tool_requires

阅读更多信息,请参阅 package_id_{embed,non_embed,python,unknown}_mode, build_mode

来自 recipe 依赖项的自定义 package_id

Recipe 可以使用 package_id_mode 特征定义其依赖项如何影响其 package_id

from conan import ConanFile

class Pkg(ConanFile):
    def requirements(self):
        self.requires("mydep/1.0", package_id_mode="patch_mode")

使用 package_id_mode 特征不区分 “embed” 和 “non-embed” 情况,由用户来定义正确的值。这种方法可能只应用于非常特殊的情况,这些情况不具有通过 options 控制的共享/静态库的可变性。

请注意,requirements() 方法在图表扩展时进行评估,依赖项尚不存在(尚未计算),因此无法知道依赖项 options。在这种情况下,可能更倾向于使用 package_id() 方法。

package_id() 方法可以定义依赖项如何使用以下方式影响当前软件包

from conan import ConanFile

class Pkg(ConanFile):
    def package_id(self):
        self.info.requires["mydep"].major_mode()

可以在 package_id_{embed,non_embed,python,unknown}_mode, build_mode 中定义可以使用的不同模式