diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e3375af --- /dev/null +++ b/build.rs @@ -0,0 +1,47 @@ +fn main() { + let git_version = std::process::Command::new("git") + .args(["describe", "--tags", "--always", "--dirty"]) + .output() + .ok() + .filter(|o| o.status.success()) + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| { + let s = s.trim(); + let s = s.strip_prefix('v').unwrap_or(s); + // "0.13.1" → clean tag → "0.13.1" + // "0.13.1-9-ga87f907" → ahead → "0.13.1+a87f907" + // "0.13.1-9-ga87f907-dirty" → dirty → "0.13.1+a87f907-dirty" + // "a87f907" → no tags → "0.0.0+a87f907" + // "a87f907-dirty" → no tags → "0.0.0+a87f907-dirty" + if let Some((base, rest)) = s.split_once("-") { + // Could be "0.13.1-9-ga87f907[-dirty]" or "a87f907-dirty" + if base.contains('.') { + // Tagged: extract sha from "-N-gSHA[-dirty]" + let parts: Vec<&str> = rest.splitn(3, '-').collect(); + match parts.as_slice() { + [_n, sha] => format!("{}+{}", base, sha.strip_prefix('g').unwrap_or(sha)), + [_n, sha, "dirty"] => { + format!("{}+{}-dirty", base, sha.strip_prefix('g').unwrap_or(sha)) + } + _ => s.to_string(), + } + } else { + // Untagged: "sha-dirty" + format!("0.0.0+{}", s) + } + } else if s.contains('.') { + // Exact tag match: "0.13.1" + s.to_string() + } else { + // Bare sha, no tags at all + format!("0.0.0+{}", s) + } + }); + + if let Some(v) = git_version { + println!("cargo:rustc-env=NUMA_BUILD_VERSION={}", v); + } + + println!("cargo:rerun-if-changed=.git/HEAD"); + println!("cargo:rerun-if-changed=.git/refs/tags/"); +} diff --git a/src/api.rs b/src/api.rs index f8b2702..dd1fe78 100644 --- a/src/api.rs +++ b/src/api.rs @@ -540,7 +540,7 @@ async fn stats(State(ctx): State>) -> Json { }; Json(StatsResponse { - version: env!("CARGO_PKG_VERSION"), + version: crate::version(), uptime_secs: snap.uptime_secs, upstream, mode: ctx.upstream_mode.as_str(), diff --git a/src/health.rs b/src/health.rs index e55c569..5767f4b 100644 --- a/src/health.rs +++ b/src/health.rs @@ -43,7 +43,7 @@ impl HealthMeta { #[cfg(test)] pub fn test_fixture() -> Self { HealthMeta { - version: env!("CARGO_PKG_VERSION"), + version: crate::version(), hostname: "test-host".to_string(), sni: "numa.numa".to_string(), dot_enabled: false, @@ -99,7 +99,7 @@ impl HealthMeta { } HealthMeta { - version: env!("CARGO_PKG_VERSION"), + version: crate::version(), hostname: crate::hostname(), sni: "numa.numa".to_string(), dot_enabled, diff --git a/src/lib.rs b/src/lib.rs index 8933e2a..a9d38fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,14 @@ pub(crate) mod testutil; pub type Error = Box; pub type Result = std::result::Result; +/// Build version string. On tagged releases: `0.13.1`. On commits ahead +/// of a tag: `0.13.1+a87f907`. With uncommitted changes: `0.13.1+a87f907-dirty`. +/// Falls back to `CARGO_PKG_VERSION` when built outside a git repo (e.g. +/// from a source tarball). +pub fn version() -> &'static str { + option_env!("NUMA_BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")) +} + /// Detect the machine hostname via the `hostname` command. Returns the /// full hostname (e.g., `macbook-pro.local`), or `"numa"` if the command /// fails. Call sites that need the short form (e.g., mDNS instance diff --git a/src/main.rs b/src/main.rs index bce7add..faf2e22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,7 +72,7 @@ async fn main() -> numa::Result<()> { }; } "version" | "--version" | "-V" => { - eprintln!("numa {}", env!("CARGO_PKG_VERSION")); + eprintln!("numa {}", numa::version()); return Ok(()); } "help" | "--help" | "-h" => { @@ -383,12 +383,10 @@ async fn main() -> numa::Result<()> { }; // Title row: center within the box - let title = format!( - "{b}NUMA{r} {it}DNS that governs itself{r} {d}v{}{r}", - env!("CARGO_PKG_VERSION") - ); + let ver = numa::version(); + let title = format!("{b}NUMA{r} {it}DNS that governs itself{r} {d}v{ver}{r}",); // The title contains ANSI codes; visible length is ~38 chars. Pad to fill the box. - let title_visible_len = 4 + 2 + 24 + 2 + 1 + env!("CARGO_PKG_VERSION").len() + 1; + let title_visible_len = 4 + 2 + 24 + 2 + 1 + ver.len() + 1; let title_pad = w.saturating_sub(title_visible_len); eprintln!("\n{o} ╔{bar_top}╗{r}"); eprint!("{o} ║{r} {title}");