Skip to main content

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}