fbuild is designed to consume existing platformio.ini projects while using a
Rust-native build, deploy, and monitor pipeline. This page owns compatibility
notes that are too detailed for the root README.
By default, fbuild strips GCC's .eh_frame exception-unwinding tables on
release builds for platforms that do not use them: Teensy, STM32, RP2040,
NRF52, and ESP8266. This can save 40-180 KB of flash on a typical FastLED
sketch. PlatformIO does not strip these tables by default.
ESP32 with the stock Arduino sdkconfig preserves .eh_frame because
esp32_exception_decoder and panic-print-backtrace consume it.
The decision is made per build by this precedence chain:
| Condition | Policy |
|---|---|
FBUILD_STRIP_EH_FRAME=1 env var |
Strip |
FBUILD_KEEP_EH_FRAME=1 env var |
Preserve |
build_type = debug in platformio.ini |
Preserve |
-fexceptions, -funwind-tables, or -fasynchronous-unwind-tables in build_flags |
Preserve |
ESP32 with CONFIG_ESP_SYSTEM_PANIC_PRINT_BACKTRACE=y |
Preserve |
| Otherwise, on supported release platforms | Strip |
Add an unwind-tables flag to platformio.ini:
[env:teensy41]
platform = teensy
board = teensy41
framework = arduino
build_flags = -funwind-tablesOr set an environment variable:
FBUILD_KEEP_EH_FRAME=1 fbuild buildGCC emits .eh_frame by default even when nothing consumes it. On the
platforms above, toolchain JSON already ships -fno-exceptions, no runtime
calls _Unwind_*, and no debugger or decoder reads the tables. In that case
.eh_frame is dead metadata occupying flash.
A byte-level audit on an ESP32-S3 FastLED Blink build
(FastLED/FastLED#2473) found
.eh_frame accounted for 36% of firmware size. Stripping at the compiler level
with -fno-asynchronous-unwind-tables -fno-unwind-tables is the reliable fix.
Implementation details and the original decision matrix are in
#245.
fbuild ci is a drop-in replacement for pio ci for supported workflows. See
the fbuild ci reference for flag mapping and examples.
Arduino CLI documents sketch preprocessing as concatenating .ino and .pde
files into one generated .cpp: the file matching the sketch folder name comes
first, then the remaining files are appended alphabetically.
PlatformIO preprocesses top-level PROJECT_SRC_DIR/*.ino and *.pde files
through its InoToCPPConverter. In PlatformIO projects this commonly makes
src/main.ino the primary file; when there is no named primary, PlatformIO
detects a file containing setup() or loop() and treats that file as the main
input. The generated output name is based on the selected main .ino path.
fbuild follows that combined compatibility rule:
- Arduino-style sketch folders use
<sketch-folder>/<sketch-folder>.inoas the primary file when present. - PlatformIO-style
src/folders usesrc/main.inoas the primary file when present, then fall back to a file containingsetup()orloop(). - Additional
.inotabs are concatenated after the primary file in case-insensitive alphabetical order, with exact filename order as the tie breaker. - The generated file is named
<primary>.ino.cpp, matching the chosen primary.inostem.
References:
- Arduino CLI sketch build process: https://arduino.github.io/arduino-cli/1.5/sketch-build-process/#pre-processing
- PlatformIO
InoToCPPConverter: https://github.com/platformio/platformio-core/blob/develop/platformio/builder/tools/pioino.py
Arduino-style .ino preprocessing inserts forward declarations for free
functions so sketches can call functions before their definitions. fbuild uses
an embedded tree-sitter C++ parser for this step instead of regex matching.
This keeps prototype generation structural enough to avoid control-flow blocks,
lambdas, class methods, macro fragments, scoped Class::method definitions, and
namespace-local definitions. It also handles common C++ signature syntax such as
templates, attributes, references, and default arguments.
No clang or libclang runtime install is required for this preprocessing path.
fbuild writes generated .ino.cpp files with LF line endings and avoids
rewriting the file when the generated bytes are unchanged. This preserves the
file mtime for unchanged sketches so compiler caches do not see a fresh input
only because preprocessing ran again.
Generated #line paths are normalized before writing:
- Paths under the project root are emitted project-relative, for example
src/main.ino. - Path separators are emitted as
/on all hosts, including Windows. - Windows drive letters are lowercased when an absolute fallback path is needed.
When a project contains both main.cpp and one or more .ino files, fbuild
uses main.cpp as the sketch source and skips automatic .ino preprocessing.
This matches projects where main.cpp includes the .ino file manually and
avoids duplicate symbols.
Because that behavior can otherwise hide ignored .ino files, fbuild prints a
yellow warning before continuing the compile.