docs/agents/build-system.md
ALWAYS use bash wrapper scripts for all build operations. DO NOT run low-level tools directly.
# ✅ CORRECT - Use bash wrapper scripts
bash test # Run all tests
bash test <test_name> # Run specific test
bash compile <platform> # Compile for platforms
bash lint # Run linting
bash validate --parlio # Hardware validation
# ⚠️ AVOID - Only when bash scripts don't provide needed functionality
uv run test.py <test_name> # Direct Python script
uv run ci/ci-compile.py <platform> # Direct Python script
# ❌ FORBIDDEN - Never use these
uv run python test.py # WRONG - never "uv run python"
meson setup builddir # WRONG - use bash scripts
ninja -C builddir # WRONG - use bash scripts
clang++ main.cpp -o main # WRONG - use bash scripts
CXX=... meson setup builddir # WRONG - use bash scripts
ONLY use low-level build commands in these specific scenarios:
Runtime Debugging - When the build is already complete:
# ✅ ALLOWED - debugging already-built executables
uv run clang-tool-chain-lldb .build/meson-debug/tests/runner.exe
gdb .build/meson-debug/examples/example-Blink.exe
Compiler Feature Testing - Manual builds to test specific compiler features:
# ✅ ALLOWED - testing specific compiler behavior
clang++ -std=c++17 test_feature.cpp -o test && ./test
Build System Development - When explicitly modifying the build system itself:
# ✅ ALLOWED - only when working on build system internals
meson introspect builddir --targets
ninja -C builddir -t commands
If you see example commands like CXX=clang-tool-chain-cpp meson setup builddir in documentation, these are for REFERENCE ONLY showing what the build system does internally - do NOT execute them directly.
DO NOT manually delete build directories. The build system is self-healing and will revalidate on its own.
# ❌ WRONG - Never manually delete build caches
rm -rf .build/meson-quick
rm -rf .build/meson-debug
rm -rf .build/meson-release
rm -rf .build/pio
rm -rf .fbuild
# ❌ WRONG - Never combine cache deletion with commands
rm -rf .build/meson-quick && uv run test.py tests/fl/stl/move
rm -rf .build && bash test
# ✅ CORRECT - Use clean flag to rebuild from scratch
bash test --clean # Clean and rebuild all tests
bash test --clean tests/fl/stl/move # Clean and rebuild specific test
bash compile --clean esp32s3 # Clean and rebuild platform
# ✅ CORRECT - Just run the command (build system self-heals)
bash test tests/fl/stl/move # Build system revalidates automatically
--clean flag selectively cleans only what's needed, preserving zccache--clean ensures proper cleanup order and dependency trackingThe build system is designed to be robust. Trust it to self-heal. Only use --clean if you specifically need a guaranteed clean rebuild.
DO NOT use --no-fingerprint. Fingerprint caching is a critical performance optimization.
# ❌ WRONG - Never disable fingerprint caching
uv run test.py --no-fingerprint
bash test --no-fingerprint
uv run test.py tests/fl/async --no-fingerprint
# ❌ WRONG - This makes builds extremely slow
uv run test.py --no-fingerprint --cpp
# ✅ CORRECT - Let fingerprint caching work
bash test
bash test tests/fl/async
bash test --cpp
# ✅ CORRECT - Use --clean if you suspect cache issues
bash test --clean # Clean rebuilds, but keeps fingerprint cache
bash test --clean tests/fl/async # Selective clean rebuild
--clean will fix it while preserving cacheThe fingerprint cache is NOT the problem. If you suspect cache issues, use --clean instead, which rebuilds but keeps the fingerprint system working.
LAST RESORT ONLY: If you have concrete evidence that fingerprint caching itself is broken (not just a stale build), then --no-fingerprint may be used for debugging. This should be extremely rare (< 0.01% of builds).
Tier 1: Bash Scripts (ALWAYS USE) - Primary interface:
bash test, bash compile, bash lint, bash validateTier 2: Direct Python (AVOID) - Only when bash doesn't provide functionality:
uv run test.py (use bash test instead when possible)uv run ci/ci-compile.py (use bash compile instead when possible)Tier 3: Low-Level Tools (FORBIDDEN) - Never for standard builds:
uv run python test.py (NEVER - always wrong)meson, ninja, clang++, gcc (only for exceptions above)The project uses clang-tool-chain for all builds. The build system internally configures meson with:
# REFERENCE ONLY - DO NOT RUN THIS DIRECTLY
# The build system (test.py/compile) handles this automatically
CXX=clang-tool-chain-cpp CC=clang-tool-chain-c meson setup builddir
clang-tool-chain provides a uniform GNU-style build environment across all platforms:
-fuse-ld=lld, -pthread, -static, -rdynamic work identically on Windows and Linuxlibunwind are available on Windows via DLL injection - no platform-specific code neededmeson.build defines shared runner_link_args, runner_cpp_args, and dll_link_args used by both tests and examplesdbghelp/psapi (Windows debug libs) and macOS static linking restrictions require platform checksif is_windows conditionals for most casesZCCACHE_DISABLE=1 or disable zccache in any way
zccache --stop-server to reset)ZCCACHE_DISABLE=1 as a workaround for zccache errorsCritical Information for Working with meson.build Files:
NO Embedded Python Scripts - Meson supports inline Python via run_command('python', '-c', '...'), but this creates unmaintainable code
run_command('python', '-c', 'import pathlib; print(...)').py file in ci/ or tests/, then call via run_command('python', 'script.py')Avoid Dictionary Subscript Assignment in Loops - Meson does NOT support dict[key] += value syntax
category_files[name] += files(...)existing = category_files.get(name); updated = existing + files(...); category_files += {name: updated}Configuration as Data - Hardcoded values should live in Python config files, not meson.build
*_config.py, import in Python scripts, call from Mesontests/test_config.py defines test categories and patternsExtract Complex Logic to Python - Meson is a declarative build system, not a programming language
run_command() to call Python helpers that output Meson-parseable datatests/organize_tests.py outputs TEST:name:path:category formatNO Global Error Suppression - Meson/build errors MUST NOT be globally suppressed
if "error:" in line: continue)ci/meson/compile.py stores "Can't invoke target" errors during quiet fallback, shows them only when ALL targets failCurrent Architecture (after refactoring):
meson.build (root): Source discovery + library compilation (still has duplication - needs refactoring)tests/meson.build: Uses organize_tests.py for test discovery (refactored)examples/meson.build: PlatformIO target registration (clean)