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
26 changes: 19 additions & 7 deletions ci/validate_boards.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@
# Must match enrich_boards.rs exactly
BUILD_FIELDS = ("core", "variant", "extra_flags", "f_cpu", "f_flash", "f_image", "flash_mode", "mcu")
ARDUINO_FIELDS = ("ldscript", "partitions", "memory_type")

# Intentional fbuild-only extensions to the board schema that PlatformIO does
# not carry upstream. Stripped from the actual side before diffing so they
# aren't reported as drift. Keep this list short and document why each entry
# is here — every addition is a place where fbuild semantically extends PIO.
FBUILD_EXTENSION_BUILD_FIELDS = frozenset(
{
# Teensy 3.x/4.x CMSIS-DSP per-MCU library name (FastLED/fbuild#300).
# PlatformIO leaves this implicit and lets Teensyduino's makefile pick
# the matching `-l...` flag; fbuild needs it declared so the linker can
# auto-link the right `arm_cortexM*_math` archive.
"cmsis_dsp_lib",
}
)
UPLOAD_FIELDS = (
"protocol",
"speed",
Expand Down Expand Up @@ -208,6 +222,10 @@ def validate_board(board_path: Path, pio_dir: Path) -> list[str] | None:
if isinstance(pio_build, dict):
expected_build = extract_build(pio_build)
actual_build = board.get("build", {})
# Strip intentional fbuild-only extensions from the actual side so
# they aren't reported as drift (see FBUILD_EXTENSION_BUILD_FIELDS).
if isinstance(actual_build, dict) and any(k in actual_build for k in FBUILD_EXTENSION_BUILD_FIELDS):
actual_build = {k: v for k, v in actual_build.items() if k not in FBUILD_EXTENSION_BUILD_FIELDS}
if expected_build and expected_build != actual_build:
diffs.extend(diff_dicts(expected_build, actual_build, "build"))

Expand Down Expand Up @@ -268,13 +286,7 @@ def run_external_comparison(output_json: bool = False) -> int:
"external_board_count": total_external,
"missing_count": total_missing,
"errors": [{"source": r.source_id, "error": r.error} for r in errors],
"missing_by_source": {
src: [
{"name": b.name, "architecture": b.architecture, "vendor": b.vendor}
for b in boards
]
for src, boards in sorted(missing.items())
},
"missing_by_source": {src: [{"name": b.name, "architecture": b.architecture, "vendor": b.vendor} for b in boards] for src, boards in sorted(missing.items())},
}
print(json.dumps(out, indent=2))
else:
Expand Down
14 changes: 14 additions & 0 deletions crates/fbuild-build/src/sam/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,20 @@ fn install_samd_core(
];
// Also include the variant dir for variant.h
includes.push(variant_dir.clone());
// And the resolved core dir for Arduino.h / WVariant.h.
//
// `BoardConfig::get_include_paths` already emits
// `framework_root/cores/<board.core>`, which for Adafruit SAMD boards is
// a vendor-brand label (e.g. "adafruit") that the framework doesn't
// actually ship as a directory — only `cores/arduino/` exists. That
// literal path becomes a phantom `-I` and `#include "Arduino.h"` /
// `#include "WVariant.h"` lookups miss. `core_dir` here was produced by
// `SamdCores::get_core_dir` which falls back to `cores/arduino/` when the
// literal subdir is absent (FastLED/fbuild#319), so injecting it into the
// compile include path is what actually resolves the headers. Putting
// it BEFORE the phantom is a no-op but makes the active dir the first
// hit during search, matching what PlatformIO's atmelsam builder does.
includes.insert(0, core_dir.clone());

Ok((
framework_dir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "esp_sr_16.csv"
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ESP32_S3R8N16 -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s2_out.ld",
"memory_type": "qio_qspi",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S2 -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s2_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S2_REVTFT -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s2_out.ld",
"memory_type": "qio_qspi",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S2_TFT -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DBOARD_HAS_PSRAM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_qspi",
"partitions": "tinyuf2-partitions-8MB.csv"
"partitions": "partitions-8MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S3_NOPSRAM -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_qspi",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S3_REVTFT -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DBOARD_HAS_PSRAM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_qspi",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S3_TFT -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DBOARD_HAS_PSRAM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s2_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_FUNHOUSE_ESP32S2 -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s2_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_MAGTAG29_ESP32S2 -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_qspi",
"partitions": "tinyuf2-partitions-8MB.csv"
"partitions": "partitions-8MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DBOARD_HAS_PSRAM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s2_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_METRO_ESP32S2 -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "tinyuf2-partitions-16MB.csv"
"partitions": "partitions-16MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_METRO_ESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DBOARD_HAS_PSRAM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s2_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_QTPY_ESP32S2 -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_QTPY_ESP32S3_N4R2 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DBOARD_HAS_PSRAM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "tinyuf2-partitions-8MB.csv"
"partitions": "partitions-8MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_QTPY_ESP32S3_NOPSRAM -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "tinyuf2-partitions-16MB.csv"
"partitions": "partitions-16MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_QUALIA_S3_RGB666 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1 -DBOARD_HAS_PSRAM",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"ldscript": "esp32s2_out.ld",
"partitions": "tinyuf2-partitions-4MB.csv"
"partitions": "partitions-4MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ADAFRUIT_FEATHER_ESP32S2_NOPSRAM -DARDUINO_USB_CDC_ON_BOOT=1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"build": {
"arduino": {
"memory_type": "qio_qspi",
"partitions": "tinyuf2-partitions-16MB.csv"
"partitions": "partitions-16MB-tinyuf2.csv"
},
"core": "esp32",
"extra_flags": "-DARDUINO_WT32_SC01_PLUS -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_RUNNING_CORE=1 -DARDUINO_EVENT_RUNNING_CORE=1",
Expand Down
72 changes: 71 additions & 1 deletion crates/fbuild-packages/src/library/samd_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,17 @@ impl SamdCores {
}

/// Get the core source directory for a specific core name.
///
/// PlatformIO's `build.core` field is a vendor-branding label more than
/// a real subdirectory name — Adafruit's ArduinoCore-samd (the SAMD
/// framework we install) ships only `cores/arduino/`, but every Adafruit
/// SAMD board declares `build.core = "adafruit"` so the literal
/// `cores/adafruit/` lookup misses. Mirror PlatformIO's atmelsam builder
/// fallback: if `cores/<core_name>/` doesn't exist on disk, fall back to
/// `cores/arduino/` (the canonical name for Arduino-compatible cores).
/// See FastLED/fbuild#319.
pub fn get_core_dir(&self, core_name: &str) -> PathBuf {
self.get_cores_dir().join(core_name)
resolve_core_dir_with_arduino_fallback(&self.get_cores_dir(), core_name)
}

/// Get the variant directory for a specific variant name.
Expand Down Expand Up @@ -194,6 +203,25 @@ fn collect_sources(dir: &Path) -> Vec<PathBuf> {
sources
}

/// Resolve a core source dir under `cores_dir`, falling back to
/// `cores/arduino/` when the literal `<core_name>` subdirectory is missing.
///
/// Adafruit (and some other vendor) Arduino-compatible cores set
/// `build.core = "<vendor>"` in their board JSON for branding even though
/// the actual sources live in `cores/arduino/`. PlatformIO's atmelsam
/// builder handles this transparently; fbuild needs to do the same.
fn resolve_core_dir_with_arduino_fallback(cores_dir: &Path, core_name: &str) -> PathBuf {
let primary = cores_dir.join(core_name);
if primary.is_dir() {
return primary;
}
let fallback = cores_dir.join("arduino");
if fallback.is_dir() {
return fallback;
}
primary
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -249,4 +277,46 @@ mod tests {
let result = SamdCores::validate(tmp.path());
assert!(result.is_err());
}

/// FastLED/fbuild#319: Adafruit SAMD boards declare `build.core = "adafruit"`
/// (a vendor brand label) but the framework only ships `cores/arduino/`.
/// The resolver must fall back to `cores/arduino/` when the literal
/// `cores/<core_name>/` doesn't exist on disk, mirroring PIO's atmelsam
/// builder behavior.
#[test]
fn resolve_falls_back_to_arduino_when_named_dir_missing() {
let tmp = tempfile::TempDir::new().unwrap();
let cores_dir = tmp.path().join("cores");
std::fs::create_dir_all(cores_dir.join("arduino")).unwrap();
// No `cores/adafruit/` exists.

let resolved = resolve_core_dir_with_arduino_fallback(&cores_dir, "adafruit");
assert_eq!(resolved, cores_dir.join("arduino"));
}

/// Sanity: when the named core dir *does* exist, the fallback must not
/// kick in — honor the literal name.
#[test]
fn resolve_uses_literal_name_when_present() {
let tmp = tempfile::TempDir::new().unwrap();
let cores_dir = tmp.path().join("cores");
std::fs::create_dir_all(cores_dir.join("custom_vendor")).unwrap();
std::fs::create_dir_all(cores_dir.join("arduino")).unwrap();

let resolved = resolve_core_dir_with_arduino_fallback(&cores_dir, "custom_vendor");
assert_eq!(resolved, cores_dir.join("custom_vendor"));
}

/// When neither the named dir nor `cores/arduino/` exists, return the
/// literal `cores/<name>/` so the eventual file-open error surfaces with
/// a meaningful path (don't synthesize a nonexistent fallback).
#[test]
fn resolve_returns_literal_when_no_fallback_available() {
let tmp = tempfile::TempDir::new().unwrap();
let cores_dir = tmp.path().join("cores");
// No cores subdirs at all.

let resolved = resolve_core_dir_with_arduino_fallback(&cores_dir, "vendor");
assert_eq!(resolved, cores_dir.join("vendor"));
}
}
4 changes: 4 additions & 0 deletions crates/fbuild-python/src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ impl Daemon {
// (FastLED/fbuild#275) so a venv install never gets shadowed by a
// stale user-level daemon on PATH.
let mut cmd = match daemon_spawn_target() {
// allow-direct-spawn: daemon must outlive the Python interpreter.
Some(path) => std::process::Command::new(path),
// allow-direct-spawn: daemon must outlive the Python interpreter.
None => std::process::Command::new(DAEMON_BIN_NAME),
};
if fbuild_paths::is_dev_mode() {
Expand Down Expand Up @@ -244,7 +246,9 @@ impl AsyncDaemon {
// from inside this async block.
// allow-direct-spawn: daemon must outlive the Python interpreter.
let mut cmd = match spawn_target {
// allow-direct-spawn: daemon must outlive the Python interpreter.
Some(path) => std::process::Command::new(path),
// allow-direct-spawn: daemon must outlive the Python interpreter.
None => std::process::Command::new(DAEMON_BIN_NAME),
};
if dev_mode {
Expand Down
Loading