feat: Add system diagnostics command
Adds `weevil doctor` to check development environment health. Reports status of Java, FTC SDK, Android SDK, ADB, and Gradle. Provides clear next steps based on system state.
This commit is contained in:
267
src/commands/doctor.rs
Normal file
267
src/commands/doctor.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
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<String>,
|
||||
pub ftc_sdk_ok: bool,
|
||||
pub ftc_sdk_version: Option<String>,
|
||||
pub android_sdk_ok: bool,
|
||||
pub adb_ok: bool,
|
||||
pub adb_version: Option<String>,
|
||||
pub gradle_ok: bool,
|
||||
pub gradle_version: Option<String>,
|
||||
}
|
||||
|
||||
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 <project-name>".bright_cyan());
|
||||
println!(" - Setup a cloned project: {}", "weevil setup <project-path>".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<SystemHealth> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
// 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<String> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ pub mod upgrade;
|
||||
pub mod deploy;
|
||||
pub mod sdk;
|
||||
pub mod config;
|
||||
pub mod setup;
|
||||
pub mod setup;
|
||||
pub mod doctor;
|
||||
@@ -33,6 +33,9 @@ enum Commands {
|
||||
android_sdk: Option<String>,
|
||||
},
|
||||
|
||||
/// Check system health and diagnose issues
|
||||
Doctor,
|
||||
|
||||
/// Setup development environment (system or project)
|
||||
Setup {
|
||||
/// Path to project directory (optional - without it, sets up system)
|
||||
@@ -105,6 +108,9 @@ fn main() -> Result<()> {
|
||||
Commands::New { name, ftc_sdk, android_sdk } => {
|
||||
commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref())
|
||||
}
|
||||
Commands::Doctor => {
|
||||
commands::doctor::run_diagnostics()
|
||||
}
|
||||
Commands::Setup { path } => {
|
||||
commands::setup::setup_environment(path.as_deref())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user