配置 recipes 中的设置和选项

我们已经解释了 Conan 设置和选项 以及如何使用它们为不同的配置(如 Debug、Release、静态或共享库等)构建您的项目。在本节中,我们将解释当其中一个设置或选项不适用于 Conan 包时,如何配置这些设置和选项。我们将简要介绍 Conan 如何建模二进制兼容性以及这与选项和设置有何关系。

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

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

您会注意到先前 recipe 中的 conanfile.py 文件中的一些更改。让我们检查相关部分

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 config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

    def configure(self):
        if self.options.shared:
            # If os=Windows, fPIC will have been removed in config_options()
            # use rm_safe to avoid double delete errors
            self.options.rm_safe("fPIC")
    ...

您可以看到我们向 recipe 添加了一个 configure() 方法。 让我们解释一下此方法的目标,以及它与我们已经在 recipe 中定义的 config_options() 方法有何不同

  • configure():使用此方法配置 recipe 的哪些选项或设置可用。 例如,在本例中,我们删除 fPIC 选项,因为它只应在我们将库构建为共享库时为 True(实际上,某些构建系统会在构建共享库时自动添加此标志)。

  • config_options():此方法用于在选项取值之前约束包中可用的选项。 如果为在此方法内部删除的设置或选项分配了值,Conan 将引发错误。 在本例中,我们在 Windows 上删除 fPIC 选项,因为该选项在该操作系统中不存在。 请注意,此方法在 configure() 方法之前执行。

请注意,使用 config_options() 方法删除选项与使用 configure() 方法删除选项具有不同的结果。 在 config_options() 中删除选项就像我们从未在 recipe 中声明它一样,这将引发异常,提示该选项不存在。 但是,如果我们在 configure() 方法中删除它,我们可以传递该选项,但它将不起作用。 例如,如果您尝试在 Windows 上为 fPIC 选项传递值,Conan 将引发错误,提示该选项不存在

Windows
$ conan create . --build=missing -o fPIC=True
...
-------- Computing dependency graph --------
ERROR: option 'fPIC' doesn't exist
Possible options are ['shared', 'with_fmt']

如您所见,configure()config_options() 方法在满足某些条件时会删除选项。 让我们解释一下我们为什么要这样做,以及删除该选项的含义。 这与 Conan 如何识别与 profile 中设置的配置二进制兼容的包有关。 在下一节中,我们将介绍 Conan 包 ID 的概念。

Conan 包二进制兼容性:包 ID

在之前的示例中,我们使用 Conan 为不同的配置(如 DebugRelease)构建。 每次您为其中一个配置创建包时,Conan 都会构建一个新的二进制文件。 它们中的每一个都与一个称为 包 ID生成的哈希值相关联。 包 ID 只是将一组设置、选项和有关包需求的信息转换为唯一标识符的一种方式。

让我们为 ReleaseDebug 配置构建我们的包,并检查生成的二进制包 ID。

$ conan create . --build=missing -s build_type=Release -tf="" # -tf="" will skip building the test_package
...
[ 50%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o
[100%] Linking CXX static library libhello.a
[100%] Built target hello
hello/1.0: Package '738feca714b7251063cc51448da0cf4811424e7c' built
hello/1.0: Build folder /Users/user/.conan2/p/tmp/7fe7f5af0ef27552/b/build/Release
hello/1.0: Generated conaninfo.txt
hello/1.0: Generating the package
hello/1.0: Temporary package folder /Users/user/.conan2/p/tmp/7fe7f5af0ef27552/p
hello/1.0: Calling package()
hello/1.0: CMake command: cmake --install "/Users/user/.conan2/p/tmp/7fe7f5af0ef27552/b/build/Release" --prefix "/Users/user/.conan2/p/tmp/7fe7f5af0ef27552/p"
hello/1.0: RUN: cmake --install "/Users/user/.conan2/p/tmp/7fe7f5af0ef27552/b/build/Release" --prefix "/Users/user/.conan2/p/tmp/7fe7f5af0ef27552/p"
-- Install configuration: "Release"
-- Installing: /Users/user/.conan2/p/tmp/7fe7f5af0ef27552/p/lib/libhello.a
-- Installing: /Users/user/.conan2/p/tmp/7fe7f5af0ef27552/p/include/hello.h
hello/1.0 package(): Packaged 1 '.h' file: hello.h
hello/1.0 package(): Packaged 1 '.a' file: libhello.a
hello/1.0: Package '738feca714b7251063cc51448da0cf4811424e7c' created
hello/1.0: Created package revision 3bd9faedc711cbb4fdf10b295268246e
hello/1.0: Full package reference: hello/1.0#e6b11fb0cb64e3777f8d62f4543cd6b3:738feca714b7251063cc51448da0cf4811424e7c#3bd9faedc711cbb4fdf10b295268246e
hello/1.0: Package folder /Users/user/.conan2/p/5c497cbb5421cbda/p

$ conan create . --build=missing -s build_type=Debug -tf="" # -tf="" will skip building the test_package
...
[ 50%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o
[100%] Linking CXX static library libhello.a
[100%] Built target hello
hello/1.0: Package '3d27635e4dd04a258d180fe03cfa07ae1186a828' built
hello/1.0: Build folder /Users/user/.conan2/p/tmp/19a2e552db727a2b/b/build/Debug
hello/1.0: Generated conaninfo.txt
hello/1.0: Generating the package
hello/1.0: Temporary package folder /Users/user/.conan2/p/tmp/19a2e552db727a2b/p
hello/1.0: Calling package()
hello/1.0: CMake command: cmake --install "/Users/user/.conan2/p/tmp/19a2e552db727a2b/b/build/Debug" --prefix "/Users/user/.conan2/p/tmp/19a2e552db727a2b/p"
hello/1.0: RUN: cmake --install "/Users/user/.conan2/p/tmp/19a2e552db727a2b/b/build/Debug" --prefix "/Users/user/.conan2/p/tmp/19a2e552db727a2b/p"
-- Install configuration: "Debug"
-- Installing: /Users/user/.conan2/p/tmp/19a2e552db727a2b/p/lib/libhello.a
-- Installing: /Users/user/.conan2/p/tmp/19a2e552db727a2b/p/include/hello.h
hello/1.0 package(): Packaged 1 '.h' file: hello.h
hello/1.0 package(): Packaged 1 '.a' file: libhello.a
hello/1.0: Package '3d27635e4dd04a258d180fe03cfa07ae1186a828' created
hello/1.0: Created package revision 67b887a0805c2a535b58be404529c1fe
hello/1.0: Full package reference: hello/1.0#e6b11fb0cb64e3777f8d62f4543cd6b3:3d27635e4dd04a258d180fe03cfa07ae1186a828#67b887a0805c2a535b58be404529c1fe
hello/1.0: Package folder /Users/user/.conan2/p/c7796386fcad5369/p

如您所见,Conan 生成了两个包 ID

  • Release 的包 738feca714b7251063cc51448da0cf4811424e7c

  • Debug 的包 3d27635e4dd04a258d180fe03cfa07ae1186a828

这两个包 ID 是通过获取设置、选项和一些关于需求的信息集(我们将在文档后面解释这一点)并计算它们的哈希值来计算的。 因此,例如,在本例中,它们是下图所示信息的​​结果。

../../_images/conan-package_id.png

这些包 ID 不同,因为 build_type 不同。 现在,当您想要安装包时,Conan 将

  • 收集应用的设置和选项,以及有关需求的一些信息,并计算相应包 ID 的哈希值。

  • 如果该包 ID 与本地 Conan 缓存中存储的包之一匹配,Conan 将使用该包。 否则,如果我们配置了任何 Conan 远程仓库,它将在远程仓库中搜索具有该包 ID 的包。

  • 如果计算出的包 ID 在本地缓存和远程仓库中都不存在,Conan 将失败并显示“缺少二进制文件”错误消息,或者将尝试从源代码构建该包(这取决于 --build 参数的值)。 此构建将在本地缓存中生成新的包 ID。

这些步骤已简化,包 ID 计算远不止我们在此处解释的内容。 Recipes 本身甚至可以调整其包 ID 计算,除了包 ID 之外,我们还可以有不同的 recipe 和包修订版本,并且 Conan 中还有一个内置机制可以配置为声明具有特定包 ID 的某些包彼此兼容。

也许您现在对我们为什么在 Conan recipes 中删除设置或选项有了直觉。 如果您这样做,这些值将不会包含在包 ID 的计算中,因此即使您定义了它们,生成的包 ID 也将相同。 您可以使用 fPIC 选项检查此行为,例如,当我们使用选项 shared=True 构建时,该选项将被删除。 无论您为 fPIC 选项传递什么值,为 hello/1.0 二进制文件生成的包 ID 都将相同

Windows
$ conan create . --build=missing -o shared=True -o fPIC=True -tf=""
Linux, macOS
$ conan create . --build=missing -o shared=True -o -tf=""
...
hello/1.0 package(): Packaged 1 '.h' file: hello.h
hello/1.0 package(): Packaged 1 '.dylib' file: libhello.dylib
hello/1.0: Package '2a899fd0da3125064bf9328b8db681cd82899d56' created
hello/1.0: Created package revision f0d1385f4f90ae465341c15740552d7e
hello/1.0: Full package reference: hello/1.0#e6b11fb0cb64e3777f8d62f4543cd6b3:2a899fd0da3125064bf9328b8db681cd82899d56#f0d1385f4f90ae465341c15740552d7e
hello/1.0: Package folder /Users/user/.conan2/p/8a55286c6595f662/p

$ conan create . --build=missing -o shared=True -o fPIC=False -tf=""
...
-------- Computing dependency graph --------
Graph root
    virtual
Requirements
    fmt/8.1.1#601209640bd378c906638a8de90070f7 - Cache
    hello/1.0#e6b11fb0cb64e3777f8d62f4543cd6b3 - Cache

-------- Computing necessary packages --------
Requirements
    fmt/8.1.1#601209640bd378c906638a8de90070f7:d1b3f3666400710fec06446a697f9eeddd1235aa#24a2edf207deeed4151bd87bca4af51c - Skip
    hello/1.0#e6b11fb0cb64e3777f8d62f4543cd6b3:2a899fd0da3125064bf9328b8db681cd82899d56#f0d1385f4f90ae465341c15740552d7e - Cache

-------- Installing packages --------

-------- Installing (downloading, building) binaries... --------
hello/1.0: Already installed!

如您所见,第一次运行创建了 2a899fd0da3125064bf9328b8db681cd82899d56 包,而第二次运行,无论 fPIC 选项的值如何,都表示我们已经安装了 2a899fd0da3125064bf9328b8db681cd82899d56 包。

C 库

在其他典型情况下,您可能想要删除某些设置。 假设您正在打包一个 C 库。 当您构建此库时,有些设置(如编译器 C++ 标准 (self.settings.compiler.cppstd) 或使用的标准库 (self.settings.compiler.libcxx))根本不会影响生成的二进制文件。 那么它们影响包 ID 计算就没有意义了,因此典型的模式是在 configure() 方法中删除它们

def configure(self):
    self.settings.rm_safe("compiler.cppstd")
    self.settings.rm_safe("compiler.libcxx")

请注意,在 configure() 方法中删除这些设置不仅会修改包 ID 计算,还会影响工具链和构建系统集成的工作方式,因为 C++ 设置不存在。

注意

从 Conan 2.4 开始,如果定义了 languages = "C" recipe 属性(实验性),则上述 configure() 不是必需的。

仅头文件库

对于打包 仅头文件库 的包,也会发生类似的情况。 在这种情况下,我们不需要链接任何二进制代码,而只需要将一些头文件添加到我们的项目中。 因此,Conan 包的包 ID 不应受设置或选项的影响。 对于这种情况,有一种更简单的方法可以声明生成的包 ID 不应考虑设置、选项或来自需求的任何信息:在另一个名为 package_id() 的 recipe 方法中调用 self.info.clear() 方法

def package_id(self):
    self.info.clear()

我们稍后将解释 package_id() 方法,并解释如何自定义包的包 ID 计算方式。 您还可以查看 Conanfile 的方法参考,如果您想更详细地了解此方法的工作原理。

另请参阅