在 recipe 中配置设置和选项

我们已经解释了 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 如何识别与配置文件中设置的配置具有二进制兼容性的软件包有关。在下一节中,我们将介绍 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 recipe 中删除设置或选项。如果你这样做,这些值将不会被包含在软件包 ID 的计算中,因此即使你定义了它们,最终的软件包 ID 也将是相同的。例如,你可以使用 shared=True 选项构建时被删除的 fPIC 选项来验证此行为。无论你为 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() 方法不是必需的。

Header-only 库

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

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

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

另请参阅