依赖同版本但选项不同的 tool-require

注意

这是一个高级用例。在绝大多数情况下它都不是必需的。

在一般情况下,尝试这样做

def build_requirements(self):
    self.tool_requires("gcc/1.0")
    self.tool_requires("gcc/1.0")

将生成一个“冲突”,显示类似 Duplicated requirement 的错误。

然而,在一些特殊情况下,我们可能需要依赖相同版本的 tool_requires,但使用该 tool_requires 的不同二进制文件。这可以通过为这些 tool_requires 传递不同的 options 来实现。请先克隆源代码来重现这个项目。你可以在 GitHub 上的 examples2 仓库 中找到它们。

git clone https://github.com/conan-io/examples2.git
cd examples2/examples/graph/tool_requires/different_options

在那里,我们有一个 gcc 的伪造配方,其中包含

class Pkg(ConanFile):
    name = "gcc"
    version = "1.0"
    options = {"myoption": [1, 2]}

    def package(self):
        # This fake compiler will print something different based on the option
        echo = f"@echo off\necho MYGCC={self.options.myoption}!!"
        save(self, os.path.join(self.package_folder, "bin", f"mygcc{self.options.myoption}.bat"), echo)
        save(self, os.path.join(self.package_folder, "bin", f"mygcc{self.options.myoption}.sh"), echo)
        os.chmod(os.path.join(self.package_folder, "bin", f"mygcc{self.options.myoption}.sh"), 0o777)

这不是一个实际的编译器,它用一个 shell 或 bat 脚本来模拟它,当执行时会打印 MYGCC=current-option。注意二进制文件本身被称为 mygcc1mygcc2,也就是说,它在可执行文件名本身中就包含了选项。

我们可以为 gcc/1.0 创建 2 个不同的二进制文件,方法是

$ conan create gcc -o myoption=1
$ conan create gcc -o myoption=2

现在,在 wine 文件夹中有一个 conanfile.py 文件,内容如下:

class Pkg(ConanFile):
    name = "wine"
    version = "1.0"

    def build_requirements(self):
        self.tool_requires("gcc/1.0", run=False, options={"myoption": 1})
        self.tool_requires("gcc/1.0", run=False, options={"myoption": 2})

    def generate(self):
        gcc1 = self.dependencies.build.get("gcc", options={"myoption": 1})
        assert gcc1.options.myoption == "1"
        gcc2 = self.dependencies.build.get("gcc", options={"myoption": 2})
        assert gcc2.options.myoption == "2"

    def build(self):
        ext = "bat" if platform.system() == "Windows" else "sh"
        self.run(f"mygcc1.{ext}")
        self.run(f"mygcc2.{ext}")

第一个重要点是 build_requirements() 方法,它对两个二进制文件都执行了 tool_requires(),但定义了 run=Falseoptions={"myoption": value} 特征。这一点非常重要:我们告诉 Conan,实际上我们不需要从这些包中运行任何东西。由于 tool_requires 不可见,它们不定义头文件或库,并且它们定义了不同的 options,因此没有任何东西能让 Conan 识别这两个 tool_requires 是冲突的。因此,依赖图可以无错误地构建,并且 wine/1.0 包将包含两个不同的 tool_requires,分别指向 gcc/1.0myoption=1myoption=2

当然,并非我们不会从这些 tool_requires 中运行任何东西,但现在 Conan 并不知道,这完全是用户的责任来管理。

警告

使用 run=False 会使 tool_requires() 完全不可见,这意味着配置文件中的 [tool_requires] 部分将无法覆盖其版本,但它会创建一个额外的 tool-require 依赖项,版本将从配置文件注入。你可能想使用类似 !wine/*: gcc/3.0 的方式排除特定的包。

generate() 方法中,配方仍然可以通过提供我们想要的依赖项的选项值来访问每个不同的 tool_require 版本,例如:self.dependencies.build.get("gcc", options={"myoption": 1})

最后,最重要的一点是,这些工具的使用完全由用户负责。包含可执行文件的两个 tool_requiresbin 文件夹将因为 VirtualBuildEnv 生成器(默认情况下会更新 PATH 环境变量)而处于路径中。在这种情况下,可执行文件是不同的,例如 mygcc1.sh```和 ``mygcc2.sh,所以这不是问题,每个文件都可以在其自己的包内找到。

但是,如果可执行文件名完全相同,例如 gcc.exe,那么就有必要获取完整的文件夹(通常在 generate() 方法中)并使用完整路径来消除歧义,例如:self.dependencies.build.get("gcc", options={"myoption": 1}).cpp_info.bindir

让我们看看它是如何工作的。如果我们执行

$ conan create wine
...
wine/1.0: RUN: mygcc1.bat
MYGCC=1!!

wine/1.0: RUN: mygcc2.bat
MYGCC=2!!