Anvil v1.0.0 -- Arduino build tool with HAL and test scaffolding
Single-binary CLI that scaffolds testable Arduino projects, compiles, uploads, and monitors serial output. Templates embed a hardware abstraction layer, Google Mock infrastructure, and CMake-based host tests so application logic can be verified without hardware. Commands: new, doctor, setup, devices, build, upload, monitor 39 Rust tests (21 unit, 18 integration) Cross-platform: Linux and Windows
This commit is contained in:
237
src/commands/doctor.rs
Normal file
237
src/commands/doctor.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use anyhow::Result;
|
||||
use colored::*;
|
||||
|
||||
use crate::board;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SystemHealth {
|
||||
pub arduino_cli_ok: bool,
|
||||
pub arduino_cli_path: Option<String>,
|
||||
pub avr_core_ok: bool,
|
||||
pub avr_size_ok: bool,
|
||||
pub dialout_ok: bool,
|
||||
pub cmake_ok: bool,
|
||||
pub cpp_compiler_ok: bool,
|
||||
pub git_ok: bool,
|
||||
pub ports_found: usize,
|
||||
}
|
||||
|
||||
impl SystemHealth {
|
||||
pub fn is_healthy(&self) -> bool {
|
||||
self.arduino_cli_ok && self.avr_core_ok
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_diagnostics() -> Result<()> {
|
||||
println!(
|
||||
"{}",
|
||||
"Checking system health...".bright_yellow().bold()
|
||||
);
|
||||
println!();
|
||||
|
||||
let health = check_system_health();
|
||||
print_diagnostics(&health);
|
||||
|
||||
println!();
|
||||
if health.is_healthy() {
|
||||
println!(
|
||||
"{}",
|
||||
"System is ready for Arduino development."
|
||||
.bright_green()
|
||||
.bold()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
"Issues found. Run 'anvil setup' to fix."
|
||||
.bright_yellow()
|
||||
.bold()
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_system_health() -> SystemHealth {
|
||||
// arduino-cli
|
||||
let (arduino_cli_ok, arduino_cli_path) = match board::find_arduino_cli() {
|
||||
Some(path) => (true, Some(path.display().to_string())),
|
||||
None => (false, None),
|
||||
};
|
||||
|
||||
// AVR core
|
||||
let avr_core_ok = if let Some(ref path_str) = arduino_cli_path {
|
||||
let path = std::path::Path::new(path_str);
|
||||
board::is_avr_core_installed(path)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// avr-size (optional)
|
||||
let avr_size_ok = which::which("avr-size").is_ok();
|
||||
|
||||
// dialout group (Linux only)
|
||||
let dialout_ok = check_dialout();
|
||||
|
||||
// cmake (optional -- for host tests)
|
||||
let cmake_ok = which::which("cmake").is_ok();
|
||||
|
||||
// C++ compiler (optional -- for host tests)
|
||||
let cpp_compiler_ok = which::which("g++").is_ok() || which::which("clang++").is_ok();
|
||||
|
||||
// git
|
||||
let git_ok = which::which("git").is_ok();
|
||||
|
||||
// Serial ports
|
||||
let ports_found = board::list_ports().len();
|
||||
|
||||
SystemHealth {
|
||||
arduino_cli_ok,
|
||||
arduino_cli_path,
|
||||
avr_core_ok,
|
||||
avr_size_ok,
|
||||
dialout_ok,
|
||||
cmake_ok,
|
||||
cpp_compiler_ok,
|
||||
git_ok,
|
||||
ports_found,
|
||||
}
|
||||
}
|
||||
|
||||
fn print_diagnostics(health: &SystemHealth) {
|
||||
println!("{}", "Required:".bright_yellow().bold());
|
||||
println!();
|
||||
|
||||
// arduino-cli
|
||||
if health.arduino_cli_ok {
|
||||
println!(
|
||||
" {} arduino-cli {}",
|
||||
"ok".green(),
|
||||
health
|
||||
.arduino_cli_path
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new())
|
||||
.bright_black()
|
||||
);
|
||||
} else {
|
||||
println!(" {} arduino-cli {}", "MISSING".red(), "not found in PATH".red());
|
||||
}
|
||||
|
||||
// AVR core
|
||||
if health.avr_core_ok {
|
||||
println!(" {} arduino:avr core installed", "ok".green());
|
||||
} else if health.arduino_cli_ok {
|
||||
println!(
|
||||
" {} arduino:avr core {}",
|
||||
"MISSING".red(),
|
||||
"run: anvil setup".red()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
" {} arduino:avr core {}",
|
||||
"MISSING".red(),
|
||||
"(needs arduino-cli first)".bright_black()
|
||||
);
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Optional:".bright_yellow().bold());
|
||||
println!();
|
||||
|
||||
// avr-size
|
||||
if health.avr_size_ok {
|
||||
println!(" {} avr-size (binary size reporting)", "ok".green());
|
||||
} else {
|
||||
println!(
|
||||
" {} avr-size {}",
|
||||
"--".bright_black(),
|
||||
"install: sudo apt install gcc-avr".bright_black()
|
||||
);
|
||||
}
|
||||
|
||||
// dialout
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if health.dialout_ok {
|
||||
println!(" {} user in dialout group", "ok".green());
|
||||
} else {
|
||||
println!(
|
||||
" {} dialout group {}",
|
||||
"WARN".yellow(),
|
||||
"run: sudo usermod -aG dialout $USER".yellow()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// cmake
|
||||
if health.cmake_ok {
|
||||
println!(" {} cmake (for host-side tests)", "ok".green());
|
||||
} else {
|
||||
println!(
|
||||
" {} cmake {}",
|
||||
"--".bright_black(),
|
||||
"install: sudo apt install cmake".bright_black()
|
||||
);
|
||||
}
|
||||
|
||||
// C++ compiler
|
||||
if health.cpp_compiler_ok {
|
||||
println!(" {} C++ compiler (g++/clang++)", "ok".green());
|
||||
} else {
|
||||
println!(
|
||||
" {} C++ compiler {}",
|
||||
"--".bright_black(),
|
||||
"install: sudo apt install g++".bright_black()
|
||||
);
|
||||
}
|
||||
|
||||
// git
|
||||
if health.git_ok {
|
||||
println!(" {} git", "ok".green());
|
||||
} else {
|
||||
println!(
|
||||
" {} git {}",
|
||||
"--".bright_black(),
|
||||
"install: sudo apt install git".bright_black()
|
||||
);
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Hardware:".bright_yellow().bold());
|
||||
println!();
|
||||
|
||||
if health.ports_found > 0 {
|
||||
println!(
|
||||
" {} {} serial port(s) detected",
|
||||
"ok".green(),
|
||||
health.ports_found
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
" {} no serial ports {}",
|
||||
"--".bright_black(),
|
||||
"(plug in a board to detect)".bright_black()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_dialout() -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let output = std::process::Command::new("groups")
|
||||
.output();
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let groups = String::from_utf8_lossy(&out.stdout);
|
||||
groups.contains("dialout")
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
true // Not applicable on Windows
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user