Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions crates/fbuild-build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions crates/fbuild-build/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
123 changes: 123 additions & 0 deletions crates/fbuild-build/src/script_runtime.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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(&[
Expand Down Expand Up @@ -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}");
}
}
Loading