Rust编译那些事儿从cargo build到发布包的实战踩坑记录

司马尚萍 前端 阅读 1,584
赞 8 收藏
二维码
手机扫码查看
反馈

Rust编译失败,Cargo.toml配置搞死我了

昨天遇到一个离谱的问题,Rust项目死活编译不过,各种dependency冲突,折腾了一晚上才搞定。本来以为就是普通的版本问题,结果发现是Cargo.toml配置写错了,这里记录一下免得下次再踩。

Rust编译那些事儿从cargo build到发布包的实战踩坑记录

一开始看到error信息全是什么”failed to select a version for serde“、”conflicting requirements”这种,我以为又是常见的依赖版本冲突。之前写Node的时候经常遇到npm包版本冲突,Rust的Cargo应该也差不多吧?结果不是,这次问题出在我自己配置上。

各种尝试都失败了

先试了最简单粗暴的方法:

cargo clean
cargo update

没用,报错还是那些。然后想着可能是某个crate版本太老,手动去改Cargo.toml里的版本号,把所有的~改成=,或者直接指定具体版本。折腾了半天,要么是新版本有breaking change导致代码不兼容,要么是根本找不到对应的版本。

这里我踩了个坑,以为把所有dependencies都写成固定版本就能解决问题。结果越改越乱,cargo check的时候各种提示”cannot find package”,有些crate明明存在就是找不到。后来试了下发现,问题不在版本号本身,而是我理解错了dependencies的写法。

真正的问题在这里

翻了官方文档才意识到,我把dev-dependencies和dependencies混用了。我的测试代码需要一些特定的crate,但我把它们全放在了[dependencies]下面,导致生产环境也要依赖这些测试专用的包。而且某些测试包可能跟主项目的依赖版本冲突。

正确的写法应该是这样:

[package]
name = "my-project"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
reqwest = "0.11"

[dev-dependencies]
tokio-test = "0.4"
serial_test = "2.0"
mockito = "0.31"

[target.'cfg(unix)'.dependencies]
nix = "0.26"

dev-dependencies里的包只在测试、构建测试、运行benchmarks时才会被拉取和编译,正常build和run的时候是不会引入的。我之前把tokio-test、serial_test这些测试工具都放在了dependencies里,难怪总是版本冲突。

target相关依赖的坑

还有一个地方容易出错,就是[target.*]相关的配置。我有个项目需要根据不同平台使用不同依赖,刚开始写成了这样:

# 错误写法
[dependencies]
[target.'cfg(windows)'.dependencies.winapi]
version = "0.3"
optional = true

这样写是不对的!正确的写法应该是:

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true }

[target.'cfg(unix)'.dependencies]
nix = "0.26"

折腾了半天发现,target-specific dependencies不能嵌套在普通dependencies里面,必须单独写。这个坑真的坑了我很久,因为错误信息也不够明确,只是说解析失败。

features的配置也很重要

另一个踩坑点是features的写法。我的项目用了conditional compilation,想根据不同的feature启用不同功能:

[features]
default = ["logging", "http"]
logging = ["tracing", "tracing-subscriber"]
http = ["reqwest", "tokio/rt-multi-thread"]
web = ["actix-web", "serde_json"]

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"
reqwest = "0.11"
tokio = { version = "1.0", features = ["rt"] }
actix-web = { version = "4.0", optional = true }
serde_json = { version = "1.0", optional = true }

这里要注意,如果某个dependency被标记为optional = true,那它就必须出现在某一个feature的列表中,否则cargo build的时候会报warning,甚至可能在某些严格的CI环境中直接fail。我当时就是因为actix-web和serde_json这两个包虽然设了optional = true,但是没有在features里声明,导致编译失败。

缓存清理的正确姿势

解决这些问题后,记得要彻底清理缓存,不然Cargo可能还会用旧的resolve结果:

cargo clean
rm -rf target/
cargo update --workspace
cargo build

有时候cargo update还不够,需要用–workspace参数强制更新整个workspace的依赖关系。我之前的项目是monorepo结构,多个crate共享依赖,不加–workspace的话可能会有部分crate的依赖关系没有更新。

一些额外的调试技巧

如果还是有问题,可以用这些命令来调试:

cargo tree -d  # 查看依赖冲突
cargo tree --format "{p} {f}"  # 查看features
cargo metadata  # 查看完整的依赖元数据

cargo tree -d特别有用,能直观看出哪些包有版本冲突。我之前就是通过这个命令发现serde的不同版本在不同子crate中被引用,导致link失败。

还有个小技巧,如果遇到很奇怪的编译错误,可以试试在Cargo.toml中添加:

[profile.dev.package."*"]
opt-level = 0

[profile.test.package."*"]
opt-level = 0

这样可以让某些复杂的宏展开更清晰,方便定位问题所在。

以上是我踩坑后的总结

其实大部分Rust编译问题都是Cargo.toml配置不当导致的,特别是dependencies、dev-dependencies、target-dependencies的区别一定要搞清楚。另外features的声明和使用也要保持一致,optional的包必须出现在某个feature定义中。

这次的问题虽然最后解决了,但也提醒我平时写Cargo.toml的时候还是要仔细一点,别图省事把所有东西都往dependencies里塞。现在回头看,错误信息其实挺明确的,只是当时没仔细看,一直在version上纠结。

以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论