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
11 changes: 10 additions & 1 deletion crates/fbuild-build-esp/src/esp32/esp32_linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub struct Esp32Linker {
/// The app partition's size, which is what the image must fit. `None`
/// falls back to `max_flash` (FastLED/fbuild#1409).
app_size_limit: Option<u64>,
print_memory_usage: bool,
}

impl Esp32Linker {
Expand Down Expand Up @@ -219,6 +220,7 @@ impl Esp32Linker {
verbose,
caller_path: None,
app_size_limit: None,
print_memory_usage: true,
}
}

Expand All @@ -230,6 +232,11 @@ impl Esp32Linker {
self
}

pub fn with_memory_usage_reporting(mut self, enabled: bool) -> Self {
self.print_memory_usage = enabled;
self
}

fn flash_limit(&self) -> Option<u64> {
self.app_size_limit.or(self.max_flash)
}
Expand Down Expand Up @@ -418,7 +425,9 @@ impl Esp32Linker {
link_args.extend(self.linker_scripts.to_args());

// Memory usage reporting
link_args.push("-Wl,--print-memory-usage".to_string());
if self.print_memory_usage {
link_args.push("-Wl,--print-memory-usage".to_string());
}

// Output
link_args.extend(["-o".to_string(), elf_path.to_string_lossy().to_string()]);
Expand Down
14 changes: 14 additions & 0 deletions crates/fbuild-build-esp/src/esp32/esp32_linker_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,20 @@ fn test_esp32_link_command_emits_linker_map_next_to_elf() {
);
}

#[test]
fn legacy_xtensa_linker_omits_unsupported_memory_report() {
let args = test_linker("esp32")
.with_memory_usage_reporting(false)
.build_link_args(
&[],
&[],
Path::new("/build/firmware.elf"),
&LinkExtraArgs::default(),
);
assert!(!args.iter().any(|arg| arg == "-Wl,--print-memory-usage"));
assert!(args.iter().any(|arg| arg == "-Wl,-Map=/build/firmware.map"));
}

#[test]
fn legacy_esp32s3_link_includes_cpp_runtime_once() {
let mut linker = test_linker("esp32s3");
Expand Down
65 changes: 61 additions & 4 deletions crates/fbuild-build-esp/src/esp32/fixups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@ type Rule = fn(Esp32McuConfig) -> Esp32McuConfig;
/// - Per-MCU Xtensa GCC 8 (official `espressif32` 6.x/7.x) needs its own
/// linker recipe, older language standards, and no LTO.
pub fn for_toolchain(config: Esp32McuConfig, toolchain: &PackageInfo) -> Esp32McuConfig {
let per_mcu_xtensa = is_per_mcu_xtensa(&toolchain.name);
let legacy_xtensa32 = toolchain.name == "toolchain-xtensa32";
let per_mcu_xtensa = is_per_mcu_xtensa(&toolchain.name) || legacy_xtensa32;
let config = apply_if(is_before_gcc14(&toolchain.version), config, drop_cxa_atexit);
let config = apply_if(per_mcu_xtensa, config, drop_hardware_atomics);
apply_if(
per_mcu_xtensa && is_gcc8(&toolchain.version),
let config = apply_if(
(per_mcu_xtensa && is_gcc8(&toolchain.version)) || legacy_xtensa32,
config,
legacy_gcc8,
)
);
apply_if(legacy_xtensa32, config, legacy_gcc5)
}

/// GCC 5 in the official legacy ESP32 package predates `-fmacro-prefix-map`.
pub fn supports_macro_prefix_map(toolchain: &PackageInfo) -> bool {
toolchain.name != "toolchain-xtensa32"
}

/// Drop LTO when the SDK links with `-fno-lto`: objects compiled with LTO
Expand Down Expand Up @@ -82,6 +89,40 @@ fn legacy_gcc8(config: Esp32McuConfig) -> Esp32McuConfig {
without_lto(gcc8_language_standards(gcc8_linker_recipe(config)))
}

fn legacy_gcc5(mut config: Esp32McuConfig) -> Esp32McuConfig {
config.compiler_flags.cxx = replaced(config.compiler_flags.cxx, "-std=gnu++11", "-std=gnu++1z");
config.linker_flags = [
"-nostdlib",
"-Wl,-static",
"-u",
"call_user_start_cpu0",
"-Wl,--undefined=uxTopUsedPriority",
"-Wl,--gc-sections",
"-Wl,-EL",
"-u",
"ld_include_panic_highint_hdl",
"-u",
"__cxa_guard_dummy",
"-u",
"__cxx_fatal_exception",
]
.into_iter()
.map(str::to_string)
.collect();
config.linker_scripts = [
"esp32_out.ld",
"esp32.common.ld",
"esp32.rom.ld",
"esp32.peripherals.ld",
"esp32.rom.libgcc.ld",
"esp32.rom.spiram_incompatible_fns.ld",
]
.into_iter()
.map(str::to_string)
.collect();
config
}

/// Swap in the MCU's `legacy_gcc8` linker flags and scripts, if it has them.
fn gcc8_linker_recipe(mut config: Esp32McuConfig) -> Esp32McuConfig {
if let Some(recipe) = &config.legacy_gcc8 {
Expand Down Expand Up @@ -238,6 +279,22 @@ mod tests {
assert!(!has_lto(&config));
}

#[test]
fn platformio_legacy_xtensa32_uses_supported_flags() {
let package = toolchain("toolchain-xtensa32", "2.50200.97");
let config = for_toolchain(get_mcu_config("esp32").unwrap(), &package);
assert!(!has(
&config.compiler_flags.common,
"-mdisable-hardware-atomics"
));
assert!(has(&config.compiler_flags.cxx, "-std=gnu++1z"));
assert!(!has(&config.compiler_flags.cxx, "-std=gnu++2b"));
assert!(!has_lto(&config));
assert!(!supports_macro_prefix_map(&package));
assert!(has(&config.linker_flags, "call_user_start_cpu0"));
assert!(!has(&config.linker_flags, "-Wl,--no-warn-rwx-segments"));
}

#[test]
fn gcc8_rules_need_a_per_mcu_xtensa_package() {
let base = get_mcu_config("esp32s3").unwrap();
Expand Down
7 changes: 5 additions & 2 deletions crates/fbuild-build-esp/src/esp32/orchestrator/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,10 @@ impl BuildOrchestrator for Esp32Orchestrator {
// SDK defines (from flags/defines) are prepended so user flags can override them.
let mut user_flags = sdk_defines.clone();
// Before the user's build_flags, so their own prefix maps still win.
user_flags.extend(framework_macro_prefix_map(&core_dir));
user_flags.extend(sdk_farm.as_ref().map(SdkIncludeFarm::macro_prefix_map));
if fixups::supports_macro_prefix_map(&toolchain_info) {
user_flags.extend(framework_macro_prefix_map(&core_dir));
user_flags.extend(sdk_farm.as_ref().map(SdkIncludeFarm::macro_prefix_map));
}
let mut user_build_flags = ctx.config.get_build_flags(&params.env_name)?;
user_build_flags.extend(params.extra_build_flags.clone());
user_flags.extend(user_build_flags.clone());
Expand Down Expand Up @@ -861,6 +863,7 @@ impl BuildOrchestrator for Esp32Orchestrator {
params.verbose,
)
.with_caller_path(params.caller_path.clone())
.with_memory_usage_reporting(toolchain_info.name != "toolchain-xtensa32")
.with_app_size_limit(super::boot_artifacts::app_partition_limit(
&params.project_dir,
&framework,
Expand Down
18 changes: 12 additions & 6 deletions crates/fbuild-build-esp/src/esp32/orchestrator/packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,12 +333,10 @@ fn per_mcu_toolchain_name(mcu_config: &Esp32McuConfig) -> Option<String> {

/// The toolchain package the platform actually declares.
///
/// A declared per-MCU package wins: pioarduino 51.x declares only those, and
/// the official PlatformIO `espressif32` 6.x/7.x declares both per-MCU GCC 8
/// toolchains and a unified one, but its Arduino builds use the per-MCU
/// package. pioarduino 53.x/54.x declare only the unified
/// `toolchain-xtensa-esp-elf`, by a registry version rather than a metadata
/// URL, so "no metadata URL" does not imply a per-MCU package.
/// Prefer a declared per-MCU package for pioarduino 51.x and official 6.x/7.x.
/// Official 1.11.2 calls ESP32's package `toolchain-xtensa32`. Pioarduino
/// 53.x/54.x declare only a unified registry package, so a missing metadata
/// URL does not imply a per-MCU package.
fn platform_toolchain_name(
platform: &fbuild_packages::library::Esp32Platform,
mcu_config: &Esp32McuConfig,
Expand All @@ -349,6 +347,10 @@ fn platform_toolchain_name(
fn toolchain_name_for(mcu_config: &Esp32McuConfig, declares: impl Fn(&str) -> bool) -> String {
per_mcu_toolchain_name(mcu_config)
.filter(|name| declares(name))
.or_else(|| {
(mcu_config.mcu == "esp32" && declares("toolchain-xtensa32"))
.then(|| "toolchain-xtensa32".to_string())
})
.unwrap_or_else(|| primary_toolchain_name(mcu_config.is_riscv()).to_string())
}

Expand Down Expand Up @@ -986,3 +988,7 @@ mod registry_toolchain_tests {
}
}
}

#[cfg(test)]
#[path = "packages_legacy_tests.rs"]
mod legacy_tests;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use super::*;

#[test]
fn legacy_espressif32_1_11_2_uses_xtensa32() {
// The official espressif32@1.11.2 manifest declares this sole Xtensa package.
let packages = serde_json::json!({"toolchain-xtensa32": {"version": "~2.50200.0"}});
assert_eq!(
toolchain_name_for(&get_mcu_config("esp32").unwrap(), |name| packages
.get(name)
.is_some()),
"toolchain-xtensa32"
);
}
38 changes: 36 additions & 2 deletions crates/fbuild-library/src/library/esp32_framework/sdk_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@ fn pio_join_literals(line: &str) -> Option<PathBuf> {
/// Returns `None` when the script is absent or does not parse, so the caller
/// can fall back to the tree scan.
fn parse_pio_cpppath(root: &Path, mcu: &str) -> Option<Vec<PathBuf>> {
let script = root
let per_mcu_script = root
.join("tools")
.join(format!("platformio-build-{mcu}.py"));
let script = if per_mcu_script.exists() {
per_mcu_script
} else {
root.join("tools").join("platformio-build.py")
};
let content = std::fs::read_to_string(&script).ok()?;

let mut dirs = Vec::new();
Expand Down Expand Up @@ -103,7 +108,11 @@ pub(super) fn sdk_mcu_dir(fw: &Esp32Framework, mcu: &str) -> PathBuf {
if new_path.exists() {
return new_path;
}
root.join("tools").join("sdk").join(mcu)
let flat_path = root.join("tools").join("sdk");
if flat_path.join("include").exists() {
return flat_path;
}
flat_path.join(mcu)
}

fn sdk_memory_variant_dir(sdk_dir: &Path, requested: Option<&str>) -> Option<PathBuf> {
Expand Down Expand Up @@ -382,3 +391,28 @@ impl Esp32Framework {
flags
}
}

#[cfg(test)]
mod legacy_sdk_tests {
use super::*;

#[test]
fn platformio_1_11_2_builder_supplies_flat_sdk_includes() {
let tmp = tempfile::TempDir::new().unwrap();
let root = tmp.path();
let mut script = String::from("CPPPATH=[\n");
for i in 0..20 {
let dir = root.join(format!("tools/sdk/include/component{i}"));
std::fs::create_dir_all(&dir).unwrap();
script.push_str(&format!(
"join(FRAMEWORK_DIR, \"tools\", \"sdk\", \"include\", \"component{i}\"),\n"
));
}
script.push_str("]\n");
std::fs::write(root.join("tools/platformio-build.py"), script).unwrap();

let dirs = parse_pio_cpppath(root, "esp32").unwrap();
assert_eq!(dirs.len(), 20);
assert_eq!(dirs[0], root.join("tools/sdk/include/component0"));
}
}
17 changes: 16 additions & 1 deletion crates/fbuild-library/src/library/esptool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ impl Esptool {
}

fn validate_source_esptool(dir: &Path) -> Result<()> {
if dir.join("pyproject.toml").is_file() && dir.join("esptool.py").is_file() {
if (dir.join("pyproject.toml").is_file() || dir.join("setup.py").is_file())
&& dir.join("esptool.py").is_file()
{
Ok(())
} else {
Err(FbuildError::PackageError(format!(
Expand Down Expand Up @@ -972,4 +974,17 @@ mod tests {
);
assert!(validate_source_esptool(tmp.path()).is_err());
}

#[test]
fn legacy_platformio_esptool_setup_py_archive_is_valid() {
let tmp = tempfile::TempDir::new().unwrap();
std::fs::write(
tmp.path().join("setup.py"),
"from setuptools import setup\nsetup()\n",
)
.unwrap();
std::fs::write(tmp.path().join("esptool.py"), "print('esptool')\n").unwrap();

validate_source_esptool(tmp.path()).unwrap();
}
}
Loading