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:
Eric Ratliff
2026-02-15 11:16:17 -06:00
commit 3298844399
41 changed files with 4866 additions and 0 deletions

196
src/main.rs Normal file
View File

@@ -0,0 +1,196 @@
use clap::{Parser, Subcommand};
use colored::*;
use anyhow::Result;
use anvil::version::ANVIL_VERSION;
mod commands {
pub use anvil::commands::*;
}
#[derive(Parser)]
#[command(name = "anvil")]
#[command(author = "Eric Ratliff <eric@nxlearn.net>")]
#[command(version = ANVIL_VERSION)]
#[command(
about = "Arduino project generator and build tool - forges clean embedded projects",
long_about = None
)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Create a new Arduino project
New {
/// Project name
name: Option<String>,
/// Template to use (basic)
#[arg(long, short = 't', value_name = "TEMPLATE")]
template: Option<String>,
/// List available templates
#[arg(long, conflicts_with = "name")]
list_templates: bool,
},
/// Check system health and diagnose issues
Doctor,
/// Install arduino-cli and required cores
Setup,
/// List connected boards and serial ports
Devices,
/// Compile a sketch (and optionally upload)
Build {
/// Path to sketch directory
sketch: String,
/// Compile only -- do not upload
#[arg(long)]
verify: bool,
/// Open serial monitor after upload
#[arg(long)]
monitor: bool,
/// Delete cached build artifacts first
#[arg(long)]
clean: bool,
/// Show full compiler output
#[arg(long)]
verbose: bool,
/// Serial port (auto-detected if omitted)
#[arg(short, long)]
port: Option<String>,
/// Serial monitor baud rate
#[arg(short, long)]
baud: Option<u32>,
/// Override Fully Qualified Board Name
#[arg(long)]
fqbn: Option<String>,
},
/// Upload cached build artifacts (no recompile)
Upload {
/// Path to sketch directory
sketch: String,
/// Serial port (auto-detected if omitted)
#[arg(short, long)]
port: Option<String>,
/// Show full avrdude output
#[arg(long)]
verbose: bool,
/// Override Fully Qualified Board Name
#[arg(long)]
fqbn: Option<String>,
},
/// Open serial monitor
Monitor {
/// Serial port (auto-detected if omitted)
#[arg(short, long)]
port: Option<String>,
/// Baud rate (default: from project config or 115200)
#[arg(short, long)]
baud: Option<u32>,
/// Persistent mode: reconnect after upload/reset/replug
#[arg(long)]
watch: bool,
},
}
fn main() -> Result<()> {
#[cfg(windows)]
colored::control::set_virtual_terminal(true).ok();
let cli = Cli::parse();
print_banner();
match cli.command {
Commands::New { name, template, list_templates } => {
if list_templates {
commands::new::list_templates()
} else if let Some(project_name) = name {
commands::new::create_project(
&project_name,
template.as_deref(),
)
} else {
anyhow::bail!(
"Project name required.\n\
Usage: anvil new <name>\n\
List templates: anvil new --list-templates"
);
}
}
Commands::Doctor => {
commands::doctor::run_diagnostics()
}
Commands::Setup => {
commands::setup::run_setup()
}
Commands::Devices => {
commands::devices::scan_devices()
}
Commands::Build {
sketch, verify, monitor, clean, verbose,
port, baud, fqbn,
} => {
commands::build::run_build(
&sketch, verify, monitor, clean, verbose,
port.as_deref(), baud, fqbn.as_deref(),
)
}
Commands::Upload { sketch, port, verbose, fqbn } => {
commands::build::run_upload_only(
&sketch,
port.as_deref(),
verbose,
fqbn.as_deref(),
)
}
Commands::Monitor { port, baud, watch } => {
commands::monitor::run_monitor(
port.as_deref(),
baud,
watch,
)
}
}
}
fn print_banner() {
println!(
"{}",
"================================================================"
.bright_cyan()
);
println!(
"{}",
format!(" Anvil - Arduino Build Tool v{}", ANVIL_VERSION)
.bright_cyan()
.bold()
);
println!("{}", " Nexus Workshops LLC".bright_cyan());
println!(
"{}",
"================================================================"
.bright_cyan()
);
println!();
}