wt/version.rs
1//! Build and version metadata surfaced by `wt --version` / `wt -V`.
2//!
3//! The plain semver alone is rarely enough to diagnose a report, so the version
4//! output also carries the build commit, profile, toolchain, and timestamp
5//! (issue #22). The raw facts are captured at compile time by `build.rs` and
6//! exposed here as constants; [`long_version`] assembles them into the
7//! multi-line string handed to `clap`.
8
9/// Crate semver from `Cargo.toml`.
10pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
11
12/// Short Git commit hash at build time (with a `-dirty` suffix when the working
13/// tree had uncommitted changes), or `"unknown"` when git was unavailable.
14pub const COMMIT_HASH: &str = env!("WT_COMMIT_HASH");
15
16/// ISO-8601 committer date of the build commit, or `"unknown"`.
17pub const COMMIT_DATE: &str = env!("WT_COMMIT_DATE");
18
19/// Cargo build profile the binary was compiled with (e.g. `debug`, `release`).
20pub const BUILD_PROFILE: &str = env!("WT_BUILD_PROFILE");
21
22/// `rustc` version used to compile the binary, or `"unknown"`.
23pub const RUSTC_VERSION: &str = env!("WT_RUSTC_VERSION");
24
25/// UTC build timestamp (RFC-3339), or `"unknown"`.
26pub const BUILD_TIMESTAMP: &str = env!("WT_BUILD_TIMESTAMP");
27
28/// Returns the rich, multi-line version string shown by `wt --version` and
29/// `wt -V`. `clap` prefixes the first line with the binary name (`wt`).
30///
31/// The string is built once and cached so it can be handed to `clap` as a
32/// `&'static str`.
33pub fn long_version() -> &'static str {
34 static VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
35 VERSION
36 .get_or_init(|| {
37 format_version(
38 PKG_VERSION,
39 COMMIT_HASH,
40 COMMIT_DATE,
41 BUILD_PROFILE,
42 RUSTC_VERSION,
43 BUILD_TIMESTAMP,
44 )
45 })
46 .as_str()
47}
48
49/// Assembles the multi-line version body from individual build facts. Kept
50/// separate from [`long_version`] so the layout is unit-testable without
51/// depending on the values baked in at compile time.
52fn format_version(
53 version: &str,
54 commit: &str,
55 commit_date: &str,
56 profile: &str,
57 rustc: &str,
58 built: &str,
59) -> String {
60 format!(
61 "{version}\n\
62 commit: {commit} ({commit_date})\n\
63 profile: {profile}\n\
64 rustc: {rustc}\n\
65 built: {built}"
66 )
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn format_version_lays_out_all_facts() {
75 let out = format_version(
76 "1.2.3",
77 "abc123",
78 "2026-01-01T00:00:00Z",
79 "release",
80 "rustc 1.96.0",
81 "2026-06-08T12:00:00Z",
82 );
83 assert_eq!(
84 out,
85 "1.2.3\n\
86 commit: abc123 (2026-01-01T00:00:00Z)\n\
87 profile: release\n\
88 rustc: rustc 1.96.0\n\
89 built: 2026-06-08T12:00:00Z"
90 );
91 }
92
93 #[test]
94 fn long_version_starts_with_semver_and_includes_build_facts() {
95 let out = long_version();
96 assert!(out.starts_with(PKG_VERSION));
97 assert!(out.contains("commit:"));
98 assert!(out.contains("profile:"));
99 assert!(out.contains("rustc:"));
100 assert!(out.contains("built:"));
101 }
102
103 #[test]
104 fn constants_are_populated() {
105 // build.rs always emits a value (real or the "unknown" fallback).
106 assert!(!COMMIT_HASH.is_empty());
107 assert!(!COMMIT_DATE.is_empty());
108 assert!(!BUILD_PROFILE.is_empty());
109 assert!(!RUSTC_VERSION.is_empty());
110 assert!(!BUILD_TIMESTAMP.is_empty());
111 }
112}