use std::fmt::{self, Display, Formatter};
use std::sync::LazyLock;
use chrono::{DateTime, SecondsFormat, Utc};
use serde::{Deserialize, Serialize};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const GIT_COMMIT: Option<&str> = option_env!("GIT_COMMIT");
pub const PROFILE: &str = env!("BUILD_PROFILE");
pub const RUSTC: &str = env!("BUILD_RUSTC");
const GIT_DIRTY: Option<&str> = option_env!("GIT_DIRTY");
const BUILD_UNIX_TIME: &str = env!("BUILD_UNIX_TIME");
pub fn git_dirty() -> Option<bool> {
match GIT_DIRTY {
Some("true") => Some(true),
Some("false") => Some(false),
_ => None,
}
}
pub fn build_time() -> DateTime<Utc> {
BUILD_UNIX_TIME
.parse::<i64>()
.ok()
.and_then(|secs| DateTime::from_timestamp(secs, 0))
.unwrap_or_else(|| DateTime::from_timestamp(0, 0).expect("epoch is a valid timestamp"))
}
pub fn build_timestamp() -> String {
build_time().to_rfc3339_opts(SecondsFormat::Secs, true)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildInfo {
pub version: String,
pub git_commit: Option<String>,
pub git_dirty: Option<bool>,
pub profile: String,
pub rustc: String,
pub build_timestamp: String,
}
impl BuildInfo {
pub fn current() -> Self {
Self {
version: VERSION.to_string(),
git_commit: GIT_COMMIT.map(str::to_string),
git_dirty: git_dirty(),
profile: PROFILE.to_string(),
rustc: RUSTC.to_string(),
build_timestamp: build_timestamp(),
}
}
pub fn commit_short(&self) -> Option<String> {
self.git_commit.as_ref().map(|commit| {
let short: String = commit.chars().take(12).collect();
let suffix = match self.git_dirty {
Some(true) => "+dirty",
Some(false) => "",
None => "?",
};
format!("{short}{suffix}")
})
}
pub fn summary(&self) -> String {
let mut parts = Vec::new();
if let Some(commit) = self.commit_short() {
parts.push(commit);
}
parts.push(self.profile.clone());
format!("{} ({})", self.version, parts.join(", "))
}
}
impl Default for BuildInfo {
fn default() -> Self {
Self::current()
}
}
impl Display for BuildInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let commit = match &self.git_commit {
Some(c) => {
let dirty = match self.git_dirty {
Some(true) => " (dirty)",
Some(false) => "",
None => " (dirty: unknown)",
};
format!("{c}{dirty}")
}
None => "unknown".to_string(),
};
writeln!(f, "{}", self.version)?;
writeln!(f, "commit: {commit}")?;
writeln!(f, "profile: {}", self.profile)?;
writeln!(f, "rustc: {}", self.rustc)?;
write!(f, "built: {}", self.build_timestamp)
}
}
pub static LONG_VERSION: LazyLock<String> = LazyLock::new(|| BuildInfo::current().to_string());