Add comprehensive build identification to agent

- 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 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 08:26:50 -07:00
parent 4614df04fb
commit 48076e12b0
3 changed files with 141 additions and 2 deletions

View File

@@ -99,6 +99,7 @@ windows-service = "0.7"
[build-dependencies] [build-dependencies]
prost-build = "0.13" prost-build = "0.13"
winres = "0.1" winres = "0.1"
chrono = "0.4"
[[bin]] [[bin]]
name = "guruconnect" name = "guruconnect"

View File

@@ -1,4 +1,5 @@
use std::io::Result; use std::io::Result;
use std::process::Command;
fn main() -> Result<()> { fn main() -> Result<()> {
// Compile protobuf definitions // Compile protobuf definitions
@@ -7,6 +8,71 @@ fn main() -> Result<()> {
// Rerun if proto changes // Rerun if proto changes
println!("cargo:rerun-if-changed=../proto/guruconnect.proto"); 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 // On Windows, embed the manifest for UAC elevation
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {

View File

@@ -29,6 +29,66 @@ pub mod proto {
include!(concat!(env!("OUT_DIR"), "/guruconnect.rs")); 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 anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use tracing::{info, error, warn, Level}; use tracing::{info, error, warn, Level};
@@ -46,7 +106,7 @@ use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW};
/// GuruConnect Remote Desktop /// GuruConnect Remote Desktop
#[derive(Parser)] #[derive(Parser)]
#[command(name = "guruconnect")] #[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 { struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Option<Commands>, command: Option<Commands>,
@@ -102,6 +162,10 @@ enum Commands {
/// The guruconnect:// URL to handle /// The guruconnect:// URL to handle
url: String, url: String,
}, },
/// Show detailed version and build information
#[command(name = "version-info")]
VersionInfo,
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@@ -115,7 +179,8 @@ fn main() -> Result<()> {
.with_thread_ids(true) .with_thread_ids(true)
.init(); .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 { match cli.command {
Some(Commands::Agent { code }) => { Some(Commands::Agent { code }) => {
@@ -133,6 +198,13 @@ fn main() -> Result<()> {
Some(Commands::Launch { url }) => { Some(Commands::Launch { url }) => {
run_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 => { None => {
// Legacy mode: if a support code was provided, run as agent // Legacy mode: if a support code was provided, run as agent
if let Some(code) = cli.support_code { if let Some(code) = cli.support_code {