☰
Bosion:为 Rust CLI 生成 rustc -Vv 风格的长版本信息的构建期收集器
2026/9/29 3:12:28 网站建设 项目流程
  • 开发工具
  • CLI

【免费下载链接】watchexec

Executes commands in response to file modifications

项目地址:https://gitcode.com/gh_mirrors/wa/watchexec
点击查看免费下载

Bosion 是一个运行在 Cargo 构建脚本(build.rs)中的 Rust 库,用于在编译期采集 crate 版本、启用的 features、构建日期/时间以及 Git 提交信息,并生成可直接嵌入二进制程序的版本常量。本文以 crates/bosion/README.md 为主线,结合 lib.rs、info.rs 的源码实现,讲解其接入方式、四种输出形态、配置项与原理,并演示 Watchexec 是如何用它打造出--version的长版本输出的。

Bosion 解决什么问题

CLI 工具在排查问题时,--version输出越详细越有价值。rustc -Vv就是一个范例:除了版本号,还列出 commit-hash、commit-date、host、release、LLVM version 等字段。Bosion 的目标就是"Gather build information for verbose versions flags"——在构建期收集这些信息,供 verbose 版本标志使用。

它特别适合通过多种方式分发安装的 Rust 程序:cargo install、预编译二进制、带 Git 仓库的源码编译、以及不带 Git 的源码包(如 tarball)等。无论以哪种方式构建,都能生成合理可用的版本信息。Watchexec 的 CLI 正是它的实际使用者(见 crates/cli/Cargo.toml 中的[build-dependencies.bosion])。

快速上手:三步接入

1. 在 Cargo.toml 添加构建期依赖

在 crate 的[build-dependencies]中加入:

[build-dependencies] bosion = "2.0.0"

注意必须是build-dependencies而不是dependencies——Bosion 只在构建期运行,不会进入最终二进制。

2. 在 build.rs 调用 gather

fn main() { bosion::gather(); }

gather()是 lib.rs 中定义的便捷封装:它把收集结果写入OUT_DIR/bosion.rs(Cargo 约定的输出目录),生成一个pub(crate)可见、名为Bosion的零大小结构体,并通过cargo:rustc-env=BOSION_PATH=...把生成文件的绝对路径暴露给编译单元。

3. 在 src/main.rs 引入生成代码

include!(env!("BOSION_PATH")); fn main() { // 默认输出,格式类似 rustc -Vv println!("{}", Bosion::LONG_VERSION); // 追加自定义字段 println!("{}", Bosion::long_version_with(&[ ("custom data", "value"), ("LLVM version", "15.0.6"), ])); // 启用的 features,如 +feature +an-other println!("{}", Bosion::CRATE_FEATURE_STRING); // 原始数据 println!("{}", Bosion::GIT_COMMIT_HASH); println!("{}", Bosion::GIT_COMMIT_SHORTHASH); println!("{}", Bosion::GIT_COMMIT_DATE); println!("{}", Bosion::GIT_COMMIT_DATETIME); println!("{}", Bosion::CRATE_VERSION); println!("{:?}", Bosion::CRATE_FEATURES); println!("{}", Bosion::BUILD_DATE); println!("{}", Bosion::BUILD_DATETIME); }

include!(env!("BOSION_PATH"))在编译期把生成的结构体源码直接嵌入,因此所有常量都是&'static str或静态切片,运行时零开销。

生成代码的成员一览

从 lib.rs 的代码生成模板可以看出,Bosion结构体包含以下常量:

成员类型含义
LONG_VERSION&'static strclap 可直接使用的长版本字符串,含版本、features、构建日期,有 Git 信息时还包含 commit
CRATE_VERSION&'static strcrate 版本,来自 Cargo(也可直接读CARGO_PKG_VERSION环境变量)
CRATE_FEATURES&'static [&'static str]构建时启用的 feature 列表,小写、下划线转连字符
CRATE_FEATURE_STRING&'static strfeatures 的字符串形式,格式+feature +feature2
BUILD_DATE&'static str构建日期,YYYY-MM-DD
BUILD_DATETIME&'static str构建日期时间,YYYY-MM-DD HH:MM:SS
GIT_COMMIT_HASH&'static str完整 commit 哈希(启用gitfeature 且有仓库时)
GIT_COMMIT_SHORTHASH&'static str8 字符短哈希
GIT_COMMIT_DATE&'static strcommit 日期,YYYY-MM-DD
GIT_COMMIT_DATETIME&'static strcommit 日期时间
long_version_with(extra)方法(需stdfeature)在LONG_VERSION末尾按相同格式追加key: value对

日期与时间均为 UTC 格式(见 info.rs 中的format_description!宏定义)。生成代码自带完整文档注释,若使用公开可见性,还会出现在 docs.rs 的文档中。

与 clap 集成

LONG_VERSION被设计为 clap 的long_version直接可用:

use clap::Parser; include!(env!("BOSION_PATH")); #[derive(Parser)] #[clap(version, long_version = Bosion::LONG_VERSION)] struct Args { /* ... */ }

这正是 examples/clap/src/main.rs 中官方示例的做法,也是 crates/cli/src/args.rs 中 Watchexec CLI 的真实用法(long_version = Bosion::LONG_VERSION)。clap 的行为是:短-V输出version,长--version输出long_version。

高级用法

生成公开可见的结构体

// build.rs bosion::gather_pub();

gather_pub()(lib.rs)与gather()的唯一区别是生成pub struct Bosion而非pub(crate),便于在库 crate 中跨模块使用,并让生成代码出现在公开文档中。

自定义输出文件名与结构体名

// build.rs bosion::gather_to("buildinfo.rs", "Build", /* public? */ false);

gather_to(lib.rs)是底层实现,三个参数分别控制文件名、结构体名和可见性。文件总是写入OUT_DIR目录,同时把路径通过BOSION_PATH环境变量告知 rustc。

输出到环境变量(替代源码生成)

// build.rs bosion::gather_to_env(); // src/main.rs fn main() { println!("{}", env!("BOSION_GIT_COMMIT_HASH")); println!("{}", env!("BOSION_GIT_COMMIT_SHORTHASH")); println!("{}", env!("BOSION_GIT_COMMIT_DATE")); println!("{}", env!("BOSION_GIT_COMMIT_DATETIME")); println!("{}", env!("BOSION_BUILD_DATE")); println!("{}", env!("BOSION_BUILD_DATETIME")); println!("{}", env!("BOSION_CRATE_VERSION")); println!("{}", env!("BOSION_CRATE_FEATURES")); // 逗号分隔 }

这种方式不生成任何新代码,也不会把未使用的信息带进二进制,适合只用到少量字段的场景。它的局限是没有 clap 现成的LONG_VERSION字符串,需要自行拼接。

自定义环境变量前缀

// build.rs bosion::gather_to_env_with_prefix("MYAPP_");

gather_to_env_with_prefix(lib.rs)接受任意前缀,约定俗成用全大写并以_结尾,例如MYAPP_会产出MYAPP_GIT_COMMIT_HASH等变量。

Features 配置

Bosion 自身有三个 feature(见 Cargo.toml):

  • reproducible(默认开启):读取SOURCE_DATE_EPOCH环境变量(可复现构建标准),存在时用其时间戳替代当前时间;同时输出cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH,环境变化即触发重建。
  • git(默认开启):启用 Git 信息收集。这会引入可选的flate2依赖——Git 对象以 zlib 压缩存储,读取 loose 对象与 packfile 时需要解压。
  • std(默认开启):提供long_version_with方法。注意它控制的是使用方 crate的 std 支持,Bosion 自身始终依赖 std(它运行在 build.rs 中)。

需要指出的是,git与reproducible这两个默认 feature 的组合意味着:默认配置下,Bosion 构建时若找不到SOURCE_DATE_EPOCH,就采用当前时间作为构建日期,这会破坏可复现构建——因此需要可复现构建的发布流程应当显式设置该环境变量。

底层原理:从源码结构看收集过程

信息从哪来

info.rs 中的Info::gather()定义了所有数据的来源:

  • crate_version:读CARGO_PKG_VERSION环境变量(Cargo 构建脚本注入);
  • crate_features:遍历环境变量中所有CARGO_FEATURE_*前缀,将下划线替换为连字符并转小写(info.rs);
  • build_date/build_datetime:UTC 当前时间,或SOURCE_DATE_EPOCH指定的时间戳;
  • git:Git 仓库信息,读取失败时置为None,并打印cargo:warning=git info gathering failed: ...警告(info.rs)。

不依赖 git CLI 的 Git 读取

这是 Bosion 与同类工具的关键差异。从 info.rs 的GitInfo::gather()可以看出,它不使用git可执行文件,而是直接解析.git目录结构:

  1. 定位仓库:从当前目录向上逐级查找.git(info.rs),支持.git为文件时的 git worktree 情形(读取其中的gitdir: <path>指向);
  2. 解析 HEAD:读取HEAD文件,若为ref: refs/heads/...则解析引用的哈希,否则为 detached HEAD 直接使用哈希;引用可能位于 loose ref 文件或packed-refs中(info.rs);
  3. 读取 commit 时间戳:先在objects/xx/yyyy...中解压 loose 对象(zlib),未命中则遍历objects/pack/*.idx二分查找对象再读取 packfile(info.rs);read_commit_timestamp从 commit 对象内容中解析committer行的时间戳(info.rs)。

短哈希固定截取前 8 个字符(info.rs)。需要说明的是,pack 读取对 delta 压缩对象(类型 6/7)暂不支持,会返回None。

重新构建的触发条件

set_reruns()(info.rs)输出 Cargo 的 rerun 指令:

  • cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH(启用reproducible时);
  • cargo:rerun-if-changed=<git_root>/HEAD(存在 Git 信息时),即仓库 HEAD 变化会触发重建。

这保证了版本信息不会陈旧。gather_to还通过println!("cargo:rustc-env=BOSION_PATH=...")(lib.rs)把生成文件路径暴露给 rustc。

真实输出效果

README 给出了 Watchexec CLI 的--version输出:

watchexec 1.21.1 (5026793 2023-03-05) commit-hash: 5026793a12ff895edf2dafb92111e7bd1767650e commit-date: 2023-03-05 build-date: 2023-03-05 release: 1.21.1 features:

与rustc -Vv对比:

rustc 1.67.1 (d5a82bbd2 2023-02-07) binary: rustc commit-hash: d5a82bbd26e1ad8b7401f6a718a9c57c96905483 commit-date: 2023-02-07 host: x86_64-unknown-linux-gnu release: 1.67.1 LLVM version: 15.0.6

LONG_VERSION的生成逻辑在 lib.rs 中:有 Git 信息时首行为版本 (短哈希 提交日期) features,无 Git 时退化为版本 (构建日期) features;随后的key: value行固定包含 commit-hash/commit-date 或 build-date、release、features。features 字段来自CRATE_FEATURES的逗号拼接(含default)。long_version_with可以继续追加如LLVM version: 15.0.6这样的自定义行。

仓库的集成测试通过快照机制验证了这一点:examples/default/src/main.rs 与 examples/no-git/src/main.rs 将各常量与 snapshots 下的模板比对(模板中以{git hash}、{today date}等占位符动态替换实际值)。例如 default_long_version.txt 记录了带 Git 时的长版本模板,no_git_long_version.txt 记录了无 Git 时的退化形态;run-tests.sh 会对每个示例子 crate 依次执行cargo check与cargo test。

与同类库的取舍

README 的 "Why not...?" 一节列出了 Bosion 与常见替代品的差异:

  • bugreport / human-panic:运行期库,用于收集 bug 报告或 panic 信息,与构建期版本信息用途不同;
  • git-testament / vergen:通过调用gitCLI 获取信息,而 Bosion 直接解析.git目录,不依赖环境中是否存在 git 可执行文件;
  • shadow-rs:底层使用 libgit2,且不会在 Git 提交变化时自动重建。

Bosion 在 build.rs 之外零依赖,这也是它能覆盖cargo install、预编译二进制、带/不带 Git 的源码编译等多种安装方式的原因。选择哪款工具取决于具体场景:若你的构建环境保证有 git 可用,vergen 等也是合理选择;若追求构建环境无关性,Bosion 的纯目录解析方案更有优势。

适用场景与注意事项

  • 适用:需要rustc -Vv风格长版本输出的 CLI/库 crate;以多种方式分发安装的 Rust 程序;使用 clap 的--version增强。
  • 版本与运行环境:Bosion 2.x 要求 Rust 1.64.0+ 与 edition 2021(见 Cargo.toml),并依赖time0.3 crate 处理日期时间。
  • dirty 工作区:commit 相关常量取自 HEAD 提交本身,不感知未提交的改动(源码注释与生成的文档均已说明此限制)。
  • delta 压缩对象:当前实现对 packfile 中的 delta 对象暂不解析,极端情况下可能读不到提交时间戳,此时 Git 字段整体为None并发出构建警告。
  • 可复现构建:发布构建请设置SOURCE_DATE_EPOCH,否则默认使用当前时间。

按上述步骤接入后,运行cargo build时构建脚本会自动完成信息收集、代码生成与重跑触发,cargo run -- --version即可看到完整的版本信息。

  • 开发工具
  • CLI

【免费下载链接】watchexec

Executes commands in response to file modifications

项目地址:https://gitcode.com/gh_mirrors/wa/watchexec
点击查看免费下载
上一篇:Wayback Machine网页存档扩展:你的终极互联网时光机使用指南
下一篇:告别臃肿Windows 11:一键优化让电脑重获新生的终极方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询