覆盖依赖项
在许多情况下,您可能需要覆盖依赖项。 然而,大多数情况都归结为在将 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 将使用 uuid
的 2.0.0
版本。这将允许您通过依赖关系图逐步推出对 crate 的重大更改,而无需强制立即更新所有内容。
将 [patch]
与多个版本一起使用
您可以使用用于重命名依赖项的 package
键修补同一 crate 的多个版本。例如,假设 serde
crate 对其 1.*
系列有一个我们想要使用的错误修复,但我们也想尝试使用我们在 git 仓库中的 serde
的 2.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/serde
的 v2
分支中拉取 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。