diff --git a/crates/fbuild-build/README.md b/crates/fbuild-build/README.md index da737db6..7acc0e8d 100644 --- a/crates/fbuild-build/README.md +++ b/crates/fbuild-build/README.md @@ -13,6 +13,29 @@ Build orchestration, compilation, linking for all platforms (AVR, ESP32, RP2040, - **parallel** — Parallel compilation with job control - **source_scanner** — Source file discovery (sketch, core, variant) +## Native `extra_scripts` Boundary + +Native mode intentionally supports only a narrow subset of PlatformIO `extra_scripts`. +The goal is to preserve ordinary flag and path mutations without trying to emulate all of SCons. + +Supported in native mode: + +- `pre:` and `post:` script entries +- `Import("env")` in PRE/POST scripts +- `Import("projenv")` in POST scripts only +- `Append`, `AppendUnique`, `Prepend`, and `Replace` over `CPPDEFINES`, `CPPPATH`, `CCFLAGS`, `CFLAGS`, `CXXFLAGS`, `ASFLAGS`, `LINKFLAGS`, `LIBPATH`, and `LIBS` +- helper shims such as `Dump`, `BoardConfig`, `PioPlatform`, `Flatten`, `VerboseAction`, and `Execute` + +Rejected or out of scope: + +- unsupported script prefixes +- PRE scripts that request `projenv` +- mutations on unsupported SCons scopes +- unsupported `Import(...)` targets +- builders, middleware, upload/package hooks, and other arbitrary Python-driven build behavior + +Unsupported `extra_scripts` behavior fails the native build early with a recommendation to use `--platformio`. + ## Compile Database (compile_commands.json) After every build, fbuild generates a [JSON Compilation Database](https://clang.llvm.org/docs/JSONCompilationDatabase.html) so that clangd and VS Code IntelliSense can resolve includes to actual source files. diff --git a/crates/fbuild-build/src/README.md b/crates/fbuild-build/src/README.md index c9c500e2..09683fa6 100644 --- a/crates/fbuild-build/src/README.md +++ b/crates/fbuild-build/src/README.md @@ -14,6 +14,17 @@ Build orchestration for all supported embedded platforms. - **`build_output.rs`** -- Uniform build log formatting across all platforms - **`zccache.rs`** -- Optional zccache compiler cache wrapper integration +## Native `extra_scripts` Boundary + +The native replay path intentionally supports a narrow subset of PlatformIO script behavior: + +- Supported script prefixes: `pre:` and `post:` +- Supported imports: `Import("env")`; `Import("projenv")` only in post scripts +- Supported mutation scopes: `CPPDEFINES`, `CPPPATH`, `CCFLAGS`, `CFLAGS`, `CXXFLAGS`, `ASFLAGS`, `LINKFLAGS`, `LIBPATH`, `LIBS` +- Supported no-op helpers: `AddPreAction`, `AddPostAction`, `AlwaysBuild`, `Alias`, `Depends` + +Anything outside that matrix fails fast with a `use --platformio` recommendation so unsupported scripts do not silently produce a partial build. + ## Platform Subdirectories - **`avr/`** -- AVR ATmega (Arduino Uno, Mega, Nano) diff --git a/crates/fbuild-build/src/script_runtime.rs b/crates/fbuild-build/src/script_runtime.rs index 37187806..f609384a 100644 --- a/crates/fbuild-build/src/script_runtime.rs +++ b/crates/fbuild-build/src/script_runtime.rs @@ -1,3 +1,10 @@ +//! Native compatibility layer for a constrained subset of PlatformIO `extra_scripts`. +//! +//! Supported shapes are intentionally narrow: `pre:`/`post:` entries, `Import("env")` +//! in PRE/POST scripts, `Import("projenv")` in POST scripts only, and flag/path +//! mutations over the known compiler/linker scopes. Unsupported behavior fails fast +//! with a recommendation to use `--platformio`. + use std::collections::HashMap; use std::path::Path; use std::process::Command; @@ -340,6 +347,40 @@ mod tests { use crate::flag_overlay::ScriptScopeState; use std::fs; + fn write_runtime_project( + extra_scripts: &str, + script_name: &str, + script_body: &str, + ) -> tempfile::TempDir { + let temp = tempfile::tempdir().unwrap(); + let project_dir = temp.path(); + fs::write( + project_dir.join("platformio.ini"), + format!( + "\ +[env:demo] +platform = atmelavr +board = uno +framework = arduino +extra_scripts = {} +", + extra_scripts + ), + ) + .unwrap(); + fs::write(project_dir.join(script_name), script_body).unwrap(); + temp + } + + fn resolve_runtime_error(project_dir: &Path) -> String { + let config = + fbuild_config::PlatformIOConfig::from_path(&project_dir.join("platformio.ini")) + .unwrap(); + resolve_extra_script_overlay(project_dir, "demo", &config) + .unwrap_err() + .to_string() + } + #[test] fn test_cppdefines_to_flags_string_and_kv() { let flags = cppdefines_to_flags(&[ @@ -604,4 +645,86 @@ env.Append(CPPDEFINES=[\"PIO_PLATFORM_SHIM_OK\"]) .common .contains(&"-DPIO_PLATFORM_SHIM_OK".to_string())); } + + #[test] + fn test_resolve_extra_script_overlay_rejects_unsupported_import_name() { + if find_python().is_none() { + return; + } + + let temp = write_runtime_project( + "post:bad_import_test.py", + "bad_import_test.py", + "\ +Import(\"board\") +", + ); + let err = resolve_runtime_error(temp.path()); + assert!(err.contains("Import('board') is not supported"), "{err}"); + assert!(err.contains("Recommendation: use --platformio"), "{err}"); + } + + #[test] + fn test_resolve_extra_script_overlay_rejects_projenv_in_pre_script() { + if find_python().is_none() { + return; + } + + let temp = write_runtime_project( + "pre:pre_projenv_test.py", + "pre_projenv_test.py", + "\ +Import(\"env\", \"projenv\") +", + ); + let err = resolve_runtime_error(temp.path()); + assert!( + err.contains("projenv is not available in PRE extra_scripts"), + "{err}" + ); + assert!(err.contains("Recommendation: use --platformio"), "{err}"); + } + + #[test] + fn test_resolve_extra_script_overlay_rejects_unsupported_scope_mutation() { + if find_python().is_none() { + return; + } + + let temp = write_runtime_project( + "post:unsupported_scope_test.py", + "unsupported_scope_test.py", + "\ +Import(\"env\") +env.Append(FOO=[\"x\"]) +", + ); + let err = resolve_runtime_error(temp.path()); + assert!( + err.contains("env.append on unsupported scope 'FOO'"), + "{err}" + ); + assert!(err.contains("Recommendation: use --platformio"), "{err}"); + } + + #[test] + fn test_resolve_extra_script_overlay_rejects_unsupported_script_prefix() { + if find_python().is_none() { + return; + } + + let temp = write_runtime_project( + "mid:prefix_test.py", + "prefix_test.py", + "\ +Import(\"env\") +", + ); + let err = resolve_runtime_error(temp.path()); + assert!( + err.contains("unsupported extra_scripts prefix 'mid'"), + "{err}" + ); + assert!(err.contains("Recommendation: use --platformio"), "{err}"); + } }