通过feature选择依赖版本
2023-12-03

[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_poemext_axum.

PS: 封面为奉贤骑行拍摄所得.