From 48076e12b0b84ab3827563d9280999013f188bd2 Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Tue, 30 Dec 2025 08:26:50 -0700 Subject: [PATCH] Add comprehensive build identification to agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add git hash (short and full), branch, commit date - Add build timestamp and dirty/clean state - Add build profile (debug/release) and target triple - New `version-info` command shows all build details - `--version` now shows version-hash format (e.g., 0.1.0-4614df04) - Startup logs now include version hash and build info Example output: GuruConnect v0.1.0 Git: 4614df04 (clean) Branch: main Commit: 2025-12-30 06:30:28 -0700 Built: 2025-12-30 15:25:20 UTC Profile: release Target: x86_64-pc-windows-msvc 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- agent/Cargo.toml | 1 + agent/build.rs | 66 ++++++++++++++++++++++++++++++++++++++++ agent/src/main.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 141 insertions(+), 2 deletions(-) diff --git a/agent/Cargo.toml b/agent/Cargo.toml index ed8cbf3..84de228 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -99,6 +99,7 @@ windows-service = "0.7" [build-dependencies] prost-build = "0.13" winres = "0.1" +chrono = "0.4" [[bin]] name = "guruconnect" diff --git a/agent/build.rs b/agent/build.rs index f93ee9d..bc1f387 100644 --- a/agent/build.rs +++ b/agent/build.rs @@ -1,4 +1,5 @@ use std::io::Result; +use std::process::Command; fn main() -> Result<()> { // Compile protobuf definitions @@ -7,6 +8,71 @@ fn main() -> Result<()> { // Rerun if proto changes println!("cargo:rerun-if-changed=../proto/guruconnect.proto"); + // Rerun if git HEAD changes (new commits) + println!("cargo:rerun-if-changed=../.git/HEAD"); + println!("cargo:rerun-if-changed=../.git/index"); + + // Build timestamp (UTC) + let build_timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(); + println!("cargo:rustc-env=BUILD_TIMESTAMP={}", build_timestamp); + + // Git commit hash (short) + let git_hash = Command::new("git") + .args(["rev-parse", "--short=8", "HEAD"]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + println!("cargo:rustc-env=GIT_HASH={}", git_hash); + + // Git commit hash (full) + let git_hash_full = Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + println!("cargo:rustc-env=GIT_HASH_FULL={}", git_hash_full); + + // Git branch name + let git_branch = Command::new("git") + .args(["rev-parse", "--abbrev-ref", "HEAD"]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + println!("cargo:rustc-env=GIT_BRANCH={}", git_branch); + + // Git dirty state (uncommitted changes) + let git_dirty = Command::new("git") + .args(["status", "--porcelain"]) + .output() + .ok() + .map(|o| !o.stdout.is_empty()) + .unwrap_or(false); + println!("cargo:rustc-env=GIT_DIRTY={}", if git_dirty { "dirty" } else { "clean" }); + + // Git commit date + let git_commit_date = Command::new("git") + .args(["log", "-1", "--format=%ci"]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + println!("cargo:rustc-env=GIT_COMMIT_DATE={}", git_commit_date); + + // Build profile (debug/release) + let profile = std::env::var("PROFILE").unwrap_or_else(|_| "unknown".to_string()); + println!("cargo:rustc-env=BUILD_PROFILE={}", profile); + + // Target triple + let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown".to_string()); + println!("cargo:rustc-env=BUILD_TARGET={}", target); + // On Windows, embed the manifest for UAC elevation #[cfg(target_os = "windows")] { diff --git a/agent/src/main.rs b/agent/src/main.rs index d3e8110..92a7e75 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -29,6 +29,66 @@ pub mod proto { include!(concat!(env!("OUT_DIR"), "/guruconnect.rs")); } +/// Build information embedded at compile time +pub mod build_info { + /// Cargo package version (from Cargo.toml) + pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + + /// Git commit hash (short, 8 chars) + pub const GIT_HASH: &str = env!("GIT_HASH"); + + /// Git commit hash (full) + pub const GIT_HASH_FULL: &str = env!("GIT_HASH_FULL"); + + /// Git branch name + pub const GIT_BRANCH: &str = env!("GIT_BRANCH"); + + /// Git dirty state ("clean" or "dirty") + pub const GIT_DIRTY: &str = env!("GIT_DIRTY"); + + /// Git commit date + pub const GIT_COMMIT_DATE: &str = env!("GIT_COMMIT_DATE"); + + /// Build timestamp (UTC) + pub const BUILD_TIMESTAMP: &str = env!("BUILD_TIMESTAMP"); + + /// Build profile (debug/release) + pub const BUILD_PROFILE: &str = env!("BUILD_PROFILE"); + + /// Target triple (e.g., x86_64-pc-windows-msvc) + pub const BUILD_TARGET: &str = env!("BUILD_TARGET"); + + /// Short version string for display (version + git hash) + pub fn short_version() -> String { + if GIT_DIRTY == "dirty" { + format!("{}-{}-dirty", VERSION, GIT_HASH) + } else { + format!("{}-{}", VERSION, GIT_HASH) + } + } + + /// Full version string with all details + pub fn full_version() -> String { + format!( + "GuruConnect v{}\n\ + Git: {} ({})\n\ + Branch: {}\n\ + Commit: {}\n\ + Built: {}\n\ + Profile: {}\n\ + Target: {}", + VERSION, + GIT_HASH, + GIT_DIRTY, + GIT_BRANCH, + GIT_COMMIT_DATE, + BUILD_TIMESTAMP, + BUILD_PROFILE, + BUILD_TARGET + ) + } +} + use anyhow::Result; use clap::{Parser, Subcommand}; use tracing::{info, error, warn, Level}; @@ -46,7 +106,7 @@ use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW}; /// GuruConnect Remote Desktop #[derive(Parser)] #[command(name = "guruconnect")] -#[command(version, about = "Remote desktop agent and viewer")] +#[command(version = concat!(env!("CARGO_PKG_VERSION"), "-", env!("GIT_HASH")), about = "Remote desktop agent and viewer")] struct Cli { #[command(subcommand)] command: Option, @@ -102,6 +162,10 @@ enum Commands { /// The guruconnect:// URL to handle url: String, }, + + /// Show detailed version and build information + #[command(name = "version-info")] + VersionInfo, } fn main() -> Result<()> { @@ -115,7 +179,8 @@ fn main() -> Result<()> { .with_thread_ids(true) .init(); - info!("GuruConnect v{}", env!("CARGO_PKG_VERSION")); + info!("GuruConnect {} ({})", build_info::short_version(), build_info::BUILD_TARGET); + info!("Built: {} | Commit: {}", build_info::BUILD_TIMESTAMP, build_info::GIT_COMMIT_DATE); match cli.command { Some(Commands::Agent { code }) => { @@ -133,6 +198,13 @@ fn main() -> Result<()> { Some(Commands::Launch { url }) => { run_launch(&url) } + Some(Commands::VersionInfo) => { + // Show detailed version info (allocate console on Windows for visibility) + #[cfg(windows)] + show_debug_console(); + println!("{}", build_info::full_version()); + Ok(()) + } None => { // Legacy mode: if a support code was provided, run as agent if let Some(code) = cli.support_code {