30e5abb6aa
RunManifest.commit read option_env!("AURA_COMMIT").unwrap_or("unknown"),
so a normal build's manifest identity field was a bare placeholder --
C8's audit trail (this run = this commit) had nothing to point back at
once runs are archived (C18 registry).
Add crates/aura-cli/build.rs: at compile time it shells out to the `git`
already in PATH -- `rev-parse HEAD`, plus a `-dirty` suffix when
`status --porcelain` is non-empty -- and emits
`cargo:rustc-env=AURA_COMMIT=<sha>`. The existing option_env! read is
untouched and now picks it up. Chose shelling to git over a libgit crate
to keep the zero-external-dependency commitment (C14/C18); the firewall
crate is aura-ingest, and a build-time process call adds no runtime dep.
No-git case (packaged source tree, no .git): build.rs returns early and
swallows git errors, leaving AURA_COMMIT unset so "unknown" still holds.
Staleness guard: build.rs emits rerun-if-changed on .git/HEAD and
.git/logs/HEAD (logs/HEAD grows on every HEAD move incl. branch switch),
so the captured sha is rebuilt when HEAD moves rather than going stale.
Determinism (C1) is intact: AURA_COMMIT is fixed at compile time, so two
runs of one build still produce byte-identical manifests.
Two tests that pinned the old "unknown" placeholder are relaxed to the
new structural contract (not scope creep -- they were the old contract):
the cli_run.rs JSON-shape prefix no longer pins the commit value, and
the main.rs unit test asserts commit is non-empty and stable across runs
instead of equal to "unknown". The headline contract (manifest.commit
contains the real HEAD sha) is pinned by run_manifest_commit_carries_
real_git_head (4552d0c). Verified: cargo test -p aura-cli green (5),
clippy --all-targets -D warnings clean.
closes #15
54 lines
2.2 KiB
Rust
54 lines
2.2 KiB
Rust
//! 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 <args>` 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<String> {
|
|
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())
|
|
}
|