频道和生成索引#

频道布局#

.
├── channeldata.json
├── linux-32
|   ├── repodata.json
│   └── package-0.0.0.tar.bz2
├── linux-64
|   ├── repodata.json
│   └── package-0.0.0.tar.bz2
├── win-64
|   ├── repodata.json
│   └── package-0.0.0.tar.bz2
├── win-32
|   ├── repodata.json
│   └── package-0.0.0.tar.bz2
├── osx-64
|   ├── repodata.json
│   └── package-0.0.0.tar.bz2
...

频道的一部分#

  • channeldata.json 包含有关频道的元数据,包括

    • 频道包含哪些子目录。

    • 频道中存在哪些包,以及它们位于哪些子目录中。

  • 子目录与平台相关联。例如,linux-64 子目录包含适用于 linux-64 系统的包。

  • repodata.json 包含子目录中包的索引。每个子目录将有自己的 repodata。

  • 频道在相应的子目录下包含包作为 tarball。

channeldata.json#

{
  "channeldata_version": 1,
  "packages": {
    "super-fun-package": {
      "activate.d": false,
      "binary_prefix": false,
      "deactivate.d": false,
      "home": "https://github.com/Home/super-fun-package",
      "license": "BSD",
      "post_link": false,
      "pre_link": false,
      "pre_unlink": false,
      "reference_package": "win-64/super-fun-package-0.1.0-py310_0.tar.bz2",
      "run_exports": {},
      "subdirs": [
        "win-64"
      ],
      "summary": "A fun package! Open me up for rainbows",
      "text_prefix": false,
      "version": "0.1.0"
    },
    "subdirs": [
      "win-64",
      ...
    ]
}

repodata.json#

{
  "packages": {
    "super-fun-package-0.1.0-py310_0.tar.bz2": {
      "build": "py37_0",
      "build_number": 0,
      "depends": [
        "some-depends"
      ],
      "license": "BSD",
      "md5": "a75683f8d9f5b58c19a8ec5d0b7f796e",
      "name": "super-fun-package",
      "sha256": "1fe3c3f4250e51886838e8e0287e39029d601b9f493ea05c37a2630a9fe5810f",
      "size": 3832,
      "subdir": "win-64",
      "timestamp": 1530731681870,
      "version": "0.1.0"
    },
    ...
  }

如何生成索引#

对于每个子目录

  • 查看子目录中存在的全部包。

  • 生成要添加/更新/删除的包列表。

  • 删除所有需要删除的包。

  • 对于所有需要添加/更新的包

    • 提取包以访问元数据,包括完整包名、文件修改时间 (mtime)、大小和 index.json

    • 将包元数据聚合到 repodata 集合中。

  • 应用 repodata 修补程序(补丁)。

  • 计算并保存缩减的 current_index.json 索引。

示例:构建频道#

要构建本地频道并将包放入其中,请按照以下说明操作

  1. 创建频道目录。

    $ mkdir local-channel
    $ cd local-channel
    
  2. 现在,下载您最喜欢的包。在我们的示例中,我们将使用 SciPy。接下来的步骤取决于您的平台

    1. Windows

      $ mkdir win-64
      $ curl -L https://anaconda.org/anaconda/scipy/1.9.1/download/win-64/scipy-1.9.1-py310h86744a3_0.tar.bz2 -o win-64\scipy-1.9.1-py310h86744a3_0.tar.bz2
      
    2. Linux

      1. 大多数 Linux 系统预装了 curl。如果您还没有安装,请安装它。

        1. 检查您是否拥有 curl

          $ which curl
          
        2. 如果未找到 curl,则安装它

          $ conda install curl
          
      2. 创建您要包含在频道中的包的本地副本

        $ mkdir linux-64
        $ curl -L https://anaconda.org/anaconda/scipy/1.9.1/download/linux-64/scipy-1.9.1-py310hd5efca6_0.tar.bz2 -o linux-64\scipy-1.9.1-py310hd5efca6_0.tar.bz2
        
    3. macOS,英特尔芯片

      $ mkdir osx-64
      $ curl -L https://anaconda.org/anaconda/scipy/1.9.1/download/osx-64/scipy-1.9.1-py310h09290a1_0.tar.bz2 -o osx-64/scipy-1.9.1-py310h09290a1_0.tar.bz2
      
    4. macOS,苹果芯片

      $ mkdir osx-arm64
      $ curl -L https://anaconda.org/anaconda/scipy/1.9.1/download/osx-arm64/scipy-1.9.1-py310h20cbe94_0.tar.bz2 -o osx-arm64/scipy-1.9.1-py310h20cbe94_0.tar.bz2
      
    5. 其他

      要在上述列表中未包含的平台上查找最新的 SciPy,请访问 Anaconda 包文件列表,用于 SciPy.

  3. 运行 conda index。这将为频道生成 channeldata.json 以及 linux-64 和 osx-64 子目录的 repodata.json,以及其他一些文件

    $ conda index .
    
  4. 通过搜索频道检查您的工作

    $ conda search -c file:/<path to>/local-channel scipy
    

    SciPy 应该在几个频道中可用,包括 local-channel

幕后更多细节#

缓存包元数据#

缓存利用了现有的 repodata.json 文件(如果存在)。索引检查要更新哪些文件,其依据是自上次创建 repodata.json 以来哪些文件是新的、已删除或已更改的。当包是新的或已更改时,其元数据将被提取并缓存到该包所属的子目录中。子文件夹是 .cache 文件夹。此文件夹有一个我们感兴趣的文件:stat.json,其中包含每个文件的 stat 命令的结果。这用于了解何时文件已更改并需要更新。在其他每个子文件夹中,每个包的提取的元数据文件都保存为原始包名加上 .json 扩展名。如果需要,拥有这些文件可以节省大量时间来完全重新创建索引。

题外话:.conda 包格式的一个设计目标是使索引尽可能快。为了实现这一点,.conda 格式将元数据与实际的包内容分开了。以前 .tar.bz2 容器需要提取整个包才能获得元数据,而新包格式允许提取元数据而不接触包内容。这使得索引速度与包大小无关。大型 .tar.bz2 包可能需要很长时间才能提取和索引。

通常,您无需手动更改缓存。要强制更新/重新扫描所有缓存的包,您可以删除 .cache 文件夹,或者只删除 .cache/stat.json 文件。理想情况下,您可以只从缓存中删除一个感兴趣的包,但此功能目前不存在。

Repodata 修补#

包 repodata 是从包内的 index.json 文件引导的。不幸的是,这些元数据并不总是正确的。有时需要追溯添加版本绑定。从包 index.json 文件中获取的值更改 repodata 的过程称为“热修复”。热修复很棘手,因为它有可能破坏已成功工作的环境,但有时也有必要修复已知无法工作的环境。

从 Python 脚本生成的 repodata 修补程序#

在您自己的服务器上,您可能可以运行您编写的任意 Python 代码来应用您的修补程序。这里的优势在于修补程序是在每次生成索引时动态生成的。这意味着自上次提交修补程序 Python 文件以来添加的任何新包都将被获取,并将对它们应用适当的热修复程序。

Anaconda 通过向 conda index 提供一个包含如何更改元数据的逻辑的 Python 文件来应用热修复程序。Anaconda 的热修复程序存储库位于 AnacondaRecipes/repodata-hotfixes

从 JSON 文件应用的 repodata 修补程序#

不幸的是,您不能总是直接运行您的 Python 代码 - 其他托管您修补程序的人员可能不允许您运行代码。您可以改为将修补程序打包为 .json 文件。这些文件将在应用时覆盖 repodata.json 中的条目。

例如,这是 conda-forge 必须采取的方法。他们的修补程序创建代码位于此处:conda-forge/conda-forge-repodata-patches-feedstock

该代码所做的是下载当前的 repodata.json,然后运行他们的 Python 逻辑以生成修补程序 JSON 文件。这些修补程序将被放置到 Anaconda 的镜像工具将找到它们并将其应用于 conda-forge 的 repodata.json 的位置,时间为镜像时间。

这里的缺点是,此 JSON 文件与上次 repodata-patches feedstock 生成包的时间一样新。在此期间添加的任何新包都不会应用任何热修复程序,因为热修复程序 JSON 文件不知道这些文件。

修剪到“当前”repodata#

可用的包数量一直在增长。这意味着 conda 始终需要做更多工作。为了减缓这种增长,在 conda 4.7 中,我们添加了使用替代 repodata.json 文件的功能,这些文件可能代表正常 repodata.json 的子集。其中一个特别的是 current_repodata.json,它表示

  1. 每个包的最新版本

  2. 使最新版本可满足的任何先前版本的依赖项

current_repodata.json 也只保留一种文件类型:.conda(如果可用),以及 .tar.bz2(如果只有 .tar.bz2 可用)。

对于 Anaconda 的默认“main”频道,current_repodata.json 文件的大小约为 repodata.json 的 1/7。这使得下载 repodata 速度更快,并且也使得将 repodata 加载到其 Python 表示形式的速度更快。

对于那些有兴趣了解如何实现这一点的人,请参阅 conda/conda-build 中的代码。