通道和生成索引#

通道布局#

.
├── 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"
    },
    ...
  },
  "packages.conda": {
      "super-fun-package-0.2.0-py310_0.conda": {
      "build": "py37_0",
      "build_number": 0,
      "depends": [
        "some-depends"
      ],
      "license": "BSD",
      "md5": "a75683f8d9f5b58c19a8ec5d0b7f796e",
      "name": "super-fun-package",
      "sha256": "e39029d601b9f493ea05c37a2630a9fe5810f1fe3c3f4250e51886838e8e0287",
      "size": 4125,
      "subdir": "win-64",
      "timestamp": 1530731987654,
      "version": "0.2.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,Intel 芯片

      $ 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,Apple 芯片

      $ 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,请转到 SciPy 的 Anaconda 包文件列表

  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 可用的情况下保留 .conda,在只有 .tar.bz2 可用的情况下保留 .tar.bz2

对于 Anaconda 的默认“main”通道,current_repodata.json 文件大约是 repodata.json 大小的 1/7。这使得下载 repodata 更快,也使得将 repodata 加载到其 python 表示形式中更快。

对于那些对如何实现这一点感兴趣的人,请参考 conda/conda-build 中的代码