use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
fn main() {
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-env-changed=SOURCE_DATE_EPOCH");
println!("cargo::rerun-if-env-changed=OWNPG_BUILD_COMMIT");
for path in git_state_files() {
println!("cargo::rerun-if-changed={}", path.display());
}
println!("cargo::rustc-env=OWNPG_BUILD_COMMIT={}", commit_hash());
println!("cargo::rustc-env=OWNPG_BUILD_DATE={}", build_date());
}
fn workspace_root() -> Option<PathBuf> {
env::var_os("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.and_then(|dir| dir.parent()?.parent().map(Path::to_path_buf))
.filter(|root| root.join(".git").exists())
}
fn git_state_files() -> Vec<PathBuf> {
let Some(root) = workspace_root() else {
return Vec::new();
};
let git_path = |name: &str| {
git_output(&root, &["rev-parse", "--git-path", name]).map(|path| root.join(path))
};
let mut files: Vec<PathBuf> = git_path("HEAD").into_iter().collect();
if let Some(target) = git_output(&root, &["symbolic-ref", "-q", "HEAD"])
.and_then(|reference| git_path(&reference))
.filter(|path| path.is_file())
{
files.push(target);
}
files
}
fn git_output(root: &Path, arguments: &[&str]) -> Option<String> {
Command::new("git")
.arg("-C")
.arg(root)
.args(arguments)
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|text| text.trim().to_owned())
.filter(|text| !text.is_empty())
}
fn packaged_commit() -> Option<String> {
let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").map(PathBuf::from)?;
let text = fs::read_to_string(manifest_dir.join(".cargo_vcs_info.json")).ok()?;
let marker = "\"sha1\"";
let start = text.find(marker)? + marker.len();
let rest = text.get(start..)?;
let value_start = rest.find('"')? + 1;
let hash: String = rest
.get(value_start..)?
.chars()
.take_while(char::is_ascii_hexdigit)
.take(12)
.collect();
(hash.len() == 12).then_some(hash)
}
fn commit_hash() -> String {
if let Some(env_commit) = env::var("OWNPG_BUILD_COMMIT")
.ok()
.map(|value| value.trim().chars().take(12).collect::<String>())
.filter(|value| !value.is_empty())
{
return env_commit;
}
workspace_root()
.and_then(|root| git_output(&root, &["rev-parse", "--short=12", "HEAD"]))
.or_else(packaged_commit)
.unwrap_or_else(|| "unknown".to_owned())
}
fn build_date() -> String {
env::var("SOURCE_DATE_EPOCH")
.ok()
.and_then(|raw| raw.trim().parse::<i64>().ok())
.or_else(|| {
workspace_root()
.and_then(|root| git_output(&root, &["log", "-1", "--format=%ct"]))
.and_then(|text| text.parse::<i64>().ok())
})
.or_else(current_epoch)
.map(|epoch| {
let (year, month, day) = civil_from_days(epoch.div_euclid(86_400));
format!("{year:04}-{month:02}-{day:02}")
})
.unwrap_or_else(|| "unknown".to_owned())
}
fn current_epoch() -> Option<i64> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.ok()
.and_then(|elapsed| i64::try_from(elapsed.as_secs()).ok())
}
#[expect(
clippy::integer_division,
reason = "the civil date formula works in whole days"
)]
fn civil_from_days(days: i64) -> (i64, i64, i64) {
let shifted = days + 719_468;
let era = shifted.div_euclid(146_097);
let day_of_era = shifted.rem_euclid(146_097);
let year_of_era =
(day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
let month_index = (5 * day_of_year + 2) / 153;
let day = day_of_year - (153 * month_index + 2) / 5 + 1;
let month = if month_index < 10 {
month_index + 3
} else {
month_index - 9
};
let year = year_of_era + era * 400 + i64::from(month <= 2);
(year, month, day)
}