server/lib/build.rs
//! Build script for `holger-server-lib`.
//!
//! Two jobs, all pure-Rust (no subprocess — see the `pure-rust-zero-shell-law`):
//!
//! 1. **Proto rerun hint.** It NO LONGER compiles protos. Proto codegen used to
//! live here (`tonic_build::compile_protos("proto/holger.proto")`), which
//! shells out to the `protoc` binary on every `cargo build` — failing on
//! checkouts without `protoc` and taking down this crate plus everything
//! path-dep'd on it. Per the owner's directive ("use an xtask, avoid shelling"),
//! codegen now happens ahead of time in `cargo xtask gen-proto` (protox +
//! tonic-build, pure Rust) and the result is committed at
//! `src/generated/holger.v1.rs`. All that's left here is a cheap rerun hint.
//!
//! 2. **Git-hash stamp.** Read the on-disk git plumbing (`.git/HEAD`, the
//! pointed-to loose ref, `packed-refs`) with `std::fs` — NEVER `git` /
//! `std::process::Command` — and emit the short commit sha as
//! `HOLGER_GIT_HASH` for `env!()` to bake into the version string. Handles
//! detached HEAD, packed refs, and linked worktrees, and degrades to
//! `unknown` (never fails the build) when no git metadata is present.
//!
//! **A published package names its commit** (lane GUN-VERSION-STAMP,
//! 2026-10-31). `cargo install holger` 3.0.0 printed `holger v3.0.0
//! (unknown)`: the crates.io copy has no `.git`. `cargo package` writes
//! `.cargo_vcs_info.json` (with the full `git.sha1`) into every package it
//! makes from a checkout, so a package takes its short sha from there, ahead
//! of the git plumbing (which, inside a package that sits in some other
//! checkout, would name THAT checkout's HEAD). No date, as before.
use std::fs;
use std::path::{Path, PathBuf};
fn main() {
// (1) Cheap, no subprocess: re-run when the proto source changes. The
// canonical proto is `proto/holger.proto` (compiled by `cargo xtask
// gen-proto`); regenerating the committed Rust is a deliberate manual step.
println!("cargo:rerun-if-changed=../../proto/holger.proto");
// (2) Git-hash stamp for the `holger v<ver> (<hash>)` version string.
let hash = packaged_short_hash()
.or_else(git_short_hash)
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=HOLGER_GIT_HASH={hash}");
}
/// The short sha of the commit a published package was made from, read from the
/// `.cargo_vcs_info.json` cargo writes into every package (absent in a checkout).
/// Read by hand: the build script adds no JSON crate to the graph.
fn packaged_short_hash() -> Option<String> {
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?);
let path = manifest_dir.join(".cargo_vcs_info.json");
let json = fs::read_to_string(&path).ok()?;
rerun_if_changed(&path);
vcs_info_short_hash(&json)
}
/// `git.sha1` of a `.cargo_vcs_info.json` text, cut to seven hex digits.
fn vcs_info_short_hash(json: &str) -> Option<String> {
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('"')?];
if sha.len() != 40 || !sha.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
Some(sha[..7].to_string())
}
/// Best-effort short commit hash, reading only the on-disk git plumbing.
fn git_short_hash() -> Option<String> {
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?);
let git_dir = find_git_dir(&manifest_dir)?;
let common_dir = common_dir(&git_dir);
let head_path = git_dir.join("HEAD");
rerun_if_changed(&head_path);
let head = fs::read_to_string(&head_path).ok()?;
let head = head.trim();
let full = if let Some(refname) = head.strip_prefix("ref:") {
let refname = refname.trim();
resolve_ref(refname, &git_dir, &common_dir)?
} else {
// Detached HEAD: the file holds the commit id directly.
head.to_string()
};
let full = full.trim();
if full.is_empty() {
return None;
}
// Short sha, matching `git rev-parse --short` default width.
Some(full.chars().take(7).collect())
}
/// Walk up from `start` until a `.git` directory or `.git` gitdir-file is found.
fn find_git_dir(start: &Path) -> Option<PathBuf> {
let mut dir = Some(start);
while let Some(d) = dir {
let dot_git = d.join(".git");
if dot_git.is_dir() {
return Some(dot_git);
}
if dot_git.is_file() {
// Linked worktree: `.git` is a file `gitdir: <path>`.
let contents = fs::read_to_string(&dot_git).ok()?;
let target = contents.trim().strip_prefix("gitdir:")?.trim();
let target = d.join(target);
return Some(normalize(&target));
}
dir = d.parent();
}
None
}
/// The shared common git dir for a (possibly linked-worktree) git dir. Worktrees
/// carry a `commondir` pointer; the main working copy is its own common dir.
fn common_dir(git_dir: &Path) -> PathBuf {
let commondir_file = git_dir.join("commondir");
if let Ok(contents) = fs::read_to_string(&commondir_file) {
let rel = contents.trim();
return normalize(&git_dir.join(rel));
}
git_dir.to_path_buf()
}
/// Resolve a `refs/…` name to a commit id: loose ref file first (worktree dir,
/// then common dir), then `packed-refs`.
fn resolve_ref(refname: &str, git_dir: &Path, common_dir: &Path) -> Option<String> {
for base in [git_dir, common_dir] {
let loose = base.join(refname);
if loose.is_file() {
rerun_if_changed(&loose);
if let Ok(s) = fs::read_to_string(&loose) {
let s = s.trim();
if !s.is_empty() {
return Some(s.to_string());
}
}
}
}
// packed-refs lives in the common dir.
let packed = common_dir.join("packed-refs");
if packed.is_file() {
rerun_if_changed(&packed);
if let Ok(contents) = fs::read_to_string(&packed) {
for line in contents.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') || line.starts_with('^') {
continue;
}
if let Some((sha, name)) = line.split_once(' ') {
if name.trim() == refname {
return Some(sha.trim().to_string());
}
}
}
}
}
None
}
/// Lightweight lexical `.`/`..` normalization (avoids `canonicalize`, which fails
/// on not-yet-existing paths and would resolve symlinks).
fn normalize(path: &Path) -> PathBuf {
let mut out = PathBuf::new();
for comp in path.components() {
match comp {
std::path::Component::ParentDir => {
out.pop();
}
std::path::Component::CurDir => {}
other => out.push(other.as_os_str()),
}
}
out
}
fn rerun_if_changed(path: &Path) {
println!("cargo:rerun-if-changed={}", path.display());
}