Rust编译那些事儿从cargo build到发布包的实战踩坑记录
Rust编译失败,Cargo.toml配置搞死我了
昨天遇到一个离谱的问题,Rust项目死活编译不过,各种dependency冲突,折腾了一晚上才搞定。本来以为就是普通的版本问题,结果发现是Cargo.toml配置写错了,这里记录一下免得下次再踩。
一开始看到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上纠结。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。

暂无评论