索引格式
以下定义了索引的格式。偶尔会添加新功能,这些功能只有在引入它们的 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}
会导致(无害但不太优雅的)目录别名。例如,crate
和 CrateTwo
的 {prefix}
值分别为 cr/at
和 Cr/at
;这些在 Unix 机器上是不同的,但在 Windows 上是同一个目录的别名。使用规范化大小写的目录可以避免别名,但在区分大小写的文件系统上,很难支持缺少 {prefix}
/{lowerprefix}
的旧版本 Cargo。例如,nginx 重写规则可以轻松构造 {prefix}
,但不能执行大小写转换来构造 {lowerprefix}
。
名称限制
注册源应考虑对其索引中添加的包名称强制执行限制。Cargo 本身允许名称包含任何 字母数字、-
或 _
字符。crates.io 强制执行其自身的限制,包括以下内容
- 只允许 ASCII 字符。
- 只允许字母数字、
-
和_
字符。 - 第一个字符必须是字母。
- 不区分大小写的冲突检测。
- 防止
-
与_
的区别。 - 长度限制(最多 64 个字符)。
- 拒绝保留名称,例如 Windows 特殊文件名,如“nul”。
注册源应考虑纳入类似的限制,并考虑安全隐患,例如 IDN 同形异义词攻击 以及 UTR36 和 UTS39 中的其他问题。
版本唯一性
索引必须确保每个版本对于每个包只出现一次。这包括忽略语义化版本的构建元数据。例如,索引不能包含两个版本分别为 1.0.7
和 1.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 格式与 发布 API 和
cargo 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 包含其他几个字段,例如
description
和readme
,这些字段不会出现在索引中。这些字段旨在使注册表更容易获取有关 crate 的元数据,以便在网站上显示,而无需提取和解析.crate
文件。这些附加信息通常会添加到注册表服务器上的数据库中。- 尽管此处包含
rust_version
,但 crates.io 将忽略此字段,而是从.crate
文件中包含的Cargo.toml
中读取它。对于
cargo metadata
,差异如下:
vers
—cargo metadata
字段称为version
。deps
name
— 当依赖项在Cargo.toml
中被 重命名 时,cargo metadata
会将原始包名称放在name
字段中,并将别名放在rename
字段中。索引将别名放在name
字段中,并将原始包名称放在package
字段中。default_features
—cargo metadata
字段称为uses_default_features
。registry
—cargo metadata
使用值null
来指示依赖项来自 crates.io。索引使用值null
来指示依赖项来自与索引相同的注册表。创建索引条目时,crates.io 以外的注册表应将值null
转换为https://github.com/rust-lang/crates.io-index
,并将与当前索引匹配的 URL 转换为null
。cargo metadata
包含一些额外的字段,例如source
和path
。- 索引包含其他字段,例如
yanked
、cksum
和v
。
索引协议
Cargo 支持两种远程注册表协议:git
和 sparse
。git
协议将索引文件存储在 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 元数据文件,并从服务器捕获每个条目的 ETag
或 Last-Modified
HTTP 标头。刷新 crate 元数据时,Cargo 会发送 If-None-Match
或 If-Modified-Since
标头,以允许服务器在本地缓存有效时响应 HTTP 304“未修改”,从而节省时间和带宽。如果 ETag
和 Last-Modified
标头都存在,则 Cargo 仅使用 ETag
。
缓存失效
如果注册表使用某种 CDN 或代理来缓存对索引文件的访问,则建议注册表在文件更新时实现某种形式的缓存失效。如果未更新这些缓存,则用户可能无法访问新 crate,直到缓存被清除。
不存在的 Crate
对于不存在的 crate,注册表应响应 404“未找到”、410“已删除”或 451“出于法律原因不可用”代码。
稀疏协议的限制
由于注册表的 URL 存储在锁定文件中,因此不建议提供同时使用两种协议的注册表。有关过渡计划的讨论正在问题 #10964 中进行。crates.io 注册表是一个例外,因为当使用稀疏协议时,Cargo 会在内部替换等效的 git URL。
如果注册表确实同时提供两种协议,则当前建议选择一种协议作为规范协议,并对另一种协议使用 源替换。