Projects Open Source

Forge — Embedded Project CLI

A Rust CLI that scaffolds embedded firmware projects — ESP-IDF, Arduino, RP2040 — with toolchain setup, host-side unit tests, CI, and flashing profiles out of the box.

Active since Nov 2025 #rust#cli#embedded#esp-idf
3
Supported targets
11
Built-in templates
40 s
Scaffold time
6 MB
Binary size

Forge exists because I counted how long it took me to start a new embedded project properly — toolchain, directory layout, CI, a way to test anything — and it was most of an afternoon. Every time. So most projects skipped the “properly” part, and I paid for it later when a regression in an ESP32 project took two evenings to bisect because nothing had tests. Forge compresses that afternoon into about forty seconds.

Template architecture

A forge new invocation picks a target (ESP-IDF, Arduino, or RP2040) and renders a template tree through Tera. Templates aren’t just file copies: each one carries a manifest declaring required toolchain versions, post-render hooks, and which files are target-specific versus shared. The shared layer is the interesting part — every generated project gets the same src/app/ core that is pure C++ with no vendor headers, plus a thin src/hal/ layer per target. That split is what makes host testing possible at all.

Host-side unit tests for firmware

The default template builds twice: once with the vendor toolchain for the board, once with the host compiler against mock HAL implementations. The generated CMake handles the split:

if(FORGE_HOST_BUILD)
  add_subdirectory(test/mocks)   # fake GPIO, I2C, timers
  add_executable(app_tests test/test_app.cpp src/app/logic.cpp)
  target_link_libraries(app_tests unity hal_mocks)
endif()

Business logic — state machines, parsers, sensor filtering — runs under Unity on the host in milliseconds, in CI, with no board attached. Hardware-touching code stays thin enough to verify by hand. This caught a debounce bug in my own doorbell project before it ever got flashed.

Why Rust

The first version was a shell script and it broke on Windows immediately. Rust gave me a single static binary for all three OSes, clap for a CLI that documents itself, and real error handling around the messiest part of the job: probing for toolchains. Cross-platform path handling alone justified the rewrite — half the bugs in the script era were backslash-related. The 6 MB binary embeds all eleven templates, so forge new works offline.

The lesson so far: the scaffold is the easy part. Keeping generated projects upgradeable after the template evolves is the hard problem, and it’s most of the current roadmap.

Development timeline

  1. 2025-11

    First prototype

    A shell script that copied a folder — enough to prove I kept redoing the same setup.

  2. 2025-12

    Rust rewrite

    Ported to Rust with clap and Tera templates, single static binary on all three OSes.

  3. 2026-02

    Host testing

    Landed the dual-target build so firmware logic runs under Unity on the host.

  4. 2026-05

    Public release

    Tagged v0.3, published install script, first outside contributor PR merged.