use anyhow::Result; use std::path::Path; use std::process::Command; use colored::*; use crate::sdk::SdkConfig; #[derive(Debug)] pub struct SystemHealth { pub java_ok: bool, pub java_version: Option, pub ftc_sdk_ok: bool, pub ftc_sdk_version: Option, pub android_sdk_ok: bool, pub adb_ok: bool, pub adb_version: Option, pub gradle_ok: bool, pub gradle_version: Option, } impl SystemHealth { pub fn is_healthy(&self) -> bool { // Required: Java, FTC SDK, Android SDK // Optional: ADB in PATH (can be in Android SDK), Gradle (projects have wrapper) self.java_ok && self.ftc_sdk_ok && self.android_sdk_ok } } /// Run system diagnostics and report health status pub fn run_diagnostics() -> Result<()> { println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!("{}", " 🩺 Weevil Doctor - System Diagnostics".bright_cyan().bold()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!(); let health = check_system_health()?; print_diagnostics(&health); println!(); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); if health.is_healthy() { println!("{}", " ✓ System is healthy and ready for FTC development".bright_green().bold()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!(); println!("{}", "You can now:".bright_yellow().bold()); println!(" - Create a new project: {}", "weevil new ".bright_cyan()); println!(" - Setup a cloned project: {}", "weevil setup ".bright_cyan()); } else { println!("{}", " ⚠ Issues found - setup required".bright_yellow().bold()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!(); println!("{}", "To fix issues, run:".bright_yellow().bold()); println!(" {}", "weevil setup".bright_cyan()); } println!(); Ok(()) } /// Check system health and return a report pub fn check_system_health() -> Result { let sdk_config = SdkConfig::new()?; // Check Java let (java_ok, java_version) = match check_java() { Ok(version) => (true, Some(version)), Err(_) => (false, None), }; // Check FTC SDK let (ftc_sdk_ok, ftc_sdk_version) = if sdk_config.ftc_sdk_path.exists() { match crate::sdk::ftc::verify(&sdk_config.ftc_sdk_path) { Ok(_) => { let version = crate::sdk::ftc::get_version(&sdk_config.ftc_sdk_path) .unwrap_or_else(|_| "unknown".to_string()); (true, Some(version)) } Err(_) => (false, None), } } else { (false, None) }; // Check Android SDK let android_sdk_ok = if sdk_config.android_sdk_path.exists() { crate::sdk::android::verify(&sdk_config.android_sdk_path).is_ok() } else { false }; // Check ADB let (adb_ok, adb_version) = match check_adb(&sdk_config.android_sdk_path) { Ok(version) => (true, Some(version)), Err(_) => (false, None), }; // Check Gradle (optional) let (gradle_ok, gradle_version) = match check_gradle() { Ok(version) => (true, Some(version)), Err(_) => (false, None), }; Ok(SystemHealth { java_ok, java_version, ftc_sdk_ok, ftc_sdk_version, android_sdk_ok, adb_ok, adb_version, gradle_ok, gradle_version, }) } fn print_diagnostics(health: &SystemHealth) { let sdk_config = SdkConfig::new().unwrap(); println!("{}", "Required Components:".bright_yellow().bold()); println!(); // Java if health.java_ok { println!(" {} Java JDK {}", "✓".green(), health.java_version.as_ref().unwrap() ); } else { println!(" {} Java JDK {}", "✗".red(), "not found".red() ); } // FTC SDK if health.ftc_sdk_ok { println!(" {} FTC SDK {} at {}", "✓".green(), health.ftc_sdk_version.as_ref().unwrap(), sdk_config.ftc_sdk_path.display() ); } else { println!(" {} FTC SDK {} (expected at {})", "✗".red(), "not found".red(), sdk_config.ftc_sdk_path.display() ); } // Android SDK if health.android_sdk_ok { println!(" {} Android SDK at {}", "✓".green(), sdk_config.android_sdk_path.display() ); } else { println!(" {} Android SDK {} (expected at {})", "✗".red(), "not found".red(), sdk_config.android_sdk_path.display() ); } println!(); println!("{}", "Optional Components:".bright_yellow().bold()); println!(); // ADB if health.adb_ok { println!(" {} ADB {}", "✓".green(), health.adb_version.as_ref().unwrap() ); } else { println!(" {} ADB {}", "⚠".yellow(), "not in PATH (included in Android SDK)".yellow() ); } // Gradle if health.gradle_ok { println!(" {} Gradle {}", "✓".green(), health.gradle_version.as_ref().unwrap() ); } else { println!(" {} Gradle {}", "⚠".yellow(), "not in PATH (projects include wrapper)".yellow() ); } } fn check_java() -> Result { let output = Command::new("java") .arg("-version") .output(); match output { Ok(out) => { let stderr = String::from_utf8_lossy(&out.stderr); for line in stderr.lines() { if line.contains("version") { if let Some(version_str) = line.split('"').nth(1) { return Ok(version_str.to_string()); } } } Ok("installed (version unknown)".to_string()) } Err(_) => anyhow::bail!("Java JDK not found in PATH"), } } fn check_adb(android_sdk_path: &Path) -> Result { // First try system PATH let output = Command::new("adb") .arg("version") .output(); if let Ok(out) = output { if out.status.success() { let stdout = String::from_utf8_lossy(&out.stdout); for line in stdout.lines() { if line.starts_with("Android Debug Bridge version") { return Ok(line.replace("Android Debug Bridge version ", "")); } } return Ok("installed (version unknown)".to_string()); } } // Try Android SDK location let adb_path = if cfg!(target_os = "windows") { android_sdk_path.join("platform-tools").join("adb.exe") } else { android_sdk_path.join("platform-tools").join("adb") }; if adb_path.exists() { anyhow::bail!("ADB found in Android SDK but not in PATH") } else { anyhow::bail!("ADB not found") } } fn check_gradle() -> Result { let output = Command::new("gradle") .arg("--version") .output(); match output { Ok(out) => { let stdout = String::from_utf8_lossy(&out.stdout); for line in stdout.lines() { if line.starts_with("Gradle") { return Ok(line.replace("Gradle ", "")); } } Ok("installed (version unknown)".to_string()) } Err(_) => anyhow::bail!("Gradle not found in PATH"), } }