- 开发工具
- CLI
【免费下载链接】watchexec
Executes commands in response to file modifications
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 str | clap 可直接使用的长版本字符串,含版本、features、构建日期,有 Git 信息时还包含 commit |
CRATE_VERSION | &'static str | crate 版本,来自 Cargo(也可直接读CARGO_PKG_VERSION环境变量) |
CRATE_FEATURES | &'static [&'static str] | 构建时启用的 feature 列表,小写、下划线转连字符 |
CRATE_FEATURE_STRING | &'static str | features 的字符串形式,格式+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 str | 8 字符短哈希 |
GIT_COMMIT_DATE | &'static str | commit 日期,YYYY-MM-DD |
GIT_COMMIT_DATETIME | &'static str | commit 日期时间 |
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目录结构:
- 定位仓库:从当前目录向上逐级查找
.git(info.rs),支持.git为文件时的 git worktree 情形(读取其中的gitdir: <path>指向); - 解析 HEAD:读取
HEAD文件,若为ref: refs/heads/...则解析引用的哈希,否则为 detached HEAD 直接使用哈希;引用可能位于 loose ref 文件或packed-refs中(info.rs); - 读取 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.6LONG_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
相关推荐
Bosion 构建信息收集指南:从 CHANGELOG 读懂 watchexec 生态的 clap 兼容版本信息生成方案
Bosion 构建信息收集指南:从 CHANGELOG 读懂 watchexec 生态的 clap 兼容版本信息生成方案 Bosion 是 watchexec
开发工具CLIDeepSeek Coder 33B Instruct性能评测:在HumanEval、MBPP等基准测试中的表现
DeepSeek Coder 33B Instruct性能评测:在HumanEval、MBPP等基准测试中的表现 DeepSeek Coder 33B Inst
fastfetch版本信息:软件版本与构建信息
fastfetch版本信息:软件版本与构建信息 概述 fastfetch作为一款高性能的系统信息工具,其自身的版本信息管理机制体现了开发者对软件质量和用户体验的
CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考