4.3 KiB
+++ title = "Add --version flag with X.Y.Z+GitSha format" priority = 5 status = "todo" ticket_type = "feature" dependencies = [] +++
Goal
Add a --version / -V flag to nbd that prints the version in the format:
0.1.0+7e311d6
where 0.1.0 comes from Cargo.toml and 7e311d6 is the short git SHA of the commit the binary was built from, embedded at compile time via build.rs.
Why
Allows agents and users to confirm exactly which build of nbd is running without inspecting the binary separately.
Implementation plan
1. Create build.rs at the crate root
fn main() {
// Capture the short git SHA at build time.
// Falls back to "unknown" when git is unavailable (e.g. CI without repo).
let sha = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| if o.status.success() { Some(o.stdout) } else { None })
.and_then(|b| String::from_utf8(b).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println\!("cargo:rustc-env=GIT_SHORT_SHA={sha}");
// Re-run whenever HEAD changes (new commits).
println\!("cargo:rerun-if-changed=.git/HEAD");
println\!("cargo:rerun-if-changed=.git/refs");
}
Key points:
- Uses
std::process::Command— no extra build dependencies. - Graceful fallback to
"unknown"when git is absent (clean Nix sandbox builds may not have.git/). rerun-if-changeddirectives ensure the SHA is refreshed on every commit without forcing a full rebuild every run.
2. Add a VERSION constant in src/main.rs
Add near the top of main.rs after the existing CLAUDE_MD_SNIPPET constant:
/// Full version string embedded at compile time: `"X.Y.Z+shortsha"`.
const VERSION: &str = concat\!(env\!("CARGO_PKG_VERSION"), "+", env\!("GIT_SHORT_SHA"));
3. Wire VERSION into the clap #[command(...)] attribute
Change:
#[command(name = "nbd", about = "Manage work tickets for agent workflows")]
to:
#[command(name = "nbd", about = "Manage work tickets for agent workflows", version = VERSION)]
clap automatically handles -V / --version when version is set: it prints the string and exits 0. No manual subcommand or dispatch code needed.
4. Update README.md
Add a brief note in the Usage section or Installation section:
nbd --version # prints e.g. 0.1.0+7e311d6
5. Add an integration test in tests/integration.rs
/// `nbd --version` exits 0 and stdout contains the semver.
#[test]
fn version_flag_exits_zero_with_semver() {
let tmp = tempfile::tempdir().expect("tempdir");
let output = std::process::Command::new(env\!("CARGO_BIN_EXE_nbd"))
.arg("--version")
.current_dir(tmp.path())
.output()
.expect("spawn nbd");
assert\!(output.status.success(), "--version should exit 0");
let stdout = String::from_utf8(output.stdout).unwrap();
// Should contain the package version (semver).
assert\!(stdout.contains("0.1.0"), "--version should include semver: {stdout}");
// Should contain a '+' separator and at least one hex char after it.
assert\!(stdout.contains('+'), "--version should contain '+': {stdout}");
}
Files to change
| File | Change |
|---|---|
build.rs |
Create — emit GIT_SHORT_SHA env var |
src/main.rs |
Add VERSION const; add version = VERSION to #[command] |
README.md |
Add nbd --version example |
tests/integration.rs |
Add version_flag_exits_zero_with_semver test |
Edge cases
- No git available (Nix sandbox):
build.rsfalls back to"unknown", producing0.1.0+unknown. This is acceptable for packaged builds where the version is already pinned by the derivation. - clap version output format: clap 4 prints
nbd X.Y.Z+sha(prefixed by the binary name) to stdout, then exits 0. This is standard behaviour — the test should checkstdout.contains("0.1.0")rather than an exact match. - No
--jsonsupport for--version: clap intercepts--versionbefore dispatch reaches the--jsonhandling. This is acceptable; version output is always plain text.
Validation
cargo fmt && cargo check && cargo clippy && cargo test
cargo run -- --version
# Expected output: nbd 0.1.0+<sha>