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 deploy;
|
||||||
pub mod sdk;
|
pub mod sdk;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
pub mod doctor;
|
||||||
@@ -33,6 +33,9 @@ enum Commands {
|
|||||||
android_sdk: Option<String>,
|
android_sdk: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Check system health and diagnose issues
|
||||||
|
Doctor,
|
||||||
|
|
||||||
/// Setup development environment (system or project)
|
/// Setup development environment (system or project)
|
||||||
Setup {
|
Setup {
|
||||||
/// Path to project directory (optional - without it, sets up system)
|
/// 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 { name, ftc_sdk, android_sdk } => {
|
||||||
commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref())
|
commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref())
|
||||||
}
|
}
|
||||||
|
Commands::Doctor => {
|
||||||
|
commands::doctor::run_diagnostics()
|
||||||
|
}
|
||||||
Commands::Setup { path } => {
|
Commands::Setup { path } => {
|
||||||
commands::setup::setup_environment(path.as_deref())
|
commands::setup::setup_environment(path.as_deref())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user