我们很高兴推出备受期待的功能,这将显著增强 Conan 用户(包括软件供应商)管理和分发其软件包的方式:“供应商”功能。此新功能旨在简化 Conan 软件包的部署和共享流程,提供对内部配方和二进制文件的更多控制,**无需公开专有详细信息**,或者只是隔离跨组织团队的实现细节。让我们深入了解“供应商”软件包带来的优势以及它们如何改善您的工作流程。

什么是“供应商包”?

“供应商”功能允许开发人员通过 Conan 分发其软件,同时保持内部依赖项和配方私密。通过在 Conan 配方中启用 vendor 属性,您可以阻止 Conan 下载软件包依赖项的配方和二进制文件。这意味着您可以将所有必要的二进制文件、库和其他工件封装在您的软件包中,确保最终用户无法访问您的内部软件包配方、构建详细信息或私有存储库。

主要优势

  1. 增强的隐私和安全性

    通过使用 vendor 功能,您可以共享您的软件包,而无需公开内部依赖项的配方和二进制文件。这对维护专有代码和内部构建流程的机密性至关重要。

  2. 简化的分发

    • vendor 功能简化了分发流程。无论您使用 Conan Center Index 还是私有工件存储库,都可以包含针对各种配置的预构建二进制文件,确保最终用户收到一个现成的软件包,而无需额外下载。
    • 供应商功能在组织内部也很有用,因为它允许共享预编译的二进制文件,这些二进制文件完全隐藏其依赖项,以实现安全性、简便性或便利性。
    • 不同工作组之间的 SDK,无需共享内部细节
  3. 减少构建时间

    当使用者安装供应商配方时,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。这将启用供应商功能
  • 从选项和默认选项中删除不必要的共享选项,并删除配置方法,因为它不再需要
  • 实际上将 lib_a 库重新打包到 SDK 中。由于我们正在生成静态库,因此可以通过将 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 

图形比较

Standard sdk graph 标准 SDK 图形
Vendored sdk graph 供应商化 SDK 图形


注意:红色虚线边框表示软件包正在对其依赖项进行供应商化

如何使用“供应商”功能?

要发布供应商化软件包,创建者应遵循以下步骤

  1. 依赖项封装

    确保所有依赖项都被正确地重新打包到供应商化软件包中。这包括将二进制文件、静态库和共享库封装到供应商化域中。用户可以使用供应商化软件包与不同类型的环境、依赖项、启用的共享库等进行交互。软件包创建者有责任确保不会出现冲突。

  2. 供应商化软件包的分发

    在分发供应商化软件包时,应为使用者可能需要的不同配置生成预构建二进制文件。这样,用户就可以使用供应商化软件包,而无需从头开始编译它,也无需直接访问软件包依赖项(这些依赖项可能是组织内部私有的)。

高级细节

要了解“供应商”功能的内部工作原理,必须掌握 Conan 中依赖项图的概念。当 Conan 为软件包构建依赖项图时,它会下载所有依赖项的配方和二进制文件,从而构建所有相关关系和配置的详细映射。

依赖项图是 Conan 中的关键组件,它代表了软件包之间的依赖关系。它确保所有必要的二进制文件和库都被正确解析并兼容。通常,Conan 会完全扩展此图,下载所有涉及的配方和二进制文件。但是,通过启用 vendor 功能,您可以限制此扩展。

限制图形扩展

通过启用 vendor 选项,配方使用者会收到指示,不要将依赖项图扩展到供应商化软件包之外。这意味着 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
Vendored expanded graph 供应商化扩展图形


注意:红色虚线边框表示软件包是供应商,黄色背景表示软件包已被强制构建

 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 进行愉快打包!