覆盖依赖
覆盖依赖的需求可能源于多种场景。然而,它们中的大多数都归结为能够在某个 crate 发布到 crates.io 之前使用它。例如:
- 您正在开发的 crate 也用于您正在开发的一个更大的应用程序中,并且您想在更大的应用程序中测试该库的 bug 修复。
- 您不参与开发的上游 crate 在其 git 仓库的 master 分支上有一个新功能或 bug 修复,您想测试一下。
- 您即将发布一个新主版本的 crate,但您希望在整个软件包中进行集成测试,以确保新的主版本能够正常工作。
- 您已经为发现的 bug 向上游 crate 提交了修复,但您希望立即让您的应用程序开始依赖于已修复版本的 crate,以避免因 bug 修复未合并而受阻。
这些场景可以使用 [patch]
清单部分来解决。
本章将介绍几个不同的用例,并详细介绍覆盖依赖的不同方法。
注意:另请参阅使用多个位置指定依赖项,这可用于覆盖本地包中单个依赖项声明的来源。
测试 bug 修复
假设您正在使用 uuid
crate,但在使用过程中发现了一个 bug。然而,您非常积极,所以您决定也尝试修复这个 bug!最初,您的清单将如下所示:
[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 注册表中。
接下来,我们需要确保我们的 lock 文件已更新为使用此新版本的 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
副本的版本。
修复最初发现的 bug 后,您接下来要做的很可能是将其作为 pull request 提交给 uuid
crate 本身。完成此操作后,您还可以更新 [patch]
部分。 [patch]
中的列表就像 [dependencies]
部分一样,因此一旦您的 pull request 被合并,您可以将 path
依赖项更改为:
[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid.git' }
使用未发布的次要版本
现在让我们将重点从 bug 修复转移到添加功能。在开发 my-library
时,您发现 uuid
crate 中需要一个全新的功能。您已经实现了这个功能,在上面使用 [patch]
在本地进行了测试,并提交了一个 pull request。让我们回顾一下如何在实际发布之前继续使用和测试它。
假设 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.*
系列中使用的 bug 修复,但我们也想原型化使用我们在 git 仓库中拥有的 2.0.0
版本的 serde。要配置这一点,我们将执行:
[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.*
(引入我们需要的 bug 修复),第二个 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
文件中或像--config 'patch.crates-io.rand.path="rand"'
这样的 CLI 选项。这对于您不想提交的本地更改或临时测试补丁非常有用。
[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。