From 3db9924444882ce0eb5c053a33a253f4efc8d23a Mon Sep 17 00:00:00 2001 From: xdustinface Date: Fri, 15 May 2026 21:35:27 +1000 Subject: [PATCH 1/2] feat(dash-spv): log version with git commit for dev builds Add a `build.rs` that captures the git commit, a dirty flag, and whether `HEAD` is at a `v*` release tag, exposed through a `version_info()` helper next to `VERSION`. `DashSpvClient::new()` logs it once on creation. Tagged clean builds render just `dash-spv `, while development builds include the commit (with a `-dirty` marker for uncommitted changes) so they are recognizable as non-release. Falls back to the bare version when git is unavailable, such as packaged builds. --- dash-spv/build.rs | 33 +++++++++++++++++ dash-spv/src/client/lifecycle.rs | 2 ++ dash-spv/src/lib.rs | 61 ++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 dash-spv/build.rs diff --git a/dash-spv/build.rs b/dash-spv/build.rs new file mode 100644 index 000000000..2d2fe2be0 --- /dev/null +++ b/dash-spv/build.rs @@ -0,0 +1,33 @@ +use std::process::Command; + +fn git(args: &[&str]) -> Option { + let output = Command::new("git").args(args).output().ok()?; + if !output.status.success() { + return None; + } + let text = String::from_utf8(output.stdout).ok()?.trim().to_string(); + if text.is_empty() { + None + } else { + Some(text) + } +} + +fn main() { + let hash = git(&["rev-parse", "--short=12", "HEAD"]).unwrap_or_default(); + let dirty = git(&["status", "--porcelain", "--untracked-files=no"]) + .map(|s| !s.is_empty()) + .unwrap_or(false); + let tagged = git(&["describe", "--exact-match", "--tags", "--match", "v*", "HEAD"]).is_some(); + + println!("cargo:rustc-env=DASH_SPV_GIT_HASH={hash}"); + println!("cargo:rustc-env=DASH_SPV_GIT_DIRTY={dirty}"); + println!("cargo:rustc-env=DASH_SPV_GIT_TAGGED={tagged}"); + + println!("cargo:rerun-if-changed=build.rs"); + for path in ["HEAD", "index", "packed-refs"] { + if let Some(p) = git(&["rev-parse", "--git-path", path]) { + println!("cargo:rerun-if-changed={p}"); + } + } +} diff --git a/dash-spv/src/client/lifecycle.rs b/dash-spv/src/client/lifecycle.rs index 1e4125dc6..763ce5029 100644 --- a/dash-spv/src/client/lifecycle.rs +++ b/dash-spv/src/client/lifecycle.rs @@ -36,6 +36,8 @@ impl DashSpvClient>, event_handlers: Vec>, ) -> Result { + tracing::info!("{}", crate::version_info()); + // Validate configuration config.validate().map_err(SpvError::Config)?; diff --git a/dash-spv/src/lib.rs b/dash-spv/src/lib.rs index cabedf76a..30062fbdf 100644 --- a/dash-spv/src/lib.rs +++ b/dash-spv/src/lib.rs @@ -101,3 +101,64 @@ pub use dashcore::sml::llmq_type::LLMQType; /// Current version of the dash-spv library. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Short git commit the library was built from, empty if it could not be determined. +pub const GIT_HASH: &str = env!("DASH_SPV_GIT_HASH"); + +/// Whether the working tree had uncommitted tracked changes at build time. +pub const GIT_DIRTY: bool = const_str_eq(env!("DASH_SPV_GIT_DIRTY"), "true"); + +/// Whether the build was made from a commit pointed at by a `v*` release tag. +pub const GIT_TAGGED: bool = const_str_eq(env!("DASH_SPV_GIT_TAGGED"), "true"); + +const fn const_str_eq(a: &str, b: &str) -> bool { + let (a, b) = (a.as_bytes(), b.as_bytes()); + if a.len() != b.len() { + return false; + } + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + i += 1; + } + true +} + +/// Human readable version. +/// +/// A release build (the commit is pointed at by a `v*` tag and the tree is clean) +/// renders just `dash-spv 0.42.0`. Any development build surfaces the commit so it +/// is recognizable as non-release: `dash-spv 0.42.0 (a1b2c3d4e5f6)`, or +/// `dash-spv 0.42.0 (a1b2c3d4e5f6-dirty)` with uncommitted changes. Builds with no +/// git context (e.g. a packaged source tarball) render just `dash-spv 0.42.0`. +pub fn version_info() -> String { + if GIT_HASH.is_empty() || (GIT_TAGGED && !GIT_DIRTY) { + format!("dash-spv {VERSION}") + } else if GIT_DIRTY { + format!("dash-spv {VERSION} ({GIT_HASH}-dirty)") + } else { + format!("dash-spv {VERSION} ({GIT_HASH})") + } +} + +#[cfg(test)] +mod tests { + use super::{version_info, GIT_DIRTY, GIT_HASH, GIT_TAGGED, VERSION}; + + #[test] + fn version_info_format() { + let info = version_info(); + assert!(info.starts_with("dash-spv ")); + assert!(info.contains(VERSION)); + + let is_release = GIT_HASH.is_empty() || (GIT_TAGGED && !GIT_DIRTY); + if is_release { + assert_eq!(info, format!("dash-spv {VERSION}")); + } else { + assert!(info.contains(GIT_HASH)); + assert_eq!(info.ends_with("-dirty)"), GIT_DIRTY); + } + } +} From a142e653be4898bf889c0085e8365b44a4a582f3 Mon Sep 17 00:00:00 2001 From: xdustinface Date: Sat, 16 May 2026 01:25:07 +1000 Subject: [PATCH 2/2] fix(dash-spv): watch active branch ref in `build.rs` rerun-if-changed `.git/HEAD` only changes when switching branches, not when committing on the current one, so watching `HEAD`/`index`/`packed-refs` could leave `DASH_SPV_GIT_HASH` stale across metadata-driven rebuilds. Also watch the symbolic HEAD target's ref file. Addresses CodeRabbit review comment on PR #770 https://github.com/dashpay/rust-dashcore/pull/770#discussion_r3249185397 --- dash-spv/build.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dash-spv/build.rs b/dash-spv/build.rs index 2d2fe2be0..3034f61e1 100644 --- a/dash-spv/build.rs +++ b/dash-spv/build.rs @@ -25,6 +25,13 @@ fn main() { println!("cargo:rustc-env=DASH_SPV_GIT_TAGGED={tagged}"); println!("cargo:rerun-if-changed=build.rs"); + // `.git/HEAD` only changes when switching branches, not when committing on + // the current one, so also watch the symbolic target's ref file. + if let Some(head_ref) = git(&["symbolic-ref", "--quiet", "HEAD"]) { + if let Some(p) = git(&["rev-parse", "--git-path", &head_ref]) { + println!("cargo:rerun-if-changed={p}"); + } + } for path in ["HEAD", "index", "packed-refs"] { if let Some(p) = git(&["rev-parse", "--git-path", path]) { println!("cargo:rerun-if-changed={p}");