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
73 changes: 65 additions & 8 deletions crates/fbuild-build-esp/src/esp32/orchestrator/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,63 @@ use std::path::{Path, PathBuf};

use fbuild_core::Result;

/// Record the requested ESP32 package pins beside the installed stack so a
/// successful build cannot conceal a cross-version framework or SDK.
/// How fbuild integrates ESP-IDF for an environment.
///
/// fbuild has no CMake/`idf.py` component build: every ESP32 environment is
/// compiled with the Arduino core. Recording the mode beside the requested and
/// resolved frameworks keeps a build log from claiming IDF component coverage
/// it never produced (FastLED/fbuild#1664).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Esp32Integration {
/// `framework = arduino`: the Arduino core only.
ArduinoCore,
/// `framework = arduino, espidf`: the Arduino core links the precompiled
/// ESP-IDF SDK. Not an ESP-IDF component build.
ArduinoOnIdf,
}

impl Esp32Integration {
pub(super) fn label(self) -> &'static str {
match self {
Self::ArduinoCore => "arduino-core",
Self::ArduinoOnIdf => "arduino-on-idf",
}
}
}

/// The frameworks an environment's `framework =` value requests.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(super) struct RequestedFrameworks {
pub arduino: bool,
pub espidf: bool,
}

impl RequestedFrameworks {
/// `None` when the request is a bare `espidf` build fbuild cannot perform,
/// otherwise the integration mode the build will actually use.
pub(super) fn integration(self) -> Option<Esp32Integration> {
match (self.arduino, self.espidf) {
(true, true) => Some(Esp32Integration::ArduinoOnIdf),
(false, true) => None,
_ => Some(Esp32Integration::ArduinoCore),
}
}
}

/// Parse a PlatformIO `framework =` value into the requested frameworks.
pub(super) fn requested_frameworks(framework: &str) -> RequestedFrameworks {
let mut requested = RequestedFrameworks::default();
for part in framework.split(',').map(str::trim) {
requested.arduino |= part.eq_ignore_ascii_case("arduino");
requested.espidf |= part.eq_ignore_ascii_case("espidf");
}
requested
}

/// Record the requested framework, the requested package pins and the actual
/// integration mode beside the installed stack so a successful build cannot
/// conceal a cross-version framework, an SDK mismatch or an integration mode
/// other than the one requested (FastLED/fbuild#1664).
pub(super) fn esp32_package_stack_summary(
env_config: Option<&HashMap<String, String>>,
platform: &fbuild_packages::PackageInfo,
Expand All @@ -27,6 +82,9 @@ pub(super) fn esp32_package_stack_summary(
.and_then(|env| env.get("platform"))
.map_or("<default>", String::as_str);
let requested_framework = env_config
.and_then(|env| env.get("framework"))
.map_or("<board default>", String::as_str);
let framework_pin = env_config
.and_then(|env| env.get("platform_packages"))
.and_then(|raw| {
raw.lines().find(|line| {
Expand All @@ -36,11 +94,14 @@ pub(super) fn esp32_package_stack_summary(
})
})
.map_or("<platform manifest>", str::trim);
let integration = requested_frameworks(requested_framework)
.integration()
.map_or("unsupported-espidf", Esp32Integration::label);
let sdk_version = installed_framework
.bundled_esp_idf_version(mcu)
.unwrap_or_else(|| "unknown (bundled with framework)".to_string());
format!(
"ESP32 packages: requested platform={requested_platform}, framework={requested_framework}; resolved platform={}@{}, framework={}@{}, toolchain={}@{}, ESP-IDF SDK={sdk_version}",
"ESP32 packages: requested platform={requested_platform}, framework={requested_framework}, framework_pin={framework_pin}, integration={integration}; resolved platform={}@{}, framework={}@{}, toolchain={}@{}, ESP-IDF SDK={sdk_version}",
platform.name,
platform.version,
framework.name,
Expand Down Expand Up @@ -87,11 +148,7 @@ pub(super) fn reject_unsupported_esp_framework(
let Some(framework) = env.get("framework") else {
return Ok(());
};
let parts: Vec<&str> = framework.split(',').map(str::trim).collect();
let espidf = parts.iter().any(|part| part.eq_ignore_ascii_case("espidf"));
let arduino = parts
.iter()
.any(|part| part.eq_ignore_ascii_case("arduino"));
let RequestedFrameworks { arduino, espidf } = requested_frameworks(framework);
if espidf && !arduino {
return Err(fbuild_core::FbuildError::ConfigError(format!(
"framework = {framework} is unsupported for environment '{env_name}': fbuild builds \
Expand Down
82 changes: 80 additions & 2 deletions crates/fbuild-build-esp/src/esp32/orchestrator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
use super::Esp32Orchestrator;
use super::cdc::{cdc_on_boot_enabled, is_esp32_project, warn_if_cdc_on_boot};
use super::helpers::{
apply_effective_define_flags, reject_unsupported_esp_framework,
reject_unsupported_sdkconfig_overlay,
Esp32Integration, apply_effective_define_flags, esp32_package_stack_summary,
reject_unsupported_esp_framework, reject_unsupported_sdkconfig_overlay, requested_frameworks,
};
use super::helpers::{
framework_failure_marker, framework_signature, record_failed_framework_lib,
should_skip_failed_framework_lib,
};
use crate::BuildOrchestrator;
use fbuild_core::Platform;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;

Expand Down Expand Up @@ -353,3 +354,80 @@ fn deploy_esptool_requires_v5_cli() {
"C:/cache/fbuild-tools/esptool/Scripts/esptool.py.exe"
)));
}

fn package_info(name: &str, version: &str) -> fbuild_packages::PackageInfo {
fbuild_packages::PackageInfo {
name: name.to_string(),
version: version.to_string(),
url: format!("https://example.invalid/{name}-{version}.tar.gz"),
install_path: PathBuf::from("/nonexistent"),
checksum: None,
installed_bytes: None,
}
}

#[test]
fn requested_frameworks_parses_single_and_dual_requests() {
let arduino = requested_frameworks("arduino");
assert!(arduino.arduino && !arduino.espidf);
assert_eq!(arduino.integration(), Some(Esp32Integration::ArduinoCore));

let dual = requested_frameworks("arduino, espidf");
assert!(dual.arduino && dual.espidf);
assert_eq!(dual.integration(), Some(Esp32Integration::ArduinoOnIdf));

// Order and spacing must not change the classification.
let reversed = requested_frameworks(" espidf , arduino ");
assert_eq!(reversed, dual);

let bare_idf = requested_frameworks("espidf");
assert_eq!(bare_idf.integration(), None, "bare espidf is unsupported");
}

#[test]
fn stack_summary_records_requested_framework_and_integration_mode() {
let tmp = tempfile::TempDir::new().unwrap();
let installed = fbuild_packages::library::Esp32Framework::new(tmp.path(), "esp32s3");
let platform = package_info("espressif32", "6.13.0");
let framework = package_info("framework-arduinoespressif32", "3.3.7");
let toolchain = package_info("toolchain-xtensa-esp32s3", "8.4.0");

let mut dual_env = HashMap::new();
dual_env.insert("framework".to_string(), "arduino, espidf".to_string());
dual_env.insert(
"platform_packages".to_string(),
"framework-arduinoespressif32@https://example.invalid/fw.tar.gz".to_string(),
);
let summary = esp32_package_stack_summary(
Some(&dual_env),
&platform,
&framework,
&toolchain,
&installed,
"esp32s3",
);
assert!(summary.contains("framework=arduino, espidf"), "{summary}");
assert!(summary.contains("integration=arduino-on-idf"), "{summary}");
assert!(
summary.contains(
"framework_pin=framework-arduinoespressif32@https://example.invalid/fw.tar.gz"
),
"{summary}"
);
assert!(
summary.contains(
"resolved platform=espressif32@6.13.0, framework=framework-arduinoespressif32@3.3.7"
),
"{summary}"
);

let plain = esp32_package_stack_summary(
None, &platform, &framework, &toolchain, &installed, "esp32s3",
);
assert!(plain.contains("framework=<board default>"), "{plain}");
assert!(
plain.contains("framework_pin=<platform manifest>"),
"{plain}"
);
assert!(plain.contains("integration=arduino-core"), "{plain}");
}
Loading