覆盖依赖项

在许多情况下,您可能需要覆盖依赖项。 然而,大多数情况都归结为在将 crate 发布到 crates.io 之前使用它的能力。 例如

  • 您正在开发的 crate 也在您正在开发的更大应用程序中使用,并且您想在更大应用程序中测试该库的错误修复。
  • 您没有参与开发的上游 crate 在其 git 存储库的 master 分支上有一个新功能或错误修复,您想对其进行测试。
  • 您即将发布 crate 的新主要版本,但您想对整个包进行集成测试,以确保新主要版本能够正常工作。
  • 您已经为在上游 crate 中发现的错误提交了一个修复程序,但您希望您的应用程序立即开始依赖该 crate 的修复版本,以避免阻塞该错误修复程序的合并。

这些情况可以使用 [patch] 清单部分 来解决。

本章将介绍几个不同的用例,并详细说明覆盖依赖项的不同方法。

注意:另请参阅使用 多个位置 指定依赖项,这可用于覆盖本地包中单个依赖项声明的源。

测试错误修复

假设您正在使用 uuid crate,但在您使用它时发现了一个错误。 然而,您非常有进取心,所以您决定尝试修复这个错误! 最初,您的清单将如下所示

[package]
name = "my-library"
version = "0.1.0"

[dependencies]
uuid = "1.0"

我们要做的第一件事是通过以下方式在本地克隆 uuid 存储库

$ git clone https://github.com/uuid-rs/uuid.git

接下来,我们将编辑 my-library 的清单以包含以下内容

[patch.crates-io]
uuid = { path = "../path/to/uuid" }

在这里,我们声明我们正在使用新的依赖项来*修补* 源 crates-io。 这将有效地将本地签出的 uuid 版本添加到我们本地包的 crates.io 注册表中。

接下来,我们需要确保更新我们的锁定文件以使用这个新版本的 uuid,以便我们的包使用本地签出的副本而不是来自 crates.io 的副本。 [patch] 的工作方式是,它将在 ../path/to/uuid 加载依赖项,然后每当查询 crates.io 获取 uuid 的版本时,它*也会* 返回本地版本。

这意味着本地签出的版本号非常重要,并且会影响是否使用该补丁。 我们的清单声明了 uuid = "1.0",这意味着我们只会解析为 >= 1.0.0, < 2.0.0,而 Cargo 的贪婪解析算法也意味着我们将在该范围内解析为最大版本。 通常情况下,这并不重要,因为 git 存储库的版本已经大于或等于 crates.io 上发布的最大版本,但请务必记住这一点!

无论如何,通常您现在需要做的就是

$ cargo build
   Compiling uuid v1.0.0 (.../uuid)
   Compiling my-library v0.1.0 (.../my-library)
    Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs

就是这样! 您现在正在使用本地版本的 uuid 进行构建(请注意构建输出中括号内的路径)。 如果您没有看到正在构建的本地路径版本,那么您可能需要运行 cargo update uuid --precise $version,其中 $version 是本地签出的 uuid 副本的版本。

一旦您修复了最初发现的错误,您接下来要做的事情很可能是将其作为拉取请求提交给 uuid crate 本身。 完成此操作后,您还可以更新 [patch] 部分。 [patch] 内部的列表就像 [dependencies] 部分一样,因此一旦您的拉取请求被合并,您可以将您的 path 依赖项更改为

[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid.git' }

使用未发布的次要版本

现在,让我们将注意力从错误修复转移到添加功能上。 在开发 my-library 时,您发现 uuid crate 中需要一个全新的功能。 您已经实现了这个功能,并在上面使用 [patch] 在本地对其进行了测试,并提交了一个拉取请求。 让我们来看看在它实际发布之前如何继续使用和测试它。

我们还要假设 crates.io 上 uuid 的当前版本是 1.0.0,但从那时起,git 存储库的 master 分支已经更新到 1.0.1。 此分支包含您之前提交的新功能。 要使用此存储库,我们将编辑我们的 Cargo.toml 文件,使其看起来像这样

[package]
name = "my-library"
version = "0.1.0"

[dependencies]
uuid = "1.0.1"

[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid.git' }

请注意,我们对 uuid 的本地依赖项已更新为 1.0.1,因为这是该 crate 发布后我们实际需要的版本。 然而,crates.io 上不存在此版本,因此我们使用清单的 [patch] 部分来提供它。

现在,当我们的库构建完成时,它将从 git 仓库中获取 uuid,并解析为仓库内的 1.0.1 版本,而不是尝试从 crates.io 下载版本。一旦 1.0.1 发布到 crates.io,就可以删除 [patch] 部分。

同样值得注意的是,[patch] 是*传递地*应用的。假设您在一个更大的包中使用 my-library,例如

[package]
name = "my-binary"
version = "0.1.0"

[dependencies]
my-library = { git = 'https://example.com/git/my-library' }
uuid = "1.0"

[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid.git' }

请记住,[patch] 是*传递地*应用的,但只能在*顶层*定义,因此如果需要,my-library 的使用者必须重复 [patch] 部分。但是,在这里,新的 uuid crate 适用于我们对 uuid 的依赖和 my-library -> uuid 依赖。uuid crate 将解析为整个 crate 图的一个版本,即 1.0.1,并且它将从 git 仓库中拉取。

覆盖存储库 URL

如果您要覆盖的依赖项不是从 crates.io 加载的,则必须稍微更改使用 [patch] 的方式。例如,如果依赖项是 git 依赖项,则可以使用以下命令将其覆盖到本地路径:

[patch."https://github.com/your/repository"]
my-library = { path = "../my-library/path" }

就是这样!

预发布重大更改

让我们来看看如何使用 crate 的新主版本,通常伴随着重大更改。坚持我们之前的 crate,这意味着我们将创建 uuid crate 的 2.0.0 版本。在我们向上提交所有更改后,我们可以更新 my-library 的清单,使其看起来像

[dependencies]
uuid = "2.0"

[patch.crates-io]
uuid = { git = "https://github.com/uuid-rs/uuid.git", branch = "2.0.0" }

就是这样!与前面的示例一样,2.0.0 版本实际上并不存在于 crates.io 上,但我们仍然可以通过使用 [patch] 部分通过 git 依赖项将其放入。作为思考练习,让我们再次查看上面的 my-binary 清单

[package]
name = "my-binary"
version = "0.1.0"

[dependencies]
my-library = { git = 'https://example.com/git/my-library' }
uuid = "1.0"

[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid.git', branch = '2.0.0' }

请注意,这实际上将解析为 uuid crate 的两个版本。my-binary crate 将继续使用 uuid crate 的 1.x.y 系列,但 my-library crate 将使用 uuid2.0.0 版本。这将允许您通过依赖关系图逐步推出对 crate 的重大更改,而无需强制立即更新所有内容。

[patch] 与多个版本一起使用

您可以使用用于重命名依赖项的 package 键修补同一 crate 的多个版本。例如,假设 serde crate 对其 1.* 系列有一个我们想要使用的错误修复,但我们也想尝试使用我们在 git 仓库中的 serde2.0.0 版本。要配置它,我们将执行

[patch.crates-io]
serde = { git = 'https://github.com/serde-rs/serde.git' }
serde2 = { git = 'https://github.com/example/serde.git', package = 'serde', branch = 'v2' }

第一个 serde = ... 指令指示应从 git 仓库中使用 serde 1.*(引入我们需要的错误修复),第二个 serde2 = ... 指令指示也应从 https://github.com/example/serdev2 分支中拉取 serde 包。我们在这里假设该分支上的 Cargo.toml 提到了版本 2.0.0

请注意,使用 package 键时,此处的 serde2 标识符实际上被忽略了。我们只需要一个与其他修补 crate 不冲突的唯一名称。

[patch] 部分

Cargo.toml[patch] 部分可用于使用其他副本覆盖依赖项。语法类似于 [dependencies] 部分

[patch.crates-io]
foo = { git = 'https://github.com/example/foo.git' }
bar = { path = 'my/local/bar' }

[dependencies.baz]
git = 'https://github.com/example/baz.git'

[patch.'https://github.com/example/baz']
baz = { git = 'https://github.com/example/patched-baz.git', branch = 'my-branch' }

**注意**:[patch] 表也可以指定为 配置选项,例如在 .cargo/config.toml 文件中或 CLI 选项中,例如 --config 'patch.crates-io.rand.path="rand"'。这对于您不想提交的仅限本地的更改或临时测试补丁很有用。

[patch] 表由类似依赖项的子表组成。[patch] 之后的每个键都是要修补的源的 URL 或注册表的名称。名称 crates-io 可用于覆盖默认注册表 crates.io。上面的示例中的第一个 [patch] 演示了如何覆盖 crates.io,第二个 [patch] 演示了如何覆盖 git 源。

这些表中的每个条目都是一个普通的依赖项规范,与清单的 [dependencies] 部分中的相同。[patch] 部分中列出的依赖项将被解析并用于修补指定 URL 处的源。上面的清单片段使用 foo crate 和 bar crate 修补 crates-io 源(例如 crates.io 本身)。它还使用来自其他地方的 my-branch 修补 https://github.com/example/baz 源。

可以使用不存在的 crate 版本修补源,也可以使用已经存在的 crate 版本修补源。如果使用源中已经存在的 crate 版本修补源,则将替换源的原始 crate。

Cargo 仅查看工作区根目录中的 Cargo.toml 清单中的补丁设置。依赖项中定义的补丁设置将被忽略。

[replace] 部分

**注意**:[replace] 已弃用。您应该改用 [patch] 表。

Cargo.toml 的这一部分可用于使用其他副本覆盖依赖项。语法类似于 [dependencies] 部分

[replace]
"foo:0.1.0" = { git = 'https://github.com/example/foo.git' }
"bar:1.0.2" = { path = 'my/local/bar' }

[replace] 表中的每个键都是一个 包 ID 规范,它允许任意选择要覆盖的依赖关系图中的节点(需要 3 部分版本号)。每个键的值与用于指定依赖项的 [dependencies] 语法相同,只是您不能指定功能。请注意,当 crate 被覆盖时,覆盖它的副本必须具有相同的名称和版本,但它可以来自不同的源(例如,git 或本地路径)。

Cargo 仅查看工作区根目录中的 Cargo.toml 清单中的替换设置。依赖项中定义的替换设置将被忽略。

paths 覆盖

有时您只是临时处理一个 crate,并且您不想像上面的 [patch] 部分那样修改 Cargo.toml。对于这种情况,Cargo 提供了一种更加有限的覆盖版本,称为**路径覆盖**。

路径覆盖是通过 .cargo/config.toml 而不是 Cargo.toml 指定的。在 .cargo/config.toml 内部,您将指定一个名为 paths 的键

paths = ["/path/to/uuid"]

此数组应填充包含 Cargo.toml 的目录。在本例中,我们只是添加了 uuid,因此它将是唯一被覆盖的。此路径可以是绝对路径,也可以是相对于包含 .cargo 文件夹的目录的路径。

但是,路径覆盖比 [patch] 部分更受限制,因为它们不能更改依赖关系图的结构。使用路径替换时,先前的一组依赖项必须与新的 Cargo.toml 规范完全匹配。例如,这意味着路径覆盖不能用于测试向 crate 添加依赖项,而是在这种情况下必须使用 [patch]。因此,路径覆盖的使用通常仅限于快速错误修复,而不是更大的更改。

**注意**:使用本地配置覆盖路径仅适用于已发布到 crates.io 的 crate。您不能使用此功能来告诉 Cargo 如何查找本地未发布的 crate。