引入供应商包:创建和共享与其依赖项分离的包
我们很高兴推出备受期待的功能,该功能将显著增强 Conan 用户(包括软件供应商)管理和分发其软件包的方式:“供应商”功能。此新增功能旨在简化 Conan 软件包的部署和共享流程,提供对内部配方和二进制文件的更多控制权,**无需公开专有详细信息**,或者只是隔离跨组织团队的实现细节。让我们深入了解“供应商”软件包带来的优势以及它们如何使您的工作流程受益。
什么是“供应商包”?
“供应商”功能允许开发人员通过 Conan 分发其软件,同时保持内部依赖项和配方私密。通过在您的 Conan 配方中启用供应商属性,您可以阻止 Conan 下载软件包依赖项的配方和二进制文件。这意味着您可以将所有必要的二进制文件、库和其他工件封装在您的软件包中,确保最终用户无法访问您的内部软件包配方、构建详细信息或私有存储库。
主要优势
-
增强的隐私和安全性
通过使用供应商功能,您可以共享您的软件包,而无需公开内部依赖项的配方和二进制文件。这对维护专有代码和内部构建流程的机密性至关重要。
-
简化的分发
- 供应商功能简化了分发流程。无论您使用的是 Conan Center Index 还是私有工件存储库,都可以包含针对各种配置的预构建二进制文件,确保最终用户收到一个现成的软件包,而无需额外下载。
- 供应商功能在组织内部也很有用,因为它允许共享预编译的二进制文件,这些二进制文件完全隐藏其依赖项,出于安全、简单或方便的原因。
- 不同工作组之间的 SDK,无需共享内部信息
-
减少构建时间
当使用者安装供应商配方时,Conan 不会从服务器下载单个依赖项二进制文件或配方,这可能会节省大量时间和存储空间,尤其是在生产环境中。
使用示例
对于此示例,请确保您已安装至少 Conan v2.4.1。
1. 从 CMake 模板创建一个基本库
$ mkdir vendor-example && cd vendor-example
$ mkdir lib_a && cd lib_a
$ conan new cmake_lib -d name=lib_a -d version=1.0
$ conan create .
2. 创建一个依赖于先前库的软件包,我们将使用它来为其依赖项提供供应商
$ cd .. && mkdir sdk && cd sdk
$ conan new cmake_lib -d name=sdk -d version=1.0 -d requires="lib_a/1.0"
$ conan create .
3. 创建一个依赖于 sdk 库的使用者应用程序
$ cd .. && mkdir app && cd app
$ conan new cmake_exe -d name=app -d version=1.0 -d requires="sdk/1.0"
4. 安装创建的应用程序
$ conan install . --build=missing
======== Computing dependency graph ========
...
Requirements
lib_a/1.0#ab64452c42599a3dc0ee6a0dc90bbd90 - Cache
sdk/1.0#1cb781c232f63845b7943764d8a084ed - Cache
======== Computing necessary packages ========
Requirements
lib_a/1.0#ab64452c42599a3dc0ee6a0dc90bbd90:39f48664f195e0847f59889d8a4cdfc6bca84bf1#e34a89988cafb2bf67f6adf40b06f442 - Cache
sdk/1.0#1cb781c232f63845b7943764d8a084ed:12ffb661ea06cee312194b5f6acd48e8236b8ed8#9127cf762dfd1a1f505ecd1d3ac056b9 - Cache
传递依赖项按预期需要。
5. 生成图形以查看稍后的比较
$ conan graph info . --format=html > graph.html
6. 现在让我们深入了解供应商功能。需要在 SDK conanfile.py
中进行一些更改
- 由于此示例旨在创建供应商**静态库**,因此我们应该首先相应地更改
package_type
- 将类属性
vendor
设置为True
。这将启用供应商功能 - 从选项和 default_options 中删除不必要的共享选项,并删除 configure 方法,因为它不再需要
- 实际上重新打包 SDK 内部的 lib_a 库。由于我们正在生成静态库,因此我们可以通过将
liblib_a.a
静态库复制到 SDK 库中来实现此目的 - 最后,更新
cpp_info.libs
,为使用者添加lib_a
依赖项
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps
from conan.tools.files import copy
import os
class sdkRecipe(ConanFile):
name = "sdk"
version = "1.0"
package_type = "static-library" # LINE CHANGE
vendor = True # LINE CHANGE
# Binary configuration
settings = "os", "compiler", "build_type", "arch"
options = {"fPIC": [True, False]} # LINE CHANGE
default_options = {"fPIC": True} # LINE CHANGE
# Sources are located in the same place as this recipe, copy them to the recipe
exports_sources = "CMakeLists.txt", "src/*", "include/*"
def config_options(self):
if self.settings.os == "Windows":
self.options.rm_safe("fPIC")
def layout(self):
cmake_layout(self)
def requirements(self):
self.requires("lib_a/1.0")
def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
def package(self):
# Repackage static dependencies inside the package supporting different OS
# LINES CHANGE
copy(self, "*.a", src=self.dependencies["lib_a"].cpp_info.libdir, dst=os.path.join(self.package_folder, self.cpp_info.libdir))
copy(self, "*.lib", src=self.dependencies["lib_a"].cpp_info.libdir, dst=os.path.join(self.package_folder, self.cpp_info.libdir))
cmake = CMake(self)
cmake.install()
def package_info(self):
self.cpp_info.libs = ["sdk", "lib_a"] # LINES CHANGE
7. 应用更改并创建 SDK 软件包
$ cd ../sdk
$ conan create .
8. 最后,让我们再次重新安装应用程序。
$ cd ../app
$ conan install . --build=missing
======== Computing dependency graph ========
Graph root
...
Requirements
sdk/1.0#f2fd2a793849725303073d37b15042b2 - Cache
======== Computing necessary packages ========
Requirements
sdk/1.0#f2fd2a793849725303073d37b15042b2:5d605f63db975c8c6004cc0a0b5c99c99dce6cc3#92c538ec767c2ff02a2fddf6b4106d02 - Cache
可以看出,在计算依赖项图时,conan 既不检索 lib_a/1.0
的配方也不检索二进制文件。
9. 我们可以尝试甚至从本地缓存中删除 lib_a
并再次安装应用程序
$ conan remove "lib_a*" -c
Found 1 pkg/version recipes matching lib_a* in local cache
Remove summary:
Local Cache
lib_a/1.0#ab64452c42599a3dc0ee6a0dc90bbd90: Removed recipe and all binaries
$ conan install .
...
Requirements
sdk/1.0#f2fd2a793849725303073d37b15042b2 - Cache
...
Install finished successfully
它有效!这就是供应商功能大放异彩的地方。软件包创建者可以分发其软件包,而无需分发其私有依赖项。
10. 使用供应商 SDK 生成应用程序的图形信息
$ conan graph info . --format=html > vendored-graph.html
图形之间的比较


注意:红色虚线边框表示软件包正在为其依赖项提供供应商
如何使用“供应商”功能?
要发布供应商软件包,创建者应遵循以下步骤
-
依赖项的封装
确保所有依赖项都正确地重新打包在供应商软件包中。这包括将二进制文件、静态库和共享库封装在供应商域中。用户可以使用供应商软件包与不同类型的环境、依赖项、启用的共享库等。软件包创建者负责确保不会出现任何冲突。
-
供应商软件包的分发
在分发供应商软件包时,应为您的使用者可能需要的不同配置生成预构建的二进制文件。这样,用户就可以使用供应商软件包,而无需从头开始编译,也无需直接访问软件包依赖项(对于组织而言可能是私有的)。
高级细节
要了解“供应商”功能的内部工作原理,必须掌握 Conan 中依赖项图的概念。当 Conan 为软件包构建依赖项图时,它会下载所有依赖项的配方和二进制文件,构建所有相关关系和配置的详细映射。
依赖项图是 Conan 中的一个关键组件,表示软件包如何相互依赖。它确保所有必要的二进制文件和库都得到正确解析并兼容。通常,Conan 会完全扩展此图,下载所有涉及的配方和二进制文件。但是,启用供应商功能后,您可以限制此扩展。
限制图形扩展
通过启用供应商选项,配方使用者会被指示不要将依赖项图扩展到供应商软件包之外。这意味着 Conan 不会下载内部依赖项的配方或二进制文件,从而使构建过程保持精简和安全。此功能对于以下情况特别有用:
- 封装:隐藏和保护私有依赖项。
- 效率:通过不获取不必要的组件来减少下载和构建时间。
- 控制:允许软件供应商管理其软件包的使用和分发方式,而无需公开内部详细信息。
强制构建供应商软件包
我们已经了解了如何从直接下载二进制文件而无需下载任何依赖项数据的使用者使用供应商。
但是,如果我们想编译内部供应商依赖项会发生什么?
1. 由于我们之前已删除 lib_a
软件包,因此我们应该重新创建它
$ cd ../lib_a && conan create .
现在,让我们强制构建我们之前的 SDK 示例
$ cd ../app
$ conan install . --build="sdk/1.0"
...
======== Computing necessary packages ========
sdk/1.0: Forced build from source
Requirements
sdk/1.0#f2fd2a793849725303073d37b15042b2:5d605f63db975c8c6004cc0a0b5c99c99dce6cc3 - Invalid
ERROR: There are invalid packages:
sdk/1.0: Invalid: The package 'sdk/1.0' is a vendoring one, needs to be built from source, but it didn't enable 'tools.graph:vendor=build' to compute its dependencies
尝试构建供应商软件包默认情况下会失败,除非将 tools.graph:vendor
配置设置为“build”。
2. 一旦启用 vendor
配置,用户必须能够访问打包的依赖项,就像它是一个普通软件包一样。
$ conan install . --build="sdk/1.0" -c tools.graph:vendor=build
...
======== Computing necessary packages ========
sdk/1.0: Forced build from source
Requirements
lib_a/1.0#ab64452c42599a3dc0ee6a0dc90bbd90:39f48664f195e0847f59889d8a4cdfc6bca84bf1#5a8bfa1c980c2008e7e24996a4b48477 - Cache
sdk/1.0#f2fd2a793849725303073d37b15042b2:5d605f63db975c8c6004cc0a0b5c99c99dce6cc3 - Build
======== Installing packages ========
lib_a/1.0: Already installed! (1 of 2)
...
Install finished successfully
在强制编译供应商依赖项时,图形会再次扩展,显示构建所需的封装依赖项。
3. 让我们再次生成图形以查看扩展的工作原理
$ conan graph info . --build="sdk/1.0" -c tools.graph:vendor=build --format=html > vendored-expanded-graph.html

注意:红色虚线边框表示软件包是供应商,黄色背景表示软件包已被强制构建
4. 为了验证供应商软件包是否不需要访问其传递依赖项(除非强制构建),我们可以尝试从本地缓存中删除我们的 lib_a
软件包并再次安装
$ conan remove "lib_a*" -c
Found 1 pkg/version recipes matching lib_a* in local cache
Remove summary:
Local Cache
lib_a/1.0#ab64452c42599a3dc0ee6a0dc90bbd90: Removed recipe and all binaries
$ conan install . --build="sdk/1.0" -c tools.graph:vendor=build
...
======== Computing dependency graph ========
lib_a/1.0: Not found in local cache, looking in remotes...
lib_a/1.0: Checking remote: conancenter
...
Requirements
sdk/1.0#f2fd2a793849725303073d37b15042b2 - Cache
ERROR: Package 'lib_a/1.0' not resolved: Unable to find 'lib_a/1.0' in remotes.
正如预期的那样,如果我们无法访问其依赖项,则在强制构建供应商软件包时会引发“错误:未解析软件包‘lib_a/1.0’”。
代理供应商以获得额外的隐私
对于希望进一步模糊其依赖项名称和版本的供应商,可以创建一个代理供应商软件包。此代理软件包应包含所有内部/私有依赖项,并且应使其对客户端不可访问。
主要供应商配方依赖于此代理软件包,并且通过将这两个软件包都标记为供应商,实际的依赖项及其版本将对最终用户隐藏。
对软件包 ID 计算的影响
为了支持供应商功能,我们调整了为供应商软件包计算 package_id
的方式。由于依赖项及其版本对最终用户不可见,因此它们被排除在 package_id
计算之外。只有配方修订版会随配方内容更改而更改,但 package_id
将保持不变。这确保了对内部依赖项的任何更改(更新、添加或删除)都不会更改 package_id
,从而节省了在软件包 ID 一致至关重要的环境中的时间和精力。
不用说,打包软件包的提供者应负责在任何内部依赖项更改版本时更新软件包的版本,以便让使用者知道软件包已以某种方式更改。
结论
“供应商”功能是任何希望通过 Conan 分发软件同时保持对内部依赖项控制的组织的强大工具。通过封装和保护您的二进制文件,您可以确保顺利高效的分发流程、增强的隐私和显着的节省时间。我们很高兴将此功能带给社区,并期待看到它如何增强您的工作流程。
另一方面,我们建议避免滥用此新功能,例如,我们认为它在 conan-center-index
或其他远程存储库中没有任何位置,除非可能是某些应用程序类型软件包(工具需要)。
敬请期待更多更新,并一如既往地享受使用 Conan 进行打包的乐趣!