diff --git a/crates/aura-cli/build.rs b/crates/aura-cli/build.rs new file mode 100644 index 0000000..8247015 --- /dev/null +++ b/crates/aura-cli/build.rs @@ -0,0 +1,53 @@ +//! Capture the git HEAD sha at compile time and expose it as `AURA_COMMIT`, so +//! the binary's RunManifest is self-identifying (C8/C18 audit trail: this run = +//! this commit). When there is no git (e.g. a packaged source tree), `AURA_COMMIT` +//! is left unset and `main.rs`'s `option_env!(...).unwrap_or("unknown")` holds. +//! +//! Zero runtime/build dependency by commitment (C14/C18): we shell out to the +//! `git` already in PATH rather than pulling in a libgit crate. + +use std::path::Path; +use std::process::Command; + +fn main() { + // build.rs runs with CWD = the crate dir (crates/aura-cli); the repo root, + // which owns `.git`, is two levels up. + let repo_root = Path::new("../.."); + let git_dir = repo_root.join(".git"); + + if !git_dir.exists() { + // No git: emit nothing; the `"unknown"` fallback in main.rs holds. + return; + } + + // Re-run when HEAD moves or its working tree changes so AURA_COMMIT never + // goes stale across commits. `.git/logs/HEAD` grows on every HEAD move + // (commit, checkout, reset), covering branch switches without resolving the + // current branch's ref file by hand. + println!("cargo:rerun-if-changed=../../.git/HEAD"); + println!("cargo:rerun-if-changed=../../.git/logs/HEAD"); + + let head = run_git(repo_root, &["rev-parse", "HEAD"]); + let Some(head) = head else { return }; + + // Append `-dirty` when the working tree has uncommitted changes, so a run + // built off an unclean tree is not silently attributed to the clean HEAD. + let dirty = run_git(repo_root, &["status", "--porcelain"]) + .map(|s| !s.is_empty()) + .unwrap_or(false); + + let commit = if dirty { format!("{head}-dirty") } else { head }; + println!("cargo:rustc-env=AURA_COMMIT={commit}"); +} + +/// Run `git ` in `dir`, returning trimmed stdout on success, `None` on any +/// failure (git missing, non-zero exit, non-utf8). Never panics: a build must +/// not fail because git is unavailable. +fn run_git(dir: &Path, args: &[&str]) -> Option { + let out = Command::new("git").current_dir(dir).args(args).output().ok()?; + if !out.status.success() { + return None; + } + let s = String::from_utf8(out.stdout).ok()?; + Some(s.trim().to_string()) +} diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 3de1f1d..8e8c66c 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -156,6 +156,11 @@ mod tests { // manifest carries the sample's known configuration. let (from, to) = r1.manifest.window; assert_eq!((from.0, to.0), (1, 7)); - assert_eq!(r1.manifest.commit, "unknown"); + // commit is the build's git identity (or the no-git "unknown" fallback); + // either way it is non-empty and fixed at compile time, so it is stable + // across runs of the same build (C1 determinism, already asserted above + // via `to_json()`). + assert!(!r1.manifest.commit.is_empty()); + assert_eq!(r1.manifest.commit, r2.manifest.commit); } } diff --git a/crates/aura-cli/tests/cli_run.rs b/crates/aura-cli/tests/cli_run.rs index c76f8cd..012d290 100644 --- a/crates/aura-cli/tests/cli_run.rs +++ b/crates/aura-cli/tests/cli_run.rs @@ -18,7 +18,10 @@ fn run_prints_json_and_exits_zero() { let line = stdout.trim_end(); // canonical cycle-0009 JSON shape: nested manifest + metrics, stable keys. - assert!(line.starts_with("{\"manifest\":{\"commit\":\"unknown\","), "got: {line}"); + // The manifest leads with a `commit` field; its value is the build's git + // identity (asserted by `run_manifest_commit_carries_real_git_head`), so + // this shape check pins only the surrounding structure, not the value. + assert!(line.starts_with("{\"manifest\":{\"commit\":\""), "got: {line}"); assert!(line.contains("\"broker\":\"sim-optimal(pip_size=0.0001)\""), "got: {line}"); assert!(line.contains("\"window\":[1,7]"), "got: {line}"); assert!(line.contains("\"metrics\":{\"total_pips\":"), "got: {line}");