use regex::Regex;
use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::process::Command;
fn main() {
let build_rs_checksum = generate_build_rs_checksum().unwrap_or_else(|e| {
println!("cargo:warning=Failed to generate build.rs checksum: {}", e);
"unknown".to_string()
});
let build_time = chrono::Utc::now().format("%Y-%m-%d").to_string();
let version = env::var("APP_VERSION")
.or_else(|_| get_latest_semantic_git_tag())
.or_else(|_| get_git_describe())
.unwrap_or_else(|_| env::var("CARGO_PKG_VERSION").unwrap_or("unknown".to_string()));
println!("cargo:rustc-env=APP_VERSION={}", version);
println!("cargo:rustc-env=BUILD_RS_CHECKSUM={}", build_rs_checksum);
println!("cargo:rustc-env=BUILD_TIME={}", build_time);
println!("cargo:rerun-if-env-changed=APP_VERSION");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-changed=.git/refs/tags");
}
fn generate_build_rs_checksum() -> Result<String, Box<dyn std::error::Error>> {
let build_rs_path = "build.rs";
let contents = fs::read_to_string(build_rs_path)?;
let mut hasher = Sha256::new();
hasher.update(contents.as_bytes());
let result = hasher.finalize();
Ok(format!("{:x}", result))
}
fn get_latest_semantic_git_tag() -> Result<String, Box<dyn std::error::Error>> {
let output = Command::new("git")
.args(&["tag", "--list", "--sort=-version:refname"])
.output()?;
if !output.status.success() {
return Err("Git tag command failed".into());
}
let tags_output = String::from_utf8(output.stdout)?;
let semver_pattern = Regex::new(r"^v(\d+)\.(\d+)\.(\d+)$")?;
let mut valid_tags: Vec<(String, (u32, u32, u32))> = Vec::new();
for line in tags_output.lines() {
let tag = line.trim();
if let Some(caps) = semver_pattern.captures(tag) {
let major: u32 = caps[1].parse()?;
let minor: u32 = caps[2].parse()?;
let patch: u32 = caps[3].parse()?;
valid_tags.push((tag.to_string(), (major, minor, patch)));
}
}
if valid_tags.is_empty() {
return Err(
"No semantic versioning tags found matching pattern v[0-9]+.[0-9]+.[0-9]+".into(),
);
}
valid_tags.sort_by(|a, b| {
let (maj_a, min_a, patch_a) = a.1;
let (maj_b, min_b, patch_b) = b.1;
maj_b
.cmp(&maj_a)
.then_with(|| min_b.cmp(&min_a))
.then_with(|| patch_b.cmp(&patch_a))
});
Ok(valid_tags[0].0.clone())
}
fn get_git_describe() -> Result<String, Box<dyn std::error::Error>> {
let output = Command::new("git")
.args(&["describe", "--always", "--dirty"])
.output()?;
if output.status.success() {
Ok(String::from_utf8(output.stdout)?.trim().to_string())
} else {
Err("Git command failed".into())
}
}