Skip to main content

rskit_version/
info.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4const MAIN_BRANCH: &str = "main";
5const MASTER_BRANCH: &str = "master";
6const SHORT_COMMIT_LEN: usize = 7;
7const GIT_BRANCH: &str = env!("GIT_BRANCH");
8
9/// Immutable snapshot of build/version metadata. Compatible with gokit and pykit `VersionInfo`.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct VersionInfo {
12    /// Crate version from `Cargo.toml` (e.g. `"0.1.0-alpha.1"`).
13    pub version: String,
14    /// Full git commit hash at build time, or empty if unavailable.
15    pub git_commit: String,
16    /// Git branch name at build time, or empty if unavailable.
17    pub git_branch: String,
18    /// UTC build timestamp in RFC 3339 format, or empty if unavailable.
19    pub build_time: String,
20    /// Rust compiler version string (e.g. `"rustc 1.91.0 ..."`).
21    pub rust_version: String,
22    /// `true` when `version` is not `"dev"` and does not contain `"dirty"`.
23    pub is_release: bool,
24}
25
26impl fmt::Display for VersionInfo {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.write_str(&self.full_version())
29    }
30}
31
32impl VersionInfo {
33    /// Return the package version without build metadata.
34    pub fn package_version(&self) -> &str {
35        &self.version
36    }
37
38    /// Return the package version parsed as a semantic version, if valid.
39    pub fn semver(&self) -> Option<crate::semver::Version> {
40        crate::semver::parse_version(&self.version)
41    }
42
43    /// Return whether this package version matches a semantic version requirement.
44    ///
45    /// Returns `None` if either the version or requirement string is invalid.
46    pub fn matches_requirement(&self, requirement: &str) -> Option<bool> {
47        crate::semver::matches_requirement(&self.version, requirement)
48    }
49
50    /// Return the short git commit hash, if available.
51    pub fn short_git_commit(&self) -> Option<&str> {
52        if self.git_commit.is_empty() {
53            None
54        } else {
55            Some(short_commit(&self.git_commit))
56        }
57    }
58
59    /// Returns a concise version string: `{version}[-{short_commit}]`.
60    pub fn short_version(&self) -> String {
61        self.short_git_commit().map_or_else(
62            || self.version.clone(),
63            |commit| format!("{}-{commit}", self.version),
64        )
65    }
66
67    /// Returns a detailed version string with optional branch and build time.
68    pub fn full_version(&self) -> String {
69        let mut parts = vec![self.version.clone()];
70
71        if let Some(commit) = self.short_git_commit() {
72            parts.push(commit.to_owned());
73        }
74
75        if !self.git_branch.is_empty()
76            && self.git_branch != MAIN_BRANCH
77            && self.git_branch != MASTER_BRANCH
78        {
79            parts.push(self.git_branch.clone());
80        }
81
82        let mut version = parts.join("-");
83
84        if !self.build_time.is_empty() {
85            use std::fmt::Write;
86            let _ = write!(version, " (built {})", self.build_time);
87        }
88
89        version
90    }
91}
92
93fn short_commit(commit: &str) -> &str {
94    commit
95        .char_indices()
96        .nth(SHORT_COMMIT_LEN)
97        .map_or(commit, |(idx, _)| &commit[..idx])
98}
99
100/// Return the Cargo package version for this crate.
101pub const fn package_version() -> &'static str {
102    env!("CARGO_PKG_VERSION")
103}
104
105/// Return the Cargo package version parsed as a semantic version, if valid.
106pub fn package_semver() -> Option<crate::semver::Version> {
107    crate::semver::parse_version(package_version())
108}
109
110/// Returns comprehensive version information collected at compile time.
111pub fn get_version_info() -> VersionInfo {
112    let version = package_version().to_owned();
113    let git_commit = env!("GIT_COMMIT").to_owned();
114    let git_branch = if GIT_BRANCH.is_empty() {
115        String::new()
116    } else {
117        GIT_BRANCH.to_owned()
118    };
119    let build_time = env!("BUILD_EPOCH")
120        .parse::<i64>()
121        .ok()
122        .and_then(rskit_util::time::format_rfc3339)
123        .unwrap_or_default();
124    let rust_version = env!("RUST_VERSION_STR").to_owned();
125
126    let is_release = version != "dev" && !version.contains("dirty");
127
128    VersionInfo {
129        version,
130        git_commit,
131        git_branch,
132        build_time,
133        rust_version,
134        is_release,
135    }
136}
137
138/// Returns a concise version string: `{version}[-{short_commit}]`.
139pub fn get_short_version() -> String {
140    get_version_info().short_version()
141}
142
143/// Returns a detailed version string with optional branch and build time.
144///
145/// Format: `{version}[-{short_commit}][-{branch}] (built {time})` Branches named `main`
146/// or `master` are omitted.
147pub fn get_full_version() -> String {
148    get_version_info().full_version()
149}
150
151/// Returns `true` when this build represents a release (not dev, not dirty).
152pub fn is_release() -> bool {
153    get_version_info().is_release
154}