入门 The Cargo Team 2026-09-13 15:48:21 · 0 阅读

第25章 注册表(Registries)

Cargo 通过“注册表”安装 crate 并获取依赖项。默认注册表是 crates.io。注册表包含一个“索引”,其中列出了可搜索的可用 crate 列表。注册表还可以提供 Web API,支持直接从 Cargo 发布新的 crate。

注意:如果你对镜像或封装现有注册表感兴趣,请参阅 源替换

如果你正在实现一个注册表服务器,关于 Cargo 与注册表之间的协议细节,请参阅 运行注册表

如果你使用的注册表需要身份验证,请参阅 注册表身份验证。如果你正在实现凭据提供程序,请参阅 凭据提供程序协议 以获取详细信息。

使用替代注册表

若需使用除 crates.io 以外的注册表,必须将注册表的名称和索引 URL 添加到 .cargo/config.toml 文件中。registries 表为每个注册表指定一个键,例如:

[registries]
my-registry = { index = "https://my-intranet:8080/git/index" }

index 键的值应指向包含注册表索引的 git 仓库 URL,或带有 sparse+ 前缀的 Cargo sparse 注册表 URL。

随后,crate 可以通过在 Cargo.toml 的依赖项条目中指定 registry 键及其值(即注册表名称),来依赖其他注册表中的 crate:

# Sample Cargo.toml
[package]
name = "my-project"
version = "0.1.0"
edition = "2024"

[dependencies]
other-crate = { version = "1.0", registry = "my-registry" }

与大多数配置值一样,你可以通过环境变量而非配置文件来指定索引。例如,设置以下环境变量可达到与定义配置文件相同的效果:

CARGO_REGISTRIES_MY_REGISTRY_INDEX=https://my-intranet:8080/git/index

注意:crates.io 不接受依赖其他注册表中 crate 的包。

发布到其他 Registry

如果 registry 支持 web API 访问,就可以直接用 Cargo 把包发布上去。Cargo 的不少命令(比如 cargo publish)都接受 --registry 命令行参数,用来指定要使用的 registry。例如,要发布当前目录下的包:

  1. cargo login --registry=my-registry

    这一步只需做一次。你需要输入从 registry 网站获取的 API token。也可以在执行 publish 时直接用 --token 命令行参数传入,或者通过以 registry 名命名的环境变量来提供,例如 CARGO_REGISTRIES_MY_REGISTRY_TOKEN

  2. cargo publish --registry=my-registry

如果不想每次都传 --registry 参数,可以在 .cargo/config.toml 中通过 registry.default 键设置默认 registry。例如:

[registry]
default = "my-registry"

Cargo.toml 清单中设置 package.publish 键可以限制包允许发布到哪些 registry。这样可以避免不小心把闭源包发布到 crates.io。其值可以是 registry 名称的列表,例如:

[package]
# ...
publish = ["my-registry"]

publish 也可以设为 false 来完全禁止发布,效果等同于空列表。

cargo login 保存的认证信息存放在 Cargo 主目录(默认为 $HOME/.cargo)下的 credentials.toml 文件中。每个 registry 对应一个独立的表,例如:

[registries.my-registry]
token = "854DvwSlUwEHtIo3kWy6x7UCPKHfzCmy"

Registry 协议

Cargo 支持两种远程注册表协议:gitsparse。如果注册表索引 URL 以 sparse+ 开头,Cargo 会采用 sparse 协议;否则,使用 git 协议。

git 协议将索引元数据存储在一个 git 仓库中,要求 Cargo 克隆整个仓库。

sparse 协议通过普通的 HTTP 请求单独获取元数据文件。由于 Cargo 仅下载相关 crate 的元数据,sparse 协议能显著节省时间和带宽。

crates.io 注册表同时支持这两种协议。crates.io 使用的协议由 registries.crates-io.protocol 配置项控制。

注册表身份验证

Cargo 通过凭证提供者向注册表进行身份验证。这些凭证提供者是外部可执行文件或内置提供者,供 Cargo 用于存储和检索凭证。

使用需要身份验证的替代注册表必须配置凭证提供者,以避免在磁盘上无意中存储未加密的凭证。基于历史原因,公开(无需身份验证)的注册表不需要配置凭证提供者;若未配置任何提供者,则默认使用 cargo:token

Cargo 还包含平台特定的提供者,利用操作系统安全地存储令牌。同时也包含 cargo:token 提供者,它将凭证以未加密的明文形式存储在 credentials 文件中。

建议在 $CARGO_HOME/config.toml 中配置全局凭证提供者列表,其默认位置为:

  • Windows: %USERPROFILE%\.cargo\config.toml
  • Unix: ~/.cargo/config.toml

此推荐配置使用操作系统提供者,并回退至 cargo:token,以从 Cargo 的 credentials 文件或环境变量中查找:

# ~/.cargo/config.toml
[registry]
global-credential-providers = ["cargo:token", "cargo:libsecret", "cargo:macos-keychain", "cargo:wincred"]

请注意,列表中靠后的条目优先级更高。详情请参阅registry.global-credential-providers

部分私有 Registry 可能还建议使用特定的 credential-provider。请查阅对应 Registry 的文档以确认。

内置 Provider

Cargo 内置了多个 credential provider。未来版本可能会调整可用列表(目前暂无此计划)。

cargo:token

通过 Cargo 的credentials文件以明文形式存储 token。获取 token 时,会检查环境变量 CARGO_REGISTRIES_<NAME>_TOKEN。若该 provider 未列入配置,则 *_TOKEN 环境变量将不生效。

cargo:wincred

使用 Windows Credential Manager 存储 token。

凭据在 Credential Manager 的“Windows Credentials”类别下保存,名称为 cargo-registry:<index-url>

cargo:macos-keychain

使用 macOS Keychain 存储 token。

可通过“钥匙串访问”应用查看已存储的 token。

cargo:libsecret

使用libsecret 存储 token。

任何支持 libsecret 的密码管理器均可用于查看已存储的 token。以下是部分示例(非详尽列表):

cargo:token-from-stdout <command> <args>

启动一个子进程,该进程需将 token 输出到 stdout。换行符会被自动去除。

  • 该进程继承用户的 stdin 和 stderr。
  • 成功时退出码应为 0,失败时为非零值。
  • 不支持cargo logincargo logout,调用时会报错。

执行命令时会提供以下环境变量:

  • CARGO — 正在执行该命令的 cargo` 可执行文件的路径。
  • CARGO_REGISTRY_INDEX_URL — registry 索引的 URL。
  • CARGO_REGISTRY_NAME_OPT — registry 的可选名称,不应作为查找键使用。

其余参数会原样传给子命令。

凭据插件

对于遵循 Cargo 凭据提供者协议的凭据提供者插件,配置值应为一个字符串,内容是可执行文件的路径(若该文件已在 PATH 中,也可以只写可执行文件名)。

例如,从 crates.io 安装 cargo-credential-1password 的步骤如下:

先用 cargo install cargo-credential-1password 安装该提供者。

然后在配置中添加(或新建)registry.global-credential-providers

[registry]
global-credential-providers = ["cargo:token", "cargo-credential-1password --account my.1password.com"]

global-credential-providers 中的值会按空格拆分成路径和命令行参数。如果路径或参数本身包含空格,需要定义全局凭据提供者时,请使用 [credential-alias]

凭据提供者协议

本文介绍如何构建 Cargo 凭据提供者。有关配置和使用凭据提供者的内容,请参阅Registry Authentication

使用外部凭据提供者时,Cargo 通过 stdin/stdout 与其通信,消息为单行 JSON。

Cargo 执行凭据提供者时始终会附带 --cargo-plugin 参数。这样凭据提供者可执行文件就能在满足 Cargo 需求之外提供额外功能。额外的参数会通过 JSON 中的 args 字段传入。

JSON 消息

本文中的 JSON 消息为了便于阅读添加了换行,实际消息中不能包含换行。

Credential hello

  • 发送方:凭据提供者
  • 用途:在进程启动时确定支持的协议
{
    "v":[1]
}

Cargo 发出的请求会包含一个 v 字段,其值设为此处列出的版本之一。如果 Cargo 不支持凭据提供者提供的任何版本,它会抛出错误并终止凭据进程。

注册表信息

  • 发送方:Cargo。 这本身不是一条独立消息,而是包含在 Cargo 发出的所有消息的 registry 字段中。
{
    // 注册表的索引 URL
    "index-url":"https://github.com/rust-lang/crates.io-index",
    // 配置中的注册表名称(可选)
    "name": "crates-io",
    // 尝试访问需身份验证的注册表时收到的 HTTP 头(可选)
    "headers": ["WWW-Authenticate: cargo"]
}

登录请求

  • 发送方:Cargo
  • 用途:收集并存储凭据
{
    // 协议版本
    "v":1,
    // 要执行的操作:登录
    "kind":"login",
    // 注册表信息(参见“注册表信息”)
    "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"},
    // 用户通过标准输入或命令行指定的 token(可选)
    "token": "<the token value>",
    // 用户可访问以获取 token 的 URL(可选)
    "login-url": "http://registry-url/login",
    // 额外的命令行参数(可选)
    "args":[]
}

如果设置了 token 字段,凭据提供者应使用提供的 token。如果未设置 token,凭据提供者应提示用户输入 token。

除了可能传递给凭据提供者的配置参数外,cargo login 还支持通过 cargo login -- <additional args> 传递额外的命令行参数。这些额外参数将追加在 Cargo 配置参数的后面,包含在 args 字段中。

读取请求

  • 发送方:Cargo
  • 用途:获取用于读取 crate 信息的凭据
{
    // 协议版本
    "v":1,
    // 请求类型:获取凭据
    "kind":"get",
    // 执行的操作:读取 Crate 信息
    "operation":"read",
    // 注册表信息(参见“注册表信息”)
    "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"},
    // 额外的命令行参数(可选)
    "args":[]
}

Publish 请求

  • 发送方:Cargo
  • 用途:获取发布 Crate 所需的凭据
{
    // 协议版本
    "v":1,
    // 请求类型:获取凭据
    "kind":"get",
    // 执行的操作:发布 Crate
    "operation":"publish",
    // Crate 名称
    "name":"sample",
    // Crate 版本
    "vers":"0.1.0",
    // Crate 校验和
    "cksum":"...",
    // 注册表信息(参见“注册表信息”)
    "registry":{"index-url":"sparse+https://registry-url/index/", "name": "my-registry"},
    // 额外的命令行参数(可选)
    "args":[]
}

获取成功响应

  • 发送方:凭据提供方
  • 用途:向 Cargo 返回凭据
{"Ok":{
    // 响应类型:对应一次 get 请求
    "kind":"get",
    // 发送至注册表的 Token
    "token":"...",
    // 缓存控制策略,可取以下值之一:
    // * "never":不缓存
    // * "session":在当前 Cargo 会话期间缓存
    // * "expires":在当前 Cargo 会话期间缓存,直到过期
    "cache":"expires",
    // Unix 时间戳(仅当 "cache" 为 "expires" 时使用)
    "expiration":1693942857,
    // 该 Token 是否独立于特定操作?
    "operation_independent":true
}}

token 会作为 Authorization HTTP 头部的值发送给注册表。

operation_independent 指示该 Token 是否可以在不同的操作(如发布或获取)之间复用。通常情况下,除非提供方希望生成仅适用于特定操作的 Token,否则此值应为 true

登录成功响应

  • 发送方:凭据提供方
  • 用途:指示登录成功
{"Ok":{
    // 响应类型:这是一次登录请求
    "kind":"login"
}}

登出成功响应

  • 发送方:credential provider
  • 用途:表示登出成功
{"Ok":{
    // 响应类型:这是一次登出请求
    "kind":"logout"
}}

失败响应(URL 不支持)

  • 发送方:credential provider
  • 用途:向 Cargo 返回错误信息
{"Err":{
    "kind":"url-not-supported"
}}

当 credential provider 只设计用于处理特定的 registry URL,而给定的 URL 不受支持时,会发送此响应。如果还有其他 provider 可用,Cargo 会尝试使用它们。

失败响应(未找到)

  • 发送方:credential provider
  • 用途:向 Cargo 返回错误信息
{"Err":{
    // 错误:provider 中找不到该凭据
    "kind":"not-found"
}}

当找不到凭据时发送此响应。对于凭据不存在的 get 请求,或者没有可删除内容的 logout 请求,出现这种情况是正常的。

失败响应(操作不支持)

  • 发送方:credential provider
  • 用途:向 Cargo 返回错误信息
{"Err":{
    // 错误:provider 中找不到该凭据
    "kind":"operation-not-supported"
}}

当 credential provider 不支持所请求的操作时发送此响应。例如某个 provider 只支持 get,却收到了 login 请求,就应返回此错误。

失败响应(其他)

  • 发送方:credential provider
  • 用途:向 Cargo 返回错误信息
{"Err":{
    // 错误:其他原因导致失败
    "kind":"other",
    // 用于展示的错误信息字符串
    "message": "free form string error message",
    // 详细的错误原因链(可选)
    "caused-by": ["cause 1", "cause 2"]
}}

获取读取令牌的通信示例

  1. Cargo 启动凭证进程,并捕获其标准输入(stdin)和标准输出(stdout)。
  2. 凭证进程向 Cargo 发送 Hello 消息
    { "v": [1] }
    
  3. Cargo 向凭证进程发送 CredentialRequest 消息(以下换行仅为了便于阅读)。
    {
        "v": 1,
        "kind": "get",
        "operation": "read",
        "registry":{"index-url":"sparse+https://registry-url/index/"}
    }
    
  4. 凭证进程向 Cargo 发送 CredentialResponse(以下换行仅为了便于阅读)。
    {
        "token": "...",
        "cache": "session",
        "operation_independent": true
    }
    
  5. Cargo 关闭传给凭证提供者的标准输入管道,随后该进程退出。
  6. 在此后的整个会话期间(直至 Cargo 退出),Cargo 在与该 Registry 交互时使用该令牌。

运行 Registry

实现一个极简 Registry 的方式是:准备一个包含索引的 git 仓库,以及一个托管由 cargo package 生成的压缩 .crate 文件的服务器。用户将无法通过 Cargo 向此 Registry 发布内容,但这在封闭环境中可能已足够用。索引格式详见 Registry 索引

支持发布功能的全功能 Registry 还需要提供一个符合 Cargo API 规范的 Web API 服务。相关 Web API 描述见 Registry Web API

市面上有商业及社区项目可用于构建和运行 Registry,可参看 https://github.com/rust-lang/cargo/wiki/Third-party-registries 获取可用项目列表。

索引格式

下文定义索引格式。新版本会偶尔引入新功能,这些功能仅被其引入版本的 Cargo 及后续版本识别。旧版本 Cargo 可能无法使用依赖新功能特性包。不过,旧版本包的格式不应变更,因此旧版本 Cargo 应能正常使用这些旧包。

索引配置

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

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

各键说明如下:

  • dl:这是下载索引中列出的 crates 的 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 的详细说明见下文。此 URL 末尾不应包含斜杠。

  • 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 的目录下。
  • 双字符名称的包放在名为 2 的目录下。
  • 三字符名称的包放在 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 的 rewrite 规则很容易构造出 {prefix},但无法做大小写转换来构造 {lowerprefix}

名称限制

Registry should consider imposing restrictions on the names of packages added to the index. Cargo itself allows names containing any alphanumeric, -, or _ characters. crates.io imposes its own limitations, including the following:

  • Only allows ASCII characters.
  • Only alphanumeric, -, and _ characters.
  • First character must be alphabetic.
  • Case-insensitive collision detection.
  • Prevent differences of - vs _.
  • Under a specific length (max 64).
  • Rejects reserved names, such as Windows special filenames like “nul”.

Registries should consider incorporating similar restrictions, and consider the security implications, such as IDN homograph attacks and other concerns in UTR36 and UTS39.

Version uniqueness

Indexes must ensure that each version only appears once for each package. This includes ignoring SemVer build metadata. For example, the index must not contain two entries with a version 1.0.7 and 1.0.7+extra.

JSON schema

Each line in a package file contains a JSON object that describes a published version of the package. The following is a pretty-printed example with comments explaining the format of the entry.

{
    // 包名。
    // 只能包含字母、数字、`-` 或 `_`。
    "name": "foo",
    // 此条目所描述的包版本。
    // 必须是 Semantic Versioning 2.0.0 规范(见 https://semver.org/)定义的有效版本号。
    "vers": "0.1.0",
    // 包的直接依赖数组。
    "deps": [
        {
            // 依赖项名称。
            // 如果依赖项相对于原始包名被重命名,此处为新名称。
            // 原始包名存储在 `package` 字段中。
            "name": "rand",
            // 此依赖项的 SemVer 版本要求。
            // 必须是 https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html 定义的有效版本要求。
            "req": "^0.6",
            // 为此依赖项启用的特性(字符串)数组。
            // 自 Cargo 1.84 起,未指定时默认为 `[]`。
            "features": ["i128_support"],
            // 布尔值,表示此依赖项是否可选。
            // 自 Cargo 1.84 起,未指定时默认为 `false`。
            "optional": false,
            // 布尔值,表示默认特性是否启用。
            // 自 Cargo 1.84 起,未指定时默认为 `true`。
            "default_features": true,
            // 依赖项的目标平台。
            // 未指定或为 `null` 时表示非目标依赖项。
            // 否则为字符串,例如 "cfg(windows)"。
            "target": null,
            // 依赖项类型。
            // 取值为 "dev"、"build" 或 "normal"。
            // 未指定或为 `null` 时默认为 "normal"。
            "kind": "normal",
            // 此依赖项所属注册表索引的 URL(字符串形式)。
            // 未指定或为 `null` 时,默认依赖项在当前注册表中。
            "registry": null,
            // 如果依赖项被重命名,此字符串为实际包名。
            // 未指定或为 `null` 时表示依赖项未重命名。
            "package": null,
        }
    ],
    // `.crate` 文件的 SHA256 校验和。
    "cksum": "d867001db0e2b6e0496f9fac96930e2d42233ecd3ca0413e0753d4c7695d289c",
    // 为包定义的特性集合。
    // 每个特性映射到其启用的特性或依赖项数组。
    // 自 Cargo 1.84 起,未指定时默认为 `{}`。
    "features": {
        "extras": ["rand/simd_support"]
    },
    // 布尔值,表示此版本是否已撤回。
    "yanked": false,
    // 包清单中的 `links` 字符串值,未指定时为 null。
    // 此字段可选,默认为 null。
    "links": null,
    // 表示此条目 schema 版本的无符号 32 位整数。
    //
    // 未指定时,应解释为默认值 1。
    //
    // Cargo(自 1.51 版本起)会忽略无法识别的版本。
    // 这提供了一种安全引入索引条目变更的方法,
    // 允许旧版 cargo 忽略其无法理解的新条目。
    // 1.51 之前的版本会忽略此字段,
    // 因此可能会误解索引条目的含义。
    //
    // 当前取值如下:
    //
    // * 1: 此处记录的 schema,不包含后续新增字段。
    //      Rust 1.51 及更高版本支持此格式。
    // * 2: 新增 `features2` 字段。
    //      Rust 1.60 及更高版本支持此格式。
    "v": 2,
    // 此可选字段包含采用新扩展语法的特性。
    // 具体包括命名空间特性(`dep:`)和弱依赖项(`pkg?/feat`)。
    //
    // 之所以与 `features` 分离,是因为 1.19 之前的版本无法解析新语法,
    // 即使有 `Cargo.lock` 文件也会加载失败。
    //
    // Cargo 会将此处列出的值与 "features" 字段合并。
    //
    // 若包含此字段,"v" 字段应至少设为 2。
    //
    // 注册表并非必须使用此字段来支持扩展特性语法,
    // 也可将其包含在 "features" 字段中。
    // 仅当注册表需支持 1.19 之前的 cargo 版本时才需使用此字段,
    // 实践中通常指 crates.io,因为旧版 cargo 不支持其他注册表。
    "features2": {
        "serde": ["dep:serde", "chrono?/serde"]
    }
    // 最低支持 Rust 版本(可选)。
    // 必须是有效版本要求,且不含操作符(例如不使用 `=`)。
    "rust_version": "1.60",
    // 此包版本的发布时间(可选)。
    //
    // 格式为 ISO8601 的子集:
    // - `yyyy-mm-ddThh:mm:ssZ`
    // - 不包含小数秒
    // - 时区固定为 `Z`(UTC),不支持时区偏移
    // - 字段使用 0 填充
    //
    // 示例:2025-11-12T19:30:12Z
    //
    // 此值应为原始发布时间,在 `yanked` 等状态变更时不应修改。
    "pubtime": "2025-11-12T19:30:12Z"
}


JSON 对象在加入索引后不应再修改,唯一的例外是 yanked 字段,它的值随时可能变化。

注意:索引 JSON 格式与 Publish APIcargo metadata 的 JSON 格式存在一些细微差异。 如果你打算用其中某个格式来生成索引条目,建议仔细对比它们各自的文档。

Publish API 的差异如下:

  • deps
    • name — 当依赖在 Cargo.toml 中被重命名时,Publish API 会把原始包名放在 name 字段,把别名放在 explicit_name_in_toml 字段。 而索引则把别名放在 name 字段,把原始包名放在 package 字段。
    • req — Publish API 中该字段名为 version_req
  • cksum — Publish API 不提供校验和,注册服务器在写入索引前必须自行计算。
  • features — 部分 feature 可能会被放入 features2 字段。 注意:这只是 crates.io 的历史遗留要求,其他 registry 无需如此处理 features。 v 字段用来标明是否存在 features2 字段。
  • Publish API 还包含其他一些字段,比如 descriptionreadme,它们不会出现在索引中。 这些字段是为了让 registry 方便地获取 crate 的元数据并展示在网站上,而不必解压并解析 .crate 文件。 这些额外信息通常会被存入 registry 服务器上的数据库。
  • 虽然这里有 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 值表示依赖项来自与索引相同的 registry。在创建索引条目时,若 registry 不是 crates.io,应将 null 值转换为 https://github.com/rust-lang/crates.io-index,并将与当前索引匹配的 URL 转换为 null
    • cargo metadata 包含若干额外字段,如 sourcepath
  • 索引包含 yankedcksumv 等额外字段。

索引协议

Cargo 支持两种远程 registry 协议:gitsparsegit 协议将索引文件存储在 Git 仓库中,而 sparse 协议通过 HTTP 单独获取文件。

Git 协议

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

Cargo 会将 Git 仓库缓存到磁盘上,以便高效地增量获取更新。

Sparse 协议

Sparse 协议在 registry URL 中使用 sparse+ 协议前缀。例如,crates.io 的 sparse 索引 URL 是 sparse+https://index.crates.io/

Sparse 协议通过独立的 HTTP 请求下载每个索引文件。这会产生大量的小型 HTTP 请求,因此使用支持流水线处理和 HTTP/2 的服务器可显著提升性能。

Sparse 认证

Cargo 会在获取其他文件之前,先尝试拉取 config.json 文件。如果服务器返回 HTTP 401,Cargo 将假设该注册表需要认证,并携带认证令牌重新请求 config.json

当认证失败(或缺少认证令牌)时,服务器可在响应中包含带有 Cargo login_url="<URL>" 挑战的 www-authenticate 头,指示用户前往该 URL 获取令牌。

需要认证的注册表必须在 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“因法律原因不可用”状态码。

Sparse 局限性

由于 registry 的 URL 保存在 lockfile 中,不建议同时提供两种协议。关于过渡计划的讨论正在进行中,见 issue #10964crates.io 是个例外,使用 sparse 协议时 Cargo 会在内部自动替换为等价的 git URL。

如果 registry 确实同时提供两种协议,目前的建议是指定其中一个作为规范协议,另一个则通过source replacement 来使用。

Web API

registry 可以在 config.json 中定义的位置部署 Web API,以支持下面列出的各类操作。

对于需要认证的请求,Cargo 会附带 Authorization 请求头,其值为 API token。如果 token 无效,服务器应返回 403 响应码。用户需要访问 registry 网站获取 token,Cargo 可以通过 cargo login 命令保存 token,也可以在命令行中直接传入。

响应成功时使用 2xx 响应码,出错时应使用恰当的响应码,比如 404。失败响应的 JSON 对象应具有如下结构:

{
    // 展示给用户的错误数组。
    "errors": [
        {
            // 字符串形式的错误信息。
            "detail": "error message text"
        }
    ]
}

如果响应包含此结构,即使响应码是 200,Cargo 也会把详细信息展示给用户。如果响应码表示出错但内容不含此结构,Cargo 会向用户显示一条帮助排查服务器错误的消息。服务器返回 errors 对象,可以让 registry 提供更详细、更贴近用户的错误信息。

为保证向后兼容,服务器应忽略任何意料之外的查询参数或 JSON 字段。如果缺少某个 JSON 字段,应将其视为 null。各端点通过路径中的 v1 组件进行版本控制,未来如果需要向后兼容的回退处理,由 Cargo 负责实现。

Cargo 会为所有请求设置 User-Agent 头,值通常是当前 Cargo 的版本号,例如 cargo/1.32.0 (8610973aa 2019-01-02)。用户可以通过配置项修改该值。此功能于 1.29 版本引入。

其他请求头因端点而异,详情见下文。

发布

  • 端点:/api/v1/crates/new
  • 方法:PUT
  • 授权:需要包含
  • 请求头:
    • Content-Typeapplication/octet-stream
    • Acceptapplication/json
  • 请求体:需要包含(见下文)

发布端点用于上传 crate 的新版本。服务端应验证该 crate,使其可供下载,并将其索引添加到索引中。

无需在发送成功响应前更新索引。成功响应后,Cargo 会在短时间内轮询索引以确认新 crate 已添加。如果索引中未显示该 crate,Cargo 将显示警告,提示用户该新 crate 尚不可用。

Cargo 发送的数据主体结构如下:

  • JSON 数据长度的 32 位无符号小端整数。
  • 包元数据(JSON 对象)。
  • .crate 文件长度的 32 位无符号小端整数。
  • .crate 文件。

以下是一个带注释的 JSON 对象示例。其中包含了一些 crates.io 施加限制的说明,仅用于展示可能的验证类型建议,不应被视为 crates.io 限制条件的完整列表。

{
    // 包名称
    "name": "foo",
    // 正在发布的包版本
    "vers": "0.1.0",
    // 包的直接依赖项数组
    "deps": [
        {
            // 依赖名称
            // 如果依赖项被重命名,这里保留的是原始包名。
            // 新的包名存储在 `explicit_name_in_toml` 字段中
            "name": "rand",
            // 此依赖项的 semver 版本要求
            "version_req": "^0.6",
            // 为此依赖项启用的功能特性(字符串)数组
            "features": ["i128_support"],
            // 布尔值,指示是否为可选依赖项
            "optional": false,
            // 布尔值,指示是否启用默认功能特性
            "default_features": true,
            // 依赖项的目标平台
            // 如果不是目标依赖项则为 null
            // 否则为类似 "cfg(windows)" 的字符串
            "target": null,
            // 依赖项类型
            // "dev"、"build" 或 "normal"
            "kind": "normal",
            // 该依赖项所在的注册表索引 URL 的字符串
            // 如果未指定或为 null,则假设该依赖项位于当前注册表中
            "registry": null,
            // 如果依赖项被重命名,这是新包名的字符串
            // 如果未指定或为 null,则表示该依赖项未被重命名
            "explicit_name_in_toml": null,
        }
    ],
    // 包定义的功能特性集合
    // 每个功能特性映射到它启用的功能特性或依赖项数组
    // Cargo 对功能特性名称没有限制,但 crates.io
    // 要求使用字母数字 ASCII、`_` 或 `-` 字符
    "features": {
        "extras": ["rand/simd_support"]
    },
    // 作者字符串列表
    // 可以为空
    "authors": ["Alice <a@example.com>"],
    // 来自清单的描述字段
    // 可以为 null,但 crates.io 要求至少有一些内容
    "description": null,
    // 指向此包文档网站的 URL 字符串
    // 可以为 null
    "documentation": null,
    // 指向此包主页网站的 URL 字符串
    // 可以为 null
    "homepage": null,
    // README 文件内容的字符串
    // 可以为 null
    "readme": null,
    // crate 内 README 文件的相对路径字符串
    // 可以为 null
    "readme_file": null,
    // 包关键字的字符串数组
    "keywords": [],
    // 包分类的字符串数组
    "categories": [],
    // 包许可证的字符串
    // 可以为 null,但 crates.io 要求设置 `license` 或 `license_file`
    "license": null,
    // crate 内许可证文件的相对路径字符串
    // 可以为 null
    "license_file": null,
    // 指向此包源代码仓库网站的 URL 字符串
    // 可以为 null
    "repository": null,
    // "status" 徽章的可选对象。每个值都是任意
    // 字符串到字符串映射的对象
    // crates.io 对徽章格式有特殊解释
    "badges": {
        "travis-ci": {
            "branch": "master",
            "repository": "rust-lang/cargo"
        }
    },
    // 包清单中的 `links` 字符串值,如果未指定则为 null
    // 此字段是可选的,默认为 null
    "links": null,
    // 最低支持的 Rust 版本(可选)
    // 这必须是一个有效的版本要求,不含运算符(例如没有 `=`)
    "rust_version": null
}

成功的响应会返回如下 JSON 对象:

{
    // 可选,向用户展示的警告信息。
    "warnings": {
        // 无效并被忽略的类别名称字符串数组。
        "invalid_categories": [],
        // 无效并被忽略的徽章名称字符串数组。
        "invalid_badges": [],
        // 其他需要向用户展示的警告字符串数组。
        "other": []
    }
}

Yank

  • Endpoint: /api/v1/crates/{crate_name}/{version}/yank
  • Method: DELETE
  • Authorization: Included
  • Headers:
    • Accept: application/json
  • Body: None

yank 端点会将索引中指定 crate 版本的 yank 字段设置为 true

成功的响应会返回如下 JSON 对象:

{
    // 表示 yank 操作成功,恒为 true。
    "ok": true,
}

Unyank

  • Endpoint: /api/v1/crates/{crate_name}/{version}/unyank
  • Method: PUT
  • Authorization: Included
  • Headers:
    • Accept: application/json
  • Body: None

unyank 端点会将索引中指定 crate 版本的 yank 字段设置为 false

成功的响应会返回如下 JSON 对象:

{
    // 表示 unyank 操作成功,恒为 true。
    "ok": true,
}

Owners

Cargo 本身没有用户和所有者的概念,但提供了 owner 命令来管理谁有权控制某个 crate。用户和所有者的具体处理方式由 registry 自行决定。关于 crates.io 如何通过 GitHub 用户和团队管理所有者,参见发布文档

Owners: List

  • Endpoint: /api/v1/crates/{crate_name}/owners
  • Method: GET
  • Authorization: Included
  • Headers:
    • Accept: application/json
  • Body: None

owners 端点返回该 crate 的所有者列表。

成功响应中包含以下 JSON 对象:

{
    // crate 所有者的数组。
    "users": [
        {
            // 所有者的唯一无符号 32 位整数 ID。
            "id": 70,
            // 所有者的唯一用户名。
            "login": "github:rust-lang:core",
            // 所有者的名称。
            // 此项为可选,可能为 null。
            "name": "Core",
        }
    ]
}

所有者:添加

  • 端点:/api/v1/crates/{crate_name}/owners
  • 方法:PUT
  • 鉴权:已包含
  • 请求头:
    • Content-Typeapplication/json
    • Acceptapplication/json
  • 请求体:已包含(见下文)

PUT 请求用于向 registry 发送请求,以将新所有者添加到 crate。具体如何处理该请求取决于 registry 的实现。例如,crates.io 会向用户发送邀请,用户接受后方可被添加为所有者。

请求体应包含以下 JSON 对象:

{
    // 待添加所有者的 `login` 字符串数组。
    "users": ["login_name"]
}

成功响应中包含以下 JSON 对象:

{
    // 指示添加是否成功,始终为 true。
    "ok": true,
    // 用于向用户显示的字符串。
    "msg": "user ehuss has been invited to be an owner of crate cargo"
}

所有者:移除

  • 端点:/api/v1/crates/{crate_name}/owners
  • 方法:DELETE
  • 鉴权:已包含
  • 请求头:
    • Content-Typeapplication/json
    • Acceptapplication/json
  • 请求体:已包含(见下文)

DELETE 请求用于从 crate 中移除所有者。请求体应包含以下 JSON 对象:

{
    // 待移除所有者的 `login` 字符串数组。
    "users": ["login_name"]
}

成功响应中包含以下 JSON 对象:

{
    // 表示移除操作成功,始终为 true。
    "ok": true
    // 展示给用户的字符串。当前 cargo 会忽略此字段。
    "msg": "owners successfully removed",
}
  • 端点: /api/v1/crates
  • 方法: GET
  • 鉴权: 不需要
  • 请求头:
    • Accept: application/json
  • 请求体: 无
  • 查询参数:
    • q: 搜索查询字符串。
    • per_page: 返回结果数量,默认 10,最大 100。

搜索请求会基于服务器端定义的准则,查找匹配的 crate。

成功响应的 JSON 对象如下:

{
    // 结果数组。
    "crates": [
        {
            // crate 名称。
            "name": "rand",
            // 当前可用的最高版本。
            "max_version": "0.6.1",
            // crate 的文本描述。
            "description": "Random number generators and other randomness functionality.\n",
        }
    ],
    "meta": {
        // 服务器端可用的结果总数。
        "total": 119
    }
}

登录

  • 端点: /me

“登录”端点并非实际的 API 请求。它仅用于配合 cargo login 命令,向用户展示一个 URL,指引其在浏览器中访问该 URL 完成登录并获取 API token。

评论 (0)