Skip to main content

memstead_base/
build_info.rs

1//! Build identity of the running binary.
2//!
3//! Between releases every dev build reports the same crate semver, so
4//! version-keyed signals — the plan-05 "engine version changed →
5//! re-read the tool roster" hint and the plan-02 mutation-stamp /
6//! `ENGINE_VERSION_SKEW` comparison — could never fire in dogfood or
7//! field use. `build.rs` captures the git commit at build time
8//! (`MEMSTEAD_BUILD_SHA`, empty outside a git checkout); this module
9//! renders the full build version every version-carrying surface
10//! serves: CLI `--version`, both MCP flavours' `serverInfo.version`,
11//! the overview's `_engine_version`, and the per-mem mutation stamp.
12
13/// The short git sha of the commit this binary was built from, with a
14/// `-dirty` suffix when tracked files were modified at build time.
15/// Empty for builds outside a git checkout (crates.io, vendored
16/// trees) — emptiness, not absence, is the sha-less signal.
17pub const BUILD_SHA: &str = env!("MEMSTEAD_BUILD_SHA");
18
19/// The full build version of the running binary: the bare crate
20/// semver ([`crate::ENGINE_VERSION`]) when no build sha exists, else
21/// `<semver>+g<sha>[-dirty]` (semver build-metadata syntax, so the
22/// value still parses as a `semver::Version`). Two dev builds of the
23/// same crate version compare unequal whenever their commits differ —
24/// exactly the property the skew stamp and the roster-refresh hint
25/// need.
26pub fn full_version() -> &'static str {
27    static FULL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
28    FULL.get_or_init(|| {
29        if BUILD_SHA.is_empty() {
30            crate::ENGINE_VERSION.to_string()
31        } else {
32            format!("{}+g{}", crate::ENGINE_VERSION, BUILD_SHA)
33        }
34    })
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    /// The full build version is the bare semver exactly when no sha
42    /// was captured, else semver plus `+g<sha>` build metadata — and
43    /// either way it parses as a real `semver::Version` whose
44    /// version core equals the crate version.
45    #[test]
46    fn full_version_is_semver_with_optional_build_sha() {
47        let full = full_version();
48        if BUILD_SHA.is_empty() {
49            assert_eq!(full, crate::ENGINE_VERSION);
50        } else {
51            assert_eq!(
52                full,
53                format!("{}+g{}", crate::ENGINE_VERSION, BUILD_SHA).as_str()
54            );
55        }
56        let parsed: semver::Version = full.parse().expect("full build version parses as semver");
57        let bare: semver::Version = crate::ENGINE_VERSION.parse().unwrap();
58        assert_eq!(
59            (parsed.major, parsed.minor, parsed.patch),
60            (bare.major, bare.minor, bare.patch)
61        );
62    }
63}