ConanCenter 是 C 和 C++ 库的中央开源仓库,它是一个成功案例,包含大量 C 和 C++ 开源包。它每天处理数十万个下载请求,从 中央 GitHub 仓库 构建其包,该仓库以特定的文件夹布局进行组织,仅在 2023 年就收到了来自贡献者的近 6000 个拉取请求。

然而,ConanCenter 并不适合所有用例。例如,它不能包含闭源库或可能对社区仍然有益的工具。此外,一些组织,特别是大型企业,更喜欢不使用从互联网下载的二进制文件。相反,他们使用 conan-center-index 食谱在内部构建自己的二进制文件。这些组织通常需要自定义这些食谱以满足对更广泛社区不适用的独特需求,这使得此类贡献不适合上游仓库。Conan 文档 承认这种做法,建议为这些需求从 conan-center-index 的分支进行操作。

出于所有这些原因,在 2.2.0 版本中,Conan 引入了一种名为 local-recipes-index 的新仓库类型,旨在通过允许 Conan 远程指向具有特定布局的 Conan 食谱的本地副本,提供更大的灵活性。

local-recipes-index 允许用户维护一个具有与 conan-center-index GitHub 仓库相同结构的本地文件夹,并将其用作包食谱的来源。这种新型仓库是仅包含食谱的,因此需要在使用包的每台机器上从源代码构建包二进制文件。为了在团队之间共享二进制文件,我们仍然建议 使用像 Artifactory 这样的 Conan 远程服务器 用于生产目的。

Diagram of the general workflow

在这篇文章中,我们将探讨此功能如何促进以下操作

  • 此功能使贡献者能够与社区共享包食谱,这些食谱可能由于各种原因(如许可证限制或二进制分发策略)不适合 ConanCenter。例如,这可能是 CUDA 或其他专有库,它们以预编译的闭源二进制文件形式分发。

  • 它简化了在 Conan 文档中概述 的最佳实践的采用,这些最佳实践适用于需要自定义构建的二进制文件或修改后的 ConanCenter 食谱以满足独特需求的组织。这种方法赋予用户对其第三方依赖项的完全控制权,确保它们既健壮又完全可定制。

接下来,我们将深入探讨实际示例,以演示 local-recipes-index 仓库的这两个用例。

在你的食谱中使用 local-recipes-index 仓库

在本节中,我们将说明如何在某些库或工具(由于许可证限制或专有性质)不适合 ConanCenter 的情况下使用 local-recipes-index 功能。

为了演示目的,让我们使用 local_recipes_index 模板为假设的 hello 闭源库创建 local-recipes-index 仓库,该模板用于 conan new 命令

$ mkdir repo && cd repo
$ conan new local_recipes_index -d name=hello -d version=0.1 \
  -d url=https://github.com/conan-io/libhello/archive/refs/tags/0.0.1.zip \
  -d sha256=1dfb66cfd1e2fb7640c88cc4798fe25853a51b628ed9372ffc0ca285fe5be16b
$ cd ..

conan new local_recipes_index 命令创建了一个模板,该模板假设 CMake 是构建系统,以及其他一些重要的假设。在实践中,它需要进行自定义,但对于此演示,它可以正常工作。它将创建一个与 conan-center-index GitHub 仓库相等的文件夹布局

.
└── repo
    └── recipes
        └── hello
            ├── all
            │   ├── conandata.yml
            │   ├── conanfile.py
            │   └── test_package
            │       ├── CMakeLists.txt
            │       ├── conanfile.py
            │       └── src
            │           └── example.cpp
            └── config.yml

设置好仓库后,我们将它添加为 Conan 的本地远程仓库

$ conan remote add mylocalrepo ./repo --allowed-packages="hello/*"

请特别注意 --allowed-packages 参数。此参数确保除 hello 之外的所有包都被 Conan 丢弃。这可以用来最小化潜在的供应链攻击的攻击面。

现在你可以列出并安装来自此新仓库的包

$ conan list "*" -r=mylocalrepo
$ conan install --requires=hello/0.1 -r=mylocalrepo --build=missing

在这一点上,你可以将此仓库推送到你的 GitHub 帐户并与社区共享。请注意,正如我们之前提到的,此功能专门针对某些库不适合 ConanCenter 的情况。请记住,“local-recipes-index”仓库存在局限性:它不能完全复制,因为它只模拟版本而不是修订版,并且它不提供二进制文件。因此,在这些情况之外,建议使用像 Artifactory 这样的远程包服务器。

现在,用户只需克隆 GitHub 仓库并将克隆的文件夹添加为本地仓库即可。

从私有的 conan-center-index 分支构建二进制文件

Conan DevOps 指南 中所述,在许多情况下,组织需要通过构建自己的二进制文件来独立于 ConanCenter 运行。与公共上游 ConanCenter 服务器分离并从 conan-center-index 的分支构建你自己的二进制文件,如链接的文档页面中所建议的那样,可能有很多优势,包括绝对控制权和自定义食谱的可能性,使我们能够让仓库充当版本的快照,完全不受上游 ConanCenter 中可能的持续更改和新版本的影响,等等。

local-recipes-index 仓库允许你轻松地从 conan-center-index 的分支构建二进制文件,然后将它们托管在像 Artifactory 这样的 Conan 远程仓库中。与 Conan DevOps 指南中解释 的过程的主要区别在于能够立即测试多个本地更改,而无需在每次修改食谱时都进行导出。

请注意,在这种情况下,不建议将来自 ConanCenter 的二进制文件与本地构建的二进制文件混合使用,原因如下

  • 二进制兼容性:ConanCenter CI 和用户 CI 之间可能存在细微的设置差异。为所有二进制文件维护一致的设置可以缓解一些问题。

  • 对构建的完全控制:自己构建所有二进制文件可以确保你对编译环境和依赖项版本有完全控制权。

相反,建议从分支构建所有直接和传递依赖项。首先,删除上游 ConanCenter,因为它不会被使用,所有内容都将来自我们自己的分支

$ conan remote remove conancenter

然后我们将克隆我们的分支(在本例中,我们将直接克隆上游用于演示目的,但你应该克隆你的分支)

$ git clone https://github.com/conan-io/conan-center-index

将其添加为我们的 mycenter 远程仓库

# Add the mycenter remote pointing to the local folder
$ conan remote add mycenter ./conan-center-index

就是这样!现在你已准备好列出并使用来自你的 conan-center-index 本地文件夹的包

$ conan list "zlib/*" -r=mycenter
mycenter
zlib
    zlib/1.2.11
    zlib/1.2.12
    zlib/1.2.13
    zlib/1.3
    zlib/1.3.1

我们也可以从这个仓库安装包,例如,我们可以执行

$ conan install --requires=zlib/1.3
...
======== Computing dependency graph ========
zlib/1.3: Not found in local cache, looking in remotes...
zlib/1.3: Checking remote: mycenter
zlib/1.3: Downloaded recipe revision 5c0f3a1a222eebb6bff34980bcd3e024
Graph root
    cli
Requirements
    zlib/1.3#5c0f3a1a222eebb6bff34980bcd3e024 - Downloaded (mycenter)

======== Computing necessary packages ========
Requirements
    zlib/1.3#5c0f3a1a222eebb6bff34980bcd3e024:72c852c5f0ae27ca0b1741e5fd7c8b8be91a590a - Missing
ERROR: Missing binary: zlib/1.3:72c852c5f0ae27ca0b1741e5fd7c8b8be91a590a

正如我们所见,Conan 成功地从 mycenter 获取了 zlib/1.3 的食谱,但随后它失败了,因为没有二进制文件。这是预期的,**仓库只包含食谱,但不包含二进制文件**。我们可以使用 --build=missing 参数从源代码构建二进制文件

$ conan install --requires=zlib/1.3 --build=missing
...
zlib/1.3: package(): Packaged 2 '.h' files: zconf.h, zlib.h
zlib/1.3: package(): Packaged 1 file: LICENSE
zlib/1.3: package(): Packaged 1 '.a' file: libz.a
zlib/1.3: Created package revision 0466b3475bcac5c2ce37bb5deda835c3
zlib/1.3: Package '72c852c5f0ae27ca0b1741e5fd7c8b8be91a590a' created
zlib/1.3: Full package reference: zlib/1.3#5c0f3a1a222eebb6bff34980bcd3e024:72c852c5f0ae27ca0b1741e5fd7c8b8be91a590a#0466b3475bcac5c2ce37bb5deda835c3
zlib/1.3: Package folder /home/conan/.conan2/p/b/zlib1ed9fe13537a2/p
WARN: deprecated: Usage of deprecated Conan 1.X features that will be removed in Conan 2.X:
WARN: deprecated:     'cpp_info.names' used in: zlib/1.3

======== Finalizing install (deploy, generators) ========
cli: Generating aggregated env files
cli: Generated aggregated env files: ['conanbuild.sh', 'conanrun.sh']
Install finished successfully

现在我们可以在本地缓存中看到二进制包

$ conan list zlib:*
Local Cache
zlib
    zlib/1.3
    revisions
        5c0f3a1a222eebb6bff34980bcd3e024 (2024-04-10 11:50:34 UTC)
        packages
            72c852c5f0ae27ca0b1741e5fd7c8b8be91a590a
            info
                settings
                arch: x86_64
                build_type: Release
                compiler: gcc
                compiler.version: 9
                os: Linux
                options
                fPIC: True
                shared: False

最后,将二进制包上传到我们的 Artifactory 仓库,使其对我们的组织、用户和 CI 作业可用

$ conan remote add myartifactoryrepo <artifactory_url>
$ conan upload zlib* -r=myartifactoryrepo -c

这样,包的使用者不仅可以使用预编译的二进制文件,避免总是从源代码重新构建所有依赖项,而且还将提供更强大的保证,确保依赖项正确构建和工作,所有依赖项和传递依赖项能够很好地协同工作,等等。将二进制文件创建过程与二进制文件使用过程分离是实现更快、更可靠的依赖项使用方式。

请记住,在生产环境中,conan upload 命令应由 CI 执行,而不是由开发人员执行,遵循 Conan 指南。这种方法可以确保包使用者能够使用预编译的二进制文件并在依赖项之间保持一致性。

修改 local-recipes-index 仓库文件

这种方法的优势之一是,我们对每个食谱所做的所有更改都会自动对 Conan 客户端可用。例如,对 recipes/zlib/config.yml 文件的更改会立即被 Conan 客户端识别。如果编辑该文件并删除所有版本,只保留最新版本,然后我们 list 食谱

$ conan list "zlib/*" -r=mycenter
mycenter
zlib
    zlib/1.3.1

当某些食谱发生更改时,请注意,当前的 Conan 主目录已包含包的缓存副本,因此除非我们显式使用 --update(就像任何其他 Conan 远程仓库一样),否则它不会更新它。

因此,如果我们在 recipes/zlib/all/conanfile.py 中的 zlib 食谱中进行更改并重复

$ conan install --requires=zlib/1.3.1 -r=mycenter --update --build=missing

我们将立即在本地从源代码构建新的包二进制文件,该二进制文件来自我们 Conan 主目录中的新修改的食谱。

在生产中使用 local-recipes-index 仓库

在使用此新功能时,应考虑以下几个重要事项

  • 它专为 **第三方包** 设计,其中一个仓库中的食谱创建带有位于其他地方的源代码的包。要打包你自己的代码,建议使用标准实践,即在源代码中添加 conanfile.py 食谱,并使用标准的 conan create 流程。

  • local-recipes-index 仓库指向 **文件系统中的本地文件夹**。虽然用户可以选择将该文件夹与 git 仓库或其他版本控制机制同步,但 Conan 对此是不可知的,因为它只知道指向仓库(当前)状态的文件系统中的文件夹。用户可以选择直接运行 git 命令来切换分支/提交/标签,Conan 会自动识别这些更改

  • 这种方法在源代码级别运行,不会生成包二进制文件。为了部署开发和生产环境,必须使用像 Artifactory 这样的远程包服务器。重要的是要注意,此功能不是 Conan 的远程包服务器的替代品,远程包服务器在托管用于常规使用的包方面发挥着至关重要的作用。

  • 另外,请注意,服务器远程仓库可以保留更改的历史记录,存储多个食谱修订版。相反,local-recipes-index 远程仓库在任何给定时间只能表示单个快照。

此外,此功能不支持在食谱中直接放置服务器 URL;必须使用 conan remote add 显式添加远程仓库。将抽象的包需求(例如,“zlib/1.3.1”)与其特定来源分离对于正确解析依赖项和利用 Conan 的图能力至关重要,包括版本冲突检测和解析、版本范围解析、选择预发行版platform_requiresreplace_requires 等等。这种分离还有助于实现现代 DevOps 实践,例如包不变性、完全可重新定位性和包升级。

结论

local-recipes-index 仓库类型引入了一种新工具,使以前使用 Conan 1.X 不可能的工作流程成为可能

  • 它允许从 conan-center-index GitHub 仓库的分支轻松创建包。许多企业由于政策要求在食谱中进行私有定制,这些定制不适合合并到上游仓库,因此需要此功能。

  • 它为在 C/C++ 生态系统中打包不能包含在 ConanCenter 中的闭源库和工具提供了解决方案,使它们的食谱能够在 Conan 社区中共享和方便地使用。