第8章 Cargo.toml vs Cargo.lock:依赖管理的两个维度
Cargo.toml 和 Cargo.lock 的用途各不相同。先给出一个简要总结:
Cargo.toml用于宽泛地描述你的依赖,由你自己编写。Cargo.lock包含依赖的确切信息,由 Cargo 维护,不应手动修改。
拿不准的话,就把 Cargo.lock 提交到版本控制系统(如 Git)里。想了解为什么这样做、以及有哪些替代方案,可以参考FAQ 中的「为什么把 Cargo.lock 纳入版本控制?」。建议配合阅读验证最新依赖。
下面展开讲讲。
Cargo.toml 是一个清单文件,你可以在其中为 package 指定各种元数据。比如,声明你依赖另一个 package:
[package]
name = "hello_world"
version = "0.1.0"
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git" }
这个 package 只有一个依赖:regex 库,来源是 GitHub 上的某个 Git 仓库。由于你没有指定其他信息,Cargo 会默认使用默认分支上的最新 commit 来构建这个 package。
听起来没问题?但有个隐患:如果你今天构建了这个 package,把副本发给我,我明天再构建,就可能出问题——这期间 regex 可能又有了新的 commit,我的构建会包含这些新 commit,而你的不会,于是我们得到的是不同的构建结果。而我们需要的是可复现的构建,所以这很糟糕。
解决办法是在 Cargo.toml 里指定具体的 rev 值,让 Cargo 在构建时确切知道该用哪个版本:
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git", rev = "9f9f693" }
这样构建结果就一致了。但代价也不小:每次想更新依赖库,都得手动去查 SHA-1 值,既繁琐又容易出错。
接下来看看 Cargo.lock。得益于它的存在,你无需手动追踪确切的代码修订版本:Cargo 会替你处理。如果你的清单(Manifest)如下所示:
[package]
name = "hello_world"
version = "0.1.0"
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git" }
首次构建时,Cargo 会获取最新的提交,并将这些信息写入 Cargo.lock 文件。该文件内容大致如下:
[[package]]
name = "hello_world"
version = "0.1.0"
dependencies = [
"regex 1.5.0 (git+https://github.com/rust-lang/regex.git#9f9f693768c584971a4d53bc3c586c33ed3a6831)",
]
[[package]]
name = "regex"
version = "1.5.0"
source = "git+https://github.com/rust-lang/regex.git#9f9f693768c584971a4d53bc3c586c33ed3a6831"
可以看到这里包含更多信息,包括用于构建的确切修订版本(Revision)。当你把包交给其他人时,即使你在 Cargo.toml 中未指定 SHA,他们也会使用完全相同的 SHA。
当你准备升级到库的新版本时,Cargo 可以重新计算依赖项并为你更新内容:
$ cargo update # 更新所有依赖项
$ cargo update regex # 仅更新 “regex”
这会将包含新版本信息的新 Cargo.lock 写入磁盘。注意,cargo update 的参数实际上是一个 Package ID Specification,而 regex 只是其中的一种简写形式。