[HowTo] 通过feature选择依赖版本
问题概述
最近在尝试将我的 ws-tool 集成到 axum 和 poem 时遇到一个问题, ws-tool 自身依赖 http 库, axum 和 poem 直接或间接的也依赖这个库, 为了方便描述, 将 ws-tool 依赖的 http 称为 http-self, axum 或 poem 依赖的 http 称为 http-other. 假设 ws-tool 自己定义了一个接受 http request 的函数, 如
use http_self::Request; pub fn check_req(req: Request) { todo!() }
假设需要在 axum 里使用这个函数
pub async fn handler(req: axum::extract::Request) -> Response { let (parts, _) = req.into_parts(); // 这里的 http 为 axum依赖的http版本, 即 http-other let req = http::Request::from_parts(parts, ()); // L1 todo!() }
如果 http-other 和 http-self 版本兼容, 则没什么问题, 但恰好最近 hyper 及其底层库(包括http)都进行了一次大版本更新, 各自从 0.x 升级升级到 1.x. axum 新版本依赖 http 1.x, 而 ws-tool 和 poem 还依赖旧版本, 而且 ws-tool 还必须同时兼容 poem 和 axum, poem 升级 http 1.x 不能立马完成, 所以这要求 ws-tool 必须同时兼容 http 新老版本.
解决方案
因为 poem 和 axum 集成支持不太可能同时使用, 所以可以使用 feature 控制只编译某个集成代码, 所以问题就变成了, 如何在一个库里通过 feature 控制依赖版本. 还是以 http 为例, 假设要求开启 old feature 时使用 http 0.2.x, 开启 new feature 时使用 1.x. 如果直接在 Cargo.toml 这样写显然是不行的
[dependencies] http = { version = "0.2" } http = { version = "1" }
但如果能重命名依赖, 让新旧版本不同名, 这样编译不会报错, 然后通过重新导出并重命名即可通过 feature 选择不同版本. 刚好, ra 的作者实现了依赖重命名这个功能, 详情见issue
所以我们可以这样修改 Cargo.toml
[dependencies] http0_x = { version = "0.2", package = "http", optional = true } http1_x = { version = "1", package = "http", optional = true} [features] old = ["dep:http0_x"] new = ["dep:http1_x"]
重新导出的代码如下
mod shim { #[cfg(all(feature = "old", not(feature = "new")))] pub use http0_x as http; // 如果偶然开启 all-features 编译, 自动选择老版本 #[cfg(all(feature = "new", feature = "old"))] pub use http0_x as http; #[cfg(all(feature = "new", not(feature = "old")))] pub use http1_x as http; } pub use shim::http;
如果使用 -F old
编译, 则 ws-tool 将选择 http 0.2.x, -F new
则是 http 1.x.
总结
通过依赖项重命名和重新导出可以实现使用feature控制项目某个依赖使用不同版本.
最后也推荐下我写的高性能,易用的 websocket 库 ws-tool, 最近添加了 axum 和 poem 集成支持, 具体使用例子可以参考 ext_poem 和 ext_axum.
PS: 封面为奉贤骑行拍摄所得.