索引格式

以下定义了索引的格式。偶尔会添加新功能,这些功能只有在引入它们的 Cargo 版本之后才能被理解。旧版本的 Cargo 可能无法使用利用了新功能的包。但是,旧包的格式不应该改变,因此旧版本的 Cargo 应该能够使用它们。

索引配置

索引的根目录包含一个名为 config.json 的文件,其中包含 Cargo 用于访问注册源的 JSON 信息。以下是 crates.io 配置文件示例

{
    "dl": "https://crates.io/api/v1/crates",
    "api": "https://crates.io"
}

键值如下

  • dl:这是用于下载索引中列出的包的 URL。该值可能包含以下标记,这些标记将被替换为其对应的值

    • {crate}:包的名称。
    • {version}:包版本。
    • {prefix}:根据包名称计算的目录前缀。例如,名为 cargo 的包的前缀为 ca/rg。有关详细信息,请参见下文。
    • {lowerprefix}{prefix} 的小写变体。
    • {sha256-checksum}:包的 SHA256 校验和。

    如果没有出现任何标记,则将值 /{crate}/{version}/download 附加到末尾。

  • api:这是 Web API 的基本 URL。此键是可选的,但如果未指定,则 cargo publish 等命令将无法工作。Web API 描述如下。

  • auth-required:指示这是否是一个私有注册源,需要对所有操作进行身份验证,包括 API 请求、包下载和稀疏索引更新。

下载端点

下载端点应发送所请求包的 .crate 文件。Cargo 支持 https、http 和文件 URL、HTTP 重定向、HTTP1 和 HTTP2。TLS 支持的具体细节取决于运行 Cargo 的平台、Cargo 的版本以及它的编译方式。

如果在 config.json 中设置了 auth-required: true,则 Authorization 标头将包含在 http(s) 下载请求中。

索引文件

索引仓库的其余部分包含每个包的一个文件,其中文件名是包名称的小写形式。包的每个版本在文件中都有单独的一行。文件按目录层级组织

  • 名称为 1 个字符的包放在名为 1 的目录中。
  • 名称为 2 个字符的包放在名为 2 的目录中。
  • 名称为 3 个字符的包放在目录 3/{first-character} 中,其中 {first-character} 是包名称的第一个字符。
  • 所有其他包都存储在名为 {first-two}/{second-two} 的目录中,其中顶层目录是包名称的前两个字符,下一个子目录是包名称的第三个和第四个字符。例如,cargo 将存储在名为 ca/rg/cargo 的文件中。

注意:尽管索引文件名是小写的,但 Cargo.toml 和索引 JSON 数据中包含包名称的字段区分大小写,并且可能包含大小写字符。

上面的目录名称是根据转换为小写的包名称计算的;它由标记 {lowerprefix} 表示。当使用原始包名称而不进行大小写转换时,生成的目录名称由标记 {prefix} 表示。例如,包 MyCrate{prefix}My/Cr{lowerprefix}my/cr。一般来说,建议使用 {prefix} 而不是 {lowerprefix},但这两种选择各有利弊。在不区分大小写的文件系统上使用 {prefix} 会导致(无害但不太优雅的)目录别名。例如,crateCrateTwo{prefix} 值分别为 cr/atCr/at;这些在 Unix 机器上是不同的,但在 Windows 上是同一个目录的别名。使用规范化大小写的目录可以避免别名,但在区分大小写的文件系统上,很难支持缺少 {prefix}/{lowerprefix} 的旧版本 Cargo。例如,nginx 重写规则可以轻松构造 {prefix},但不能执行大小写转换来构造 {lowerprefix}

名称限制

注册源应考虑对其索引中添加的包名称强制执行限制。Cargo 本身允许名称包含任何 字母数字-_ 字符。crates.io 强制执行其自身的限制,包括以下内容

  • 只允许 ASCII 字符。
  • 只允许字母数字、-_ 字符。
  • 第一个字符必须是字母。
  • 不区分大小写的冲突检测。
  • 防止 -_ 的区别。
  • 长度限制(最多 64 个字符)。
  • 拒绝保留名称,例如 Windows 特殊文件名,如“nul”。

注册源应考虑纳入类似的限制,并考虑安全隐患,例如 IDN 同形异义词攻击 以及 UTR36UTS39 中的其他问题。

版本唯一性

索引必须确保每个版本对于每个包只出现一次。这包括忽略语义化版本的构建元数据。例如,索引不能包含两个版本分别为 1.0.71.0.7+extra 的条目。

JSON 模式

包文件中的每一行都包含一个 JSON 对象,该对象描述了包的已发布版本。以下是一个格式美化的示例,并附有解释条目格式的注释。

{
    // The name of the package.
    // This must only contain alphanumeric, `-`, or `_` characters.
    "name": "foo",
    // The version of the package this row is describing.
    // This must be a valid version number according to the Semantic
    // Versioning 2.0.0 spec at https://semver.org/.
    "vers": "0.1.0",
    // Array of direct dependencies of the package.
    "deps": [
        {
            // Name of the dependency.
            // If the dependency is renamed from the original package name,
            // this is the new name. The original package name is stored in
            // the `package` field.
            "name": "rand",
            // The SemVer requirement for this dependency.
            // This must be a valid version requirement defined at
            // https://doc.rust-lang.net.cn/cargo/reference/specifying-dependencies.html.
            "req": "^0.6",
            // Array of features (as strings) enabled for this dependency.
            "features": ["i128_support"],
            // Boolean of whether or not this is an optional dependency.
            "optional": false,
            // Boolean of whether or not default features are enabled.
            "default_features": true,
            // The target platform for the dependency.
            // null if not a target dependency.
            // Otherwise, a string such as "cfg(windows)".
            "target": null,
            // The dependency kind.
            // "dev", "build", or "normal".
            // Note: this is a required field, but a small number of entries
            // exist in the crates.io index with either a missing or null
            // `kind` field due to implementation bugs.
            "kind": "normal",
            // The URL of the index of the registry where this dependency is
            // from as a string. If not specified or null, it is assumed the
            // dependency is in the current registry.
            "registry": null,
            // If the dependency is renamed, this is a string of the actual
            // package name. If not specified or null, this dependency is not
            // renamed.
            "package": null,
        }
    ],
    // A SHA256 checksum of the `.crate` file.
    "cksum": "d867001db0e2b6e0496f9fac96930e2d42233ecd3ca0413e0753d4c7695d289c",
    // Set of features defined for the package.
    // Each feature maps to an array of features or dependencies it enables.
    "features": {
        "extras": ["rand/simd_support"]
    },
    // Boolean of whether or not this version has been yanked.
    "yanked": false,
    // The `links` string value from the package's manifest, or null if not
    // specified. This field is optional and defaults to null.
    "links": null,
    // An unsigned 32-bit integer value indicating the schema version of this
    // entry.
    //
    // If this not specified, it should be interpreted as the default of 1.
    //
    // Cargo (starting with version 1.51) will ignore versions it does not
    // recognize. This provides a method to safely introduce changes to index
    // entries and allow older versions of cargo to ignore newer entries it
    // doesn't understand. Versions older than 1.51 ignore this field, and
    // thus may misinterpret the meaning of the index entry.
    //
    // The current values are:
    //
    // * 1: The schema as documented here, not including newer additions.
    //      This is honored in Rust version 1.51 and newer.
    // * 2: The addition of the `features2` field.
    //      This is honored in Rust version 1.60 and newer.
    "v": 2,
    // This optional field contains features with new, extended syntax.
    // Specifically, namespaced features (`dep:`) and weak dependencies
    // (`pkg?/feat`).
    //
    // This is separated from `features` because versions older than 1.19
    // will fail to load due to not being able to parse the new syntax, even
    // with a `Cargo.lock` file.
    //
    // Cargo will merge any values listed here with the "features" field.
    //
    // If this field is included, the "v" field should be set to at least 2.
    //
    // Registries are not required to use this field for extended feature
    // syntax, they are allowed to include those in the "features" field.
    // Using this is only necessary if the registry wants to support cargo
    // versions older than 1.19, which in practice is only crates.io since
    // those older versions do not support other registries.
    "features2": {
        "serde": ["dep:serde", "chrono?/serde"]
    }
    // The minimal supported Rust version (optional)
    // This must be a valid version requirement without an operator (e.g. no `=`)
    "rust_version": "1.60"
}

JSON 对象在添加后不应修改,但 yanked 字段的值可能会随时更改。

注意:索引 JSON 格式与 发布 APIcargo metadata 的 JSON 格式略有不同。如果您使用其中之一作为生成索引条目的源,建议您仔细检查它们之间的文档差异。

对于 发布 API,差异如下:

  • deps
    • name — 当依赖项在 Cargo.toml 中被 重命名 时,发布 API 会将原始包名称放在 name 字段中,并将别名放在 explicit_name_in_toml 字段中。索引将别名放在 name 字段中,并将原始包名称放在 package 字段中。
    • req — 发布 API 字段称为 version_req
  • cksum — 发布 API 不指定校验和,必须由注册表在添加到索引之前计算。
  • features — 一些功能可能会放在 features2 字段中。注意:这只是 crates.io 的遗留要求;其他注册表不需要费心修改功能映射。v 字段指示 features2 字段的存在。
  • 发布 API 包含其他几个字段,例如 descriptionreadme,这些字段不会出现在索引中。这些字段旨在使注册表更容易获取有关 crate 的元数据,以便在网站上显示,而无需提取和解析 .crate 文件。这些附加信息通常会添加到注册表服务器上的数据库中。
  • 尽管此处包含 rust_version,但 crates.io 将忽略此字段,而是从 .crate 文件中包含的 Cargo.toml 中读取它。

对于 cargo metadata,差异如下:

  • verscargo metadata 字段称为 version
  • deps
    • name — 当依赖项在 Cargo.toml 中被 重命名 时,cargo metadata 会将原始包名称放在 name 字段中,并将别名放在 rename 字段中。索引将别名放在 name 字段中,并将原始包名称放在 package 字段中。
    • default_featurescargo metadata 字段称为 uses_default_features
    • registrycargo metadata 使用值 null 来指示依赖项来自 crates.io。索引使用值 null 来指示依赖项来自与索引相同的注册表。创建索引条目时,crates.io 以外的注册表应将值 null 转换为 https://github.com/rust-lang/crates.io-index,并将与当前索引匹配的 URL 转换为 null
    • cargo metadata 包含一些额外的字段,例如 sourcepath
  • 索引包含其他字段,例如 yankedcksumv

索引协议

Cargo 支持两种远程注册表协议:gitsparsegit 协议将索引文件存储在 git 存储库中,而 sparse 协议通过 HTTP 获取单个文件。

Git 协议

git 协议在索引 URL 中没有协议前缀。例如,crates.io 的 git 索引 URL 是 https://github.com/rust-lang/crates.io-index

Cargo 将 git 存储库缓存在磁盘上,以便它可以有效地增量获取更新。

稀疏协议

稀疏协议在注册表 URL 中使用 sparse+ 协议前缀。例如,crates.io 的稀疏索引 URL 是 sparse+https://index.crates.io/

稀疏协议使用单独的 HTTP 请求下载每个索引文件。由于这会导致大量的小型 HTTP 请求,因此支持流水线和 HTTP/2 的服务器可以显着提高性能。

稀疏身份验证

Cargo 将尝试在获取任何其他文件之前获取 config.json 文件。如果服务器响应 HTTP 401,则 Cargo 将假定注册表需要身份验证,并使用包含的身份验证令牌重新尝试请求 config.json

在身份验证失败(或缺少身份验证令牌)时,服务器可能会包含一个带有 Cargo login_url="<URL>" 质询的 www-authenticate 标头,以指示用户可以去哪里获取令牌。

需要身份验证的注册表必须在 config.json 中设置 auth-required: true

缓存

Cargo 缓存 crate 元数据文件,并从服务器捕获每个条目的 ETagLast-Modified HTTP 标头。刷新 crate 元数据时,Cargo 会发送 If-None-MatchIf-Modified-Since 标头,以允许服务器在本地缓存有效时响应 HTTP 304“未修改”,从而节省时间和带宽。如果 ETagLast-Modified 标头都存在,则 Cargo 仅使用 ETag

缓存失效

如果注册表使用某种 CDN 或代理来缓存对索引文件的访问,则建议注册表在文件更新时实现某种形式的缓存失效。如果未更新这些缓存,则用户可能无法访问新 crate,直到缓存被清除。

不存在的 Crate

对于不存在的 crate,注册表应响应 404“未找到”、410“已删除”或 451“出于法律原因不可用”代码。

稀疏协议的限制

由于注册表的 URL 存储在锁定文件中,因此不建议提供同时使用两种协议的注册表。有关过渡计划的讨论正在问题 #10964 中进行。crates.io 注册表是一个例外,因为当使用稀疏协议时,Cargo 会在内部替换等效的 git URL。

如果注册表确实同时提供两种协议,则当前建议选择一种协议作为规范协议,并对另一种协议使用 源替换