索引格式
以下定义了索引的格式。偶尔会添加新功能,这些功能仅从引入它们的 Cargo 版本开始才被理解。较旧的 Cargo 版本可能无法使用利用新功能的包。但是,旧包的格式不应更改,因此较旧的 Cargo 版本应该能够使用它们。
索引配置
索引的根目录包含一个名为 config.json
的文件,其中包含 Cargo 用于访问注册表的 JSON 信息。这是 crates.io 配置文件的一个示例
{
"dl": "https://crates.io/api/v1/crates",
"api": "https://crates.io"
}
键是
-
dl
:这是下载索引中列出的 crate 的 URL。该值可能具有以下标记,这些标记将被替换为其对应的值{crate}
:crate 的名称。{version}
:crate 的版本。{prefix}
:从 crate 名称计算得出的目录前缀。例如,名为cargo
的 crate 的前缀为ca/rg
。有关详细信息,请参见下文。{lowerprefix}
:{prefix}
的小写变体。{sha256-checksum}
:crate 的 sha256 校验和。
如果不存在任何标记,则将值
/{crate}/{version}/download
附加到末尾。 -
api
:这是 Web API 的基本 URL。此键是可选的,但如果未指定,则诸如cargo publish
之类的命令将无法工作。Web API 在下面描述。 -
auth-required
:指示这是否是一个私有注册表,它要求所有操作都经过身份验证,包括 API 请求、crate 下载和稀疏索引更新。
下载端点
下载端点应发送所请求包的 .crate
文件。Cargo 支持 https、http 和 file URL、HTTP 重定向、HTTP1 和 HTTP2。TLS 支持的具体细节取决于运行 Cargo 的平台、Cargo 的版本以及编译方式。
如果在 config.json
中设置了 auth-required: true
,则 HTTP(S) 下载请求将包含 Authorization
标头。
索引文件
索引存储库的其余部分包含每个包的一个文件,其中文件名是包的名称,以小写形式表示。包的每个版本在文件中都有单独的一行。这些文件组织在一个目录层中
- 具有 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 中的其他问题。
版本唯一性
索引*必须*确保每个版本对于每个包只出现一次。这包括忽略 SemVer 构建元数据。例如,索引*不得*包含版本为 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.
// May be omitted since Cargo 1.84.
"features": ["i128_support"],
// Boolean of whether or not this is an optional dependency.
// Since Cargo 1.84, defaults to `false` if not specified.
"optional": false,
// Boolean of whether or not default features are enabled.
// Since Cargo 1.84, defaults to `true` if not specified.
"default_features": true,
// The target platform for the dependency.
// If not specified or `null`, it is not a target dependency.
// Otherwise, a string such as "cfg(windows)".
"target": null,
// The dependency kind.
// "dev", "build", or "normal".
// If not specified or `null`, it defaults to "normal".
"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.
// May be omitted since Cargo 1.84.
"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 is 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
。
在身份验证失败(或缺少身份验证令牌)时,服务器可以包含一个 www-authenticate
标头,其中包含一个 Cargo login_url="<URL>"
质询,以指示用户可以去哪里获取令牌。
需要身份验证的注册表必须在 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,直到缓存被清除。
不存在的 Crates
对于不存在的 crate,仓库应回复 404 “未找到”、410 “已删除” 或 451 “因法律原因不可用” 代码。
稀疏协议的限制
由于仓库的 URL 存储在 lockfile 中,因此不建议同时提供使用两种协议的仓库。关于转换计划的讨论正在 #10964 issue 中进行。 crates.io 仓库是一个例外,因为当使用稀疏协议时,Cargo 内部会替换为等效的 git URL。
如果仓库确实提供了两种协议,目前建议选择一种协议作为规范协议,并为另一种协议使用 源替换。