You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
9.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 依赖覆盖
依赖覆盖对于本地开发来说,是很常见的,大部分原因都是我们希望在某个包发布到 `crates.io` 之前使用它,例如:
- 你正在同时开发一个包和一个项目,而后者依赖于前者,你希望能在能项目中对正在开发的包进行测试
- 你引入的一个依赖包在 `master` 分支发布了新的代码,恰好修复了某个 bug因此你希望能单独对该分支进行下测试
- 你即将发布一个包的新版本,为了确保新版本正常工作,你需要对其进行集成测试
- 你为项目的某个依赖包提了一个 PR 并解决了一个重要 bug在等待合并到 `master` 分支,但是时间不等人,因此你决定先使用自己修改的版本,等未来合并后,再继续使用官方版本
下面我们来具体看看类似的问题该如何解决。
> 上一章节中我们讲了如果通过[多种引用方式](https://course.rs/cargo/reference/specify-deps/intro.html#多引用方式混合)来引入一个包,其实这也是一种依赖覆盖。
## 测试 bugfix 版本
假设我们有一个项目正在使用 [`uuid`](https://crates.io/crates/uuid) 依赖包,但是却不幸地发现了一个 bug由于这个 bug 影响了使用,没办法等到官方提交新版本,因此还是自己修复为好。
我们项目的 `Cargo.toml` 内容如下:
```toml
[package]
name = "my-library"
version = "0.1.0"
[dependencies]
uuid = "0.8.2"
```
为了修复 `bug`,首先需要将 `uuid` 的源码克隆到本地,笔者是克隆到和项目同级的目录下:
```shell
git clone https://github.com/uuid-rs/uuid
```
下面,修改项目的 `Cargo.toml` 添加以下内容以引入本地克隆的版本:
```toml
[patch.crates-io]
uuid = { path = "../uuid" }
```
这里我们使用自己修改过的 `patch` 来覆盖来自 `crates.io` 的版本,由于克隆下来的 `uuid` 目录和我们的项目同级,因此通过相对路径 "../uuid" 即可定位到。
在成功为 `uuuid` 打了本地补丁后,现在尝试在项目下运行 `cargo build`,但是却报错了,而且报错内容有一些看不太懂:
```shell
$ cargo build
Updating crates.io index
warning: Patch `uuid v1.0.0-alpha.1 (/Users/sunfei/development/rust/demos/uuid)` was not used in the crate graph.
Check that the patched package version and available features are compatible
with the dependency requirements. If the patch has a different version from
what is locked in the Cargo.lock file, run `cargo update` to use the new
version. This may also occur with an optional dependency that is not enabled.
```
具体原因比较复杂,但是仔细观察,会发现克隆下来的 `uuid` 的版本是 `v1.0.0-alpha.1` (在 `"../uuid/Cargo.toml"` 中可以查看),然后我们本地引入的 `uuid` 版本是 `0.8.2`,根据之前讲过的 `crates.io` 的[版本规则](https://course.rs/cargo/reference/specify-deps/intro.html#从-cratesio-引入依赖包),这两者是不兼容的,`0.8.2` 只能升级到 `0.8.z`,例如 `0.8.3`
既然如此,我们先将 "../uuid/Cargo.toml" 中的 `version = "1.0.0-alpha.1"` 修改为 `version = "0.8.3"` ,然后看看结果先:
```shell
$ cargo build
Updating crates.io index
Compiling uuid v0.8.3 (/Users/sunfei/development/rust/demos/uuid)
```
大家注意到最后一行了吗?我们成功使用本地的 `0.8.3` 版本的 `uuid` 作为最新的依赖,因此也侧面证明了,补丁 `patch` 的版本也必须遵循相应的版本兼容规则!
如果修改后还是有问题,大家可以试试以下命令,指定版本进行更新:
```shell
% cargo update -p uuid --precise 0.8.3
Updating crates.io index
Updating uuid v0.8.3 (/Users/sunfei/development/rust/demos/uuid) -> v0.8.3
```
修复 bug 后,我们可以提交 pr 给 `uuid`,一旦 pr 被合并到了 `master` 分支,你可以直接通过以下方式来使用补丁:
```shell
[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid' }
```
等未来新的内容更新到 `crates.io` 后,大家就可以移除这个补丁,直接更新 `[dependencies]` 中的 `uuid` 版本即可!
## 使用未发布的小版本
还是 `uuid` 包,这次假设我们要为它新增一个特性,同时我们已经修改完毕,在本地测试过,并提交了相应的 pr下面一起来看看该如何在它发布到 `crates.io` 之前继续使用。
再做一个假设,对于 `uuid` 来说,目前 `crates.io` 上的版本是 `1.0.0`,在我们提交了 pr 并合并到 `master` 分支后,`master` 上的版本变成了 `1.0.1`,这意味着未来 `crates.io` 上的版本也将变成 `1.0.1`
为了使用新加的特性,同时当该包在未来发布到 `crates.io` 后,我们可以自动使用 `crates.io` 上的新版本,而无需再使用 `patch` 补丁,可以这样修改 `Cargo.toml`
```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' }
```
注意,我们将 `[dependencies]` 中的 `uuid` 版本提前修改为 `1.0.1`,由于该版本在 `crates.io` 尚未发布,因此 `patch` 版本会被使用。
现在,我们的项目是基于 `patch` 版本的 `uuid` 来构建,也就是从 `gihtub``master` 分支中拉取最新的 `commit` 来构建。一旦未来 `crates.io` 上有了 `1.0.1` 版本,那项目就会继续基于 `crates.io` 来构建,此时,`patch` 就可以删除了。
#### 间接使用 `patch`
现在假设项目 `A` 的依赖是 `B``uuid`,而 `B` 的依赖也是 `uuid`,此时我们可以让 `A``B` 都使用来自 `github``patch` 版本,配置如下:
```toml
[package]
name = "my-binary"
version = "0.1.0"
[dependencies]
my-library = { git = 'https://example.com/git/my-library' }
uuid = "1.0.1"
[patch.crates-io]
uuid = { git = 'https://github.com/uuid-rs/uuid' }
```
如上所示,`patch` 不仅仅对于 `my-binary` 项目有用,对于 `my-binary` 的依赖 `my-library` 来说,一样可以间接生效。
#### 非crates.io的patch
若我们想要覆盖的依赖并不是来自 `crates.io` ,就需要对 `[patch]` 做一些修改。例如依赖是 `git` 仓库,然后使用本地路径来覆盖它:
```shell
[patch."https://github.com/your/repository"]
my-library = { path = "../my-library/path" }
```
easy轻松搞定!
## 使用未发布的大版本
现在假设我们要发布一个大版本 `2.0.0`,与之前类似,可以将 `Cargo.toml` 修改如下:
```toml
[dependencies]
uuid = "2.0"
[patch.crates-io]
uuid = { git = "https://github.com/uuid-rs/uuid", branch = "2.0.0" }
```
此时 `2.0` 版本在 `crates.io` 上还不存在,因此我们使用了 `patch` 版本且指定了 `branch = "2.0.0"`
#### 间接使用 `patch`
这里需要注意,**与之前的小版本不同,大版本的 `patch` 不会发生间接的传递!**,例如:
```shell
[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', branch = '2.0.0' }
```
以上配置中, `my-binary` 将继续使用 `1.x.y` 系列的版本,而 `my-library` 将使用最新的 `2.0.0` patch。
原因是大版本更新往往会带来破坏性的功能Rust 为了让我们平稳的升级,采用了滚动的方式:在依赖图中逐步推进更新,而不是一次性全部更新。
## 多版本[patch]
在之前章节,我们介绍过如何使用 `package key` 来[重命名依赖包](https://course.rs/cargo/reference/specify-deps/intro.html#在-cargotoml-中重命名依赖),现在来看看如何使用它同时引入多个 `patch`
假设,我们对 `serde` 有两个新的 `patch` 需求:
- `serde` 官方解决了一个 `bug` 但是还没发布到 `crates.io`,我们想直接从 `git` 仓库的最新 `commit` 拉取版本 `1.*`
- 我们自己为 `serde` 添加了新的功能,命名为 `2.0.0` 版本,并将该版本上传到自己的 `git` 仓库中
为了满足这两个 `patch`,可以使用如下内容的 `Cargo.toml`
```toml
[patch.crates-io]
serde = { git = 'https://github.com/serde-rs/serde' }
serde2 = { git = 'https://github.com/example/serde', package = 'serde', branch = 'v2' }
```
第一行说明,第一个 `patch` 从官方仓库 `main` 分支的最新 `commit` 拉取,而第二个则从我们自己的仓库拉取 `v2` 分支,同时将其重命名为 `serde2`
这样,在代码中就可以分别通过 `serde``serde2` 引用不同版本的依赖库了。
## 通过[path]来覆盖依赖
有时我们只是临时性地对一个项目进行处理,因此并不想去修改它的 `Cargo.toml`。此时可以使用 `Cargo` 提供的路径覆盖方法: **注意,这个方法限制较多,如果可以,还是要使用 [patch]**。
`[patch]` 修改 `Cargo.toml` 不同,路径覆盖修改的是 `Cargo` 自身的[配置文件](https://course.rs/cargo/guide/cargo-cache.html#cargo-home) `$Home/.cargo/config.toml`:
```toml
paths = ["/path/to/uuid"]
```
`paths` 数组中的元素是一个包含 `Cargo.toml` 的目录(依赖包),在当前例子中,由于我们只有一个 `uuid`,因此只需要覆盖它即可。目标路径可以是相对的,也是绝对的,需要注意,如果是相对路径,那是相对包含 `.cargo``$Home` 来说的。
## 不推荐的[replace]
> `[replace]` 已经被标记为 `deprecated`,并将在未来被移除,请使用 `[patch]` 替代
虽然不建议使用,但是如果大家阅读其它项目时依然可能会碰到这种用法:
```toml
[replace]
"foo:0.1.0" = { git = 'https://github.com/example/foo' }
"bar:1.0.2" = { path = 'my/local/bar' }
```
语法看上去还是很清晰的,`[replace]` 中的每一个 `key` 都是 `Package ID` 格式,通过这种写法可以在依赖图中任意挑选一个节点进行覆盖。