transcribe_cpp/version.rs
1//! Version + ABI introspection and the load-time version gate.
2//!
3//! Pre-1.0 the on-disk ABI may break between minor releases, so the binding
4//! and the linked library must agree on the base `MAJOR.MINOR.PATCH`. The gate
5//! runs once, lazily, on the first model load (and is exposed directly so a
6//! host can check up front). Packaging-only suffixes on the runtime string are
7//! tolerated — only the leading release segment is compared.
8
9use std::sync::OnceLock;
10
11use transcribe_cpp_sys as sys;
12
13use crate::error::{Error, Result};
14use crate::result::owned_str;
15use crate::types::AbiStruct;
16
17/// The base version string this crate's bindings were built against.
18///
19/// Taken from this crate's own `Cargo.toml` (`CARGO_PKG_VERSION`), not the
20/// generated FFI macros: a version-only bump must not churn the committed
21/// bindings or the abihash (notes/releasing.md §8 P0 #1). The generators no
22/// longer emit `TRANSCRIBE_VERSION_*`, so this is also the only source left.
23pub fn compiled_version() -> String {
24 base(env!("CARGO_PKG_VERSION")).to_string()
25}
26
27/// The `MAJOR.MINOR.PATCH` version string of the linked native library.
28pub fn version() -> String {
29 owned_str(unsafe { sys::transcribe_version() })
30}
31
32/// The short git commit the native library was built from (or "unknown").
33pub fn version_commit() -> String {
34 owned_str(unsafe { sys::transcribe_version_commit() })
35}
36
37/// The public-ABI digest the committed bindings were generated against.
38pub fn header_hash() -> &'static str {
39 sys::PUBLIC_HEADER_HASH
40}
41
42/// The native library's `sizeof` for a public ABI struct, or 0 if unknown.
43pub fn abi_struct_size(which: AbiStruct) -> usize {
44 unsafe { sys::transcribe_abi_struct_size(which.to_raw()) }
45}
46
47/// The native library's `alignof` for a public ABI struct, or 0 if unknown.
48pub fn abi_struct_align(which: AbiStruct) -> usize {
49 unsafe { sys::transcribe_abi_struct_align(which.to_raw()) }
50}
51
52/// Leading dotted-numeric release segment ("0.0.1.post1" -> "0.0.1").
53fn base(v: &str) -> &str {
54 let end = v
55 .find(|c: char| !(c.is_ascii_digit() || c == '.'))
56 .unwrap_or(v.len());
57 v[..end].trim_end_matches('.')
58}
59
60static GATE: OnceLock<std::result::Result<(), String>> = OnceLock::new();
61
62/// Run (once) the pre-1.0 base-version lock against the loaded library.
63pub(crate) fn ensure_compatible() -> Result<()> {
64 let outcome = GATE.get_or_init(|| {
65 let runtime = version();
66 let compiled = compiled_version();
67 if base(&runtime) == base(&compiled) {
68 Ok(())
69 } else {
70 Err(format!(
71 "loaded transcribe library is {runtime}, but these bindings \
72 were generated for {compiled} (base versions must match \
73 pre-1.0)"
74 ))
75 }
76 });
77 outcome.clone().map_err(Error::VersionMismatch)
78}