use std::path::{Path, PathBuf};
const BUILT_FROM: &str = "BUILT_FROM";
fn main() {
let packaged = packaged_sha();
let head = git_head();
let hash = packaged
.as_deref()
.or(head.as_deref())
.map(|s| s[..7].to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=HOLGER_UI_GIT_HASH={hash}");
println!("cargo:rerun-if-env-changed=HOLGER_UI_CONSOLE");
let here =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"));
let candidates: Vec<PathBuf> = std::env::var_os("HOLGER_UI_CONSOLE")
.map(PathBuf::from)
.into_iter()
.chain([here.join("console"), here.join("../../dist")])
.collect();
for c in &candidates {
println!("cargo:rerun-if-changed={}", c.display());
}
let dir = candidates.iter().find(|d| d.join("index.html").is_file());
if let Some(d) = dir.filter(|d| *d == &here.join("console")) {
check_built_from(d, packaged.as_deref(), head.as_deref());
}
let mut files = Vec::new();
if let Some(d) = dir {
walk(d, d, &mut files);
}
files.sort();
let mut out = String::from("/// The console bundle built into this program: (path in the bundle, bytes).\npub static CONSOLE: &[(&str, &[u8])] = &[\n");
for (rel, abs) in &files {
out.push_str(&format!(
" ({rel:?}, include_bytes!({:?})),\n",
abs.display().to_string()
));
}
out.push_str("];\n");
out.push_str(&format!("/// Where the build took it from (empty: no bundle).\npub static CONSOLE_FROM: &str = {:?};\n", dir.map(|d| d.display().to_string()).unwrap_or_default()));
let dest =
PathBuf::from(std::env::var("OUT_DIR").expect("cargo sets OUT_DIR")).join("console.rs");
std::fs::write(&dest, out).expect("write console.rs");
}
fn walk(root: &Path, dir: &Path, out: &mut Vec<(String, PathBuf)>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
walk(root, &p, out);
} else if dir == root && e.file_name() == BUILT_FROM {
} else if let Ok(rel) = p.strip_prefix(root) {
let abs = p.canonicalize().unwrap_or(p.clone());
out.push((rel.to_string_lossy().replace('\\', "/"), abs));
}
}
}
fn check_built_from(console: &Path, packaged: Option<&str>, head: Option<&str>) {
let stamp_path = console.join(BUILT_FROM);
println!("cargo:rerun-if-changed={}", stamp_path.display());
let stamp = std::fs::read_to_string(&stamp_path)
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
let fix = "rebuild it with `cargo xtask console` in the holger-ui checkout of that commit";
match (packaged, stamp.as_deref()) {
(Some(want), Some(got)) if got == want => {}
(Some(want), got) => panic!(
"[holger-ui-console-stale] this package of holger-ui commit {want} carries a console \
bundle in {} that {}: a published holger-ui must carry the console built from its \
own commit; {fix}",
console.display(),
match got {
Some(g) => format!("was built from holger-ui commit {g} ({BUILT_FROM})"),
None => format!("names no commit it was built from (no {BUILT_FROM})"),
}
),
(None, got) => {
if let (Some(h), Some(g)) = (head, got) {
if h != g {
println!(
"cargo:warning=holger-ui: the console in {} was built from {g}, not HEAD {h}; {fix}",
console.display()
);
}
}
}
}
}
fn packaged_sha() -> Option<String> {
let path =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?).join(".cargo_vcs_info.json");
println!("cargo:rerun-if-changed={}", path.display());
let json = std::fs::read_to_string(&path).ok()?;
let key = "\"sha1\"";
let rest = json[json.find(key)? + key.len()..].trim_start();
let rest = rest.strip_prefix(':')?.trim_start().strip_prefix('"')?;
let sha = &rest[..rest.find('"')?];
(sha.len() == 40 && sha.bytes().all(|b| b.is_ascii_hexdigit())).then(|| sha.to_string())
}
fn git_head() -> Option<String> {
let mut dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?);
let git_dir = loop {
let dot = dir.join(".git");
if dot.is_dir() {
break dot;
}
if dot.is_file() {
let text = std::fs::read_to_string(&dot).ok()?;
let target = PathBuf::from(text.trim().strip_prefix("gitdir:")?.trim());
break if target.is_absolute() {
target
} else {
dir.join(target)
};
}
if !dir.pop() {
return None;
}
};
let common = std::fs::read_to_string(git_dir.join("commondir"))
.ok()
.map(|c| {
let c = PathBuf::from(c.trim());
if c.is_absolute() {
c
} else {
git_dir.join(c)
}
})
.unwrap_or_else(|| git_dir.clone());
let head_path = git_dir.join("HEAD");
println!("cargo:rerun-if-changed={}", head_path.display());
let head = std::fs::read_to_string(&head_path).ok()?;
let head = head.trim();
let sha = match head.strip_prefix("ref:") {
None => head.to_string(),
Some(name) => {
let name = name.trim();
for base in [&git_dir, &common] {
let loose = base.join(name);
if let Ok(s) = std::fs::read_to_string(&loose) {
println!("cargo:rerun-if-changed={}", loose.display());
return full(s.trim());
}
}
let packed = common.join("packed-refs");
println!("cargo:rerun-if-changed={}", packed.display());
std::fs::read_to_string(&packed)
.ok()?
.lines()
.find_map(|l| l.strip_suffix(name).map(|s| s.trim().to_string()))?
}
};
full(&sha)
}
fn full(sha: &str) -> Option<String> {
(sha.len() == 40 && sha.bytes().all(|b| b.is_ascii_hexdigit())).then(|| sha.to_string())
}