use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA");
println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA_SHORT");
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let git_dir = find_git_dir(&manifest_dir);
if let Some(git_dir) = git_dir.as_deref() {
emit_rerun(&git_dir.join("HEAD"));
emit_rerun(&git_dir.join("packed-refs"));
if let Some(target) = head_ref_target(git_dir) {
emit_rerun(&git_dir.join(target));
}
}
emit_rerun(&manifest_dir.join(".cargo_vcs_info.json"));
let full_hash = env_nonempty("DIOXUS_CLI_GIT_SHA")
.or_else(|| git_dir.as_deref().and_then(read_head_sha))
.or_else(|| read_cargo_vcs_info(&manifest_dir))
.or_else(|| git_cli_sha(&manifest_dir));
if let Some(full_hash) = full_hash {
let full_hash = full_hash.trim();
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA={full_hash}");
let short_hash = env_nonempty("DIOXUS_CLI_GIT_SHA_SHORT")
.unwrap_or_else(|| full_hash.chars().take(7).collect());
println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA_SHORT={short_hash}");
}
}
fn env_nonempty(key: &str) -> Option<String> {
env::var(key).ok().filter(|s| !s.trim().is_empty())
}
fn emit_rerun(path: &Path) {
println!("cargo:rerun-if-changed={}", path.display());
}
fn find_git_dir(start: &Path) -> Option<PathBuf> {
for dir in start.ancestors() {
let candidate = dir.join(".git");
let meta = match fs::metadata(&candidate) {
Ok(m) => m,
Err(_) => continue,
};
if meta.is_dir() {
return Some(candidate);
}
if meta.is_file() {
let contents = fs::read_to_string(&candidate).ok()?;
let pointer = contents
.lines()
.find_map(|l| l.strip_prefix("gitdir:"))?
.trim();
let resolved = if Path::new(pointer).is_absolute() {
PathBuf::from(pointer)
} else {
dir.join(pointer)
};
return Some(resolved);
}
}
None
}
fn head_ref_target(git_dir: &Path) -> Option<String> {
let head = fs::read_to_string(git_dir.join("HEAD")).ok()?;
head.strip_prefix("ref:").map(|s| s.trim().to_owned())
}
fn read_head_sha(git_dir: &Path) -> Option<String> {
let head = fs::read_to_string(git_dir.join("HEAD")).ok()?;
let head = head.trim();
let Some(target) = head.strip_prefix("ref:").map(str::trim) else {
return is_sha(head).then(|| head.to_owned());
};
if let Ok(sha) = fs::read_to_string(git_dir.join(target)) {
let sha = sha.trim();
if is_sha(sha) {
return Some(sha.to_owned());
}
}
let packed = fs::read_to_string(git_dir.join("packed-refs")).ok()?;
for line in packed.lines() {
if line.starts_with('#') || line.starts_with('^') || line.is_empty() {
continue;
}
let (sha, name) = line.split_once(' ')?;
if name == target && is_sha(sha) {
return Some(sha.to_owned());
}
}
None
}
fn read_cargo_vcs_info(manifest_dir: &Path) -> Option<String> {
let contents = fs::read_to_string(manifest_dir.join(".cargo_vcs_info.json")).ok()?;
let after = contents.split("\"sha1\"").nth(1)?;
let sha = after.split('"').nth(1)?.trim();
is_sha(sha).then(|| sha.to_owned())
}
fn git_cli_sha(manifest_dir: &Path) -> Option<String> {
let output = Command::new("git")
.arg("-C")
.arg(manifest_dir)
.args(["rev-parse", "HEAD"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let sha = String::from_utf8(output.stdout).ok()?.trim().to_owned();
is_sha(&sha).then_some(sha)
}
fn is_sha(s: &str) -> bool {
matches!(s.len(), 40 | 64) && s.chars().all(|c| c.is_ascii_hexdigit())
}