use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-changed=.git/index");
println!("cargo:rerun-if-changed=COPILOT_VERSION");
println!("cargo:rerun-if-changed=container/Containerfile");
println!("cargo:rerun-if-changed=container/entrypoint.sh");
println!("cargo:rerun-if-changed=container/git-wrapper.sh");
let timestamp = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
println!("cargo:rustc-env=BN_BUILD_TIMESTAMP={}", timestamp);
let commit = get_git_commit().unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=BN_GIT_COMMIT={}", commit);
let copilot_version = get_copilot_version().unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=BN_COPILOT_VERSION={}", copilot_version);
#[cfg(feature = "gui")]
bundle_web_assets();
}
#[cfg(feature = "gui")]
fn bundle_web_assets() {
use std::fs;
use std::path::Path;
println!("cargo:rerun-if-changed=web/");
println!("cargo:rerun-if-changed=scripts/bundle-web.sh");
let bundle_path = Path::new("target/web-bundle.tar.zst");
let hash_file = Path::new("target/web-bundle.hash");
let current_hash = hash_directory("web").unwrap_or_else(|e| {
eprintln!("Warning: Failed to hash web/ directory: {}", e);
0
});
let needs_rebuild = !bundle_path.exists() || !hash_file.exists() || {
let cached_hash = fs::read_to_string(hash_file)
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
.unwrap_or(0);
cached_hash != current_hash
};
if needs_rebuild {
eprintln!("Building web bundle...");
let status = Command::new("./scripts/bundle-web.sh")
.status()
.expect("Failed to run bundle-web.sh");
if !status.success() {
panic!("Web bundle script failed");
}
if let Err(e) = fs::write(hash_file, current_hash.to_string()) {
eprintln!("Warning: Failed to save bundle hash: {}", e);
}
} else {
eprintln!("Web bundle up to date (cached)");
}
println!("cargo:rustc-env=BN_WEB_BUNDLE={}", bundle_path.display());
}
#[cfg(feature = "gui")]
fn hash_directory(path: impl AsRef<std::path::Path>) -> std::io::Result<u64> {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
let mut hasher = DefaultHasher::new();
hash_directory_recursive(path.as_ref(), &mut hasher)?;
Ok(hasher.finish())
}
#[cfg(feature = "gui")]
fn hash_directory_recursive(
path: &std::path::Path,
hasher: &mut std::collections::hash_map::DefaultHasher,
) -> std::io::Result<()> {
use std::fs;
use std::hash::Hash;
if !path.exists() {
return Ok(());
}
if path.is_file() {
path.to_string_lossy().hash(hasher);
let metadata = fs::metadata(path)?;
metadata.len().hash(hasher);
if let Ok(modified) = metadata.modified()
&& let Ok(duration) = modified.duration_since(std::time::UNIX_EPOCH)
{
duration.as_secs().hash(hasher);
}
} else if path.is_dir() {
let mut entries: Vec<_> = fs::read_dir(path)?.collect::<Result<_, _>>()?;
entries.sort_by_key(|e| e.path());
for entry in entries {
let entry_path = entry.path();
if let Some(name) = entry_path.file_name().and_then(|n| n.to_str())
&& (name.ends_with(".test.js") || name.starts_with('.'))
{
continue;
}
hash_directory_recursive(&entry_path, hasher)?;
}
}
Ok(())
}
fn get_git_commit() -> Option<String> {
let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()?;
if output.status.success() {
let hash = String::from_utf8(output.stdout).ok()?;
Some(hash.trim().to_string())
} else {
None
}
}
fn get_copilot_version() -> Option<String> {
use std::fs;
let contents = fs::read_to_string("COPILOT_VERSION").ok()?;
for line in contents.lines() {
let trimmed = line.trim();
if trimmed.starts_with('v') && !trimmed.starts_with('#') {
return Some(trimmed.to_string());
}
}
None
}