多种情境下都可能需要覆盖依赖。不过,大多数情况归根结底都是希望在 crate 发布到 crates.io 之前使用它。例如:
- 你正在开发的 crate 也被你正在开发的一个更大型应用使用,你希望在这个大型应用中测试该库的错误修复。
- 你没有参与开发的某个上游 crate,在其 Git 仓库的 master 分支中加入了新功能或错误修复,你想试一试。
- 你即将发布 crate 的新主版本,但希望对整个包进行集成测试,以确认这个新主版本能够正常工作。
- 你发现了上游 crate 的一个错误并提交了修复,但希望应用立即依赖修复后的版本,避免等待修复合并而受阻。
这些情境都可以通过清单中的 [patch] 节解决。
本章介绍几个不同的用例,并详细说明覆盖依赖的不同方式。
- 用例
- 测试错误修复
- 使用尚未发布的次版本
- 覆盖仓库 URL
- 预先使用含有破坏性变更的版本
- 对多个版本使用 [patch]
- 参考
- [patch] 节
- [replace] 节
- paths 覆盖
注意:另请参阅“为依赖指定多个位置”,它可以用来覆盖本地包中单个依赖声明的来源。
测试错误修复
假设你正在使用 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] 节一样,因此在拉取请求合并后,可以将路径依赖改为:
[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 依赖。整个 crate 依赖图中的 uuid 会解析为同一个版本 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 的 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 = … 指令表示 serde 1.* 应从 Git 仓库使用,取得所需的错误修复;第二条 serde2 = … 指令表示 serde 包也应从 https://github.com/example/serde 的 v2 分支获取。这里假设该分支的 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”‘ 这样的命令行选项。这适用于不希望提交的仅限本地的变更,或临时测试补丁。
[patch] 表由类似依赖声明的子表组成。[patch] 后的每个键都是被修补来源的 URL,或者注册表名称。crates-io 这个名称可以用来覆盖默认注册表 crates.io。上例的第一个 [patch] 演示覆盖 crates.io,第二个 [patch] 演示覆盖 Git 来源。
这些表中的每个条目都是普通的依赖规范,与清单 [dependencies] 节中的规范相同。[patch] 节列出的依赖会被解析,并用来修补指定 URL 的来源。上面的清单片段用 foo 和 bar crate 修补 crates-io 来源,即 crates.io 本身;还用来自其他位置的 my-branch 修补 https://github.com/example/baz 来源。
可以用来源中尚不存在的 crate 版本进行修补,也可以用来源中已经存在的版本。如果用来源中已有的 crate 版本修补该来源,那么该来源的原始 crate 就会被替换。
Cargo 只查看工作区根目录 Cargo.toml 清单中的 patch 设置。依赖中定义的 patch 设置会被忽略。
[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 规范,可以任意选择依赖图中的一个节点进行覆盖,必须提供三段式版本号。每个键的值与 [dependencies] 中指定依赖的语法相同,但不能指定 features。注意,覆盖 crate 时,用于覆盖的副本必须具有相同的名称和版本,但可以来自其他来源,例如 Git 或本地路径。
Cargo 只查看工作区根目录 Cargo.toml 清单中的 replace 设置。依赖中定义的 replace 设置会被忽略。
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。











暂无评论内容