在配方中配置设置和选项¶
我们已经解释了Conan 设置和选项,以及如何使用它们为不同的配置(如 Debug、Release、静态库或共享库等)构建项目。在本节中,我们将解释如何在某个设置或选项不适用于 Conan 软件包的情况下配置这些设置和选项。我们将简要介绍 Conan 如何对二进制兼容性进行建模,以及它与选项和设置之间的关系。
请首先克隆源代码以重新创建此项目。您可以在 GitHub 上的 examples2 仓库中找到它们
$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/creating_packages/configure_options_settings
您会注意到之前配方的 **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")
...
您可以看到我们在配方中添加了configure() 方法。让我们解释一下此方法的目标以及它与我们在配方中已定义的 config_options()
方法有何不同
configure()
:使用此方法配置配方中哪些选项或设置可用。例如,在本例中,我们**删除了 fPIC 选项**,因为只有在我们将库构建为共享库时,它才应为 **True**(实际上,某些构建系统在构建共享库时会自动添加此标志)。config_options()
:此方法用于**约束**软件包中可用的选项,**在它们取值之前**。如果为在此方法内部删除的设置或选项赋值,Conan 将引发错误。在本例中,我们在 Windows 中**删除了 fPIC 选项**,因为该选项对于该操作系统不存在。请注意,此方法在configure()
方法之前执行。
请注意,使用 config_options()
方法删除选项与使用 configure()
方法删除选项的结果不同。在 config_options()
中删除该选项**就像我们从未在配方中声明它一样**,这将引发一个异常,指出该选项不存在。但是,如果我们在 configure()
方法中删除它,我们可以传递该选项,但它将不起作用。例如,如果您尝试将值传递给 Windows 中的 fPIC
选项,Conan 将引发一个错误,警告该选项不存在
$ 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 为不同的配置(如 *Debug* 和 *Release*)构建。每次为这些配置之一创建软件包时,Conan 都会构建一个新的二进制文件。每个二进制文件都与一个称为**软件包 ID** 的**生成的哈希值**相关。软件包 ID 只是一种将一组设置、选项以及有关软件包需求的信息转换为唯一标识符的方法。
让我们为 *Release* 和 *Debug* 配置构建软件包,并检查生成的二进制软件包 ID。
$ conan create . --build=missing -s build_type=Release -tf="" # -tf="" will skip ng 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 是通过获取**一组设置、选项以及有关需求的某些信息**(我们将在文档后面解释)并**使用它们计算哈希值**来计算的。因此,例如,在本例中,它们是下图所示的信息的结果。
这些软件包 ID 不同,因为 **build_type** 不同。现在,当您想安装软件包时,Conan 将
收集应用的设置和选项,以及有关需求的某些信息,并计算相应软件包 ID 的哈希值。
如果该软件包 ID 与本地 Conan 缓存中存储的软件包之一匹配,Conan 将使用该软件包。如果没有,并且我们配置了任何 Conan 远程仓库,它将在远程仓库中搜索具有该软件包 ID 的软件包。
如果计算出的软件包 ID 不存在于本地缓存和远程仓库中,Conan 将失败并显示“缺少二进制文件”错误消息,或者将尝试从源代码构建该软件包(这取决于
--build
参数的值)。此构建将在本地缓存中生成一个新的软件包 ID。
这些步骤是简化的,软件包 ID 的计算远不止我们此处解释的内容,配方本身甚至可以调整其软件包 ID 的计算,除了软件包 ID 之外,我们还可以有不同的配方和软件包修订版,Conan 中还有一个内置机制可以配置以声明具有特定软件包 ID 的某些软件包与其他软件包兼容。
也许您现在已经对我们在 Conan 配方中删除设置或选项的原因有所了解。如果您这样做,这些值将不会添加到软件包 ID 的计算中,因此即使您定义它们,生成的软件包 ID 也将相同。您可以检查此行为,例如,对于当我们在使用 shared=True
选项构建时删除的 fPIC 选项。无论您为 fPIC 选项传递什么值,为 **hello/1.0** 二进制文件生成的软件包 ID 都将相同
$ conan create . --build=missing -o shared=True -o fPIC=True -tf=""
$ 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++ 标准 ( 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 软件包的软件包 ID 不应受到设置或选项的影响。对于这种情况,有一种简化的方法可以声明生成的软件包 ID 不应考虑设置、选项或需求的任何信息,即在另一个名为 package_id()
的配方方法中使用 self.info.clear()
方法
def package_id(self):
self.info.clear()
我们将在后面解释 package_id()
方法,并说明如何自定义计算软件包 ID 的方式。如果您想更详细地了解此方法的工作原理,也可以查看 Conanfile 的方法参考。
另请参阅
查看二进制兼容性 compatibility.py 扩展。
Conan 软件包类型。
阅读 二进制模型参考,全面了解 Conan 二进制模型。