From 3847989c0f5d4e72673b2f7ce8d54515acd88d68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:10:49 -1000 Subject: [PATCH 1/8] wip --- esphome/components/esp8266/__init__.py | 6 +- esphome/components/esp8266/iram_fix.py.script | 133 +++++++++++++++--- .../build_components_base.esp8266-ard.yaml | 6 +- 3 files changed, 120 insertions(+), 25 deletions(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 9d8e6b7d1e..8eab9946f2 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -230,9 +230,9 @@ async def to_code(config): # For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;` cg.add_build_flag("-DNEW_OOM_ABORT") - # In testing mode, fake a larger IRAM to allow linking grouped component tests - # Real ESP8266 hardware only has 32KB IRAM, but for CI testing we pretend it has 2MB - # This is done via a pre-build script that generates a custom linker script + # In testing mode, fake larger memory to allow linking grouped component tests + # Real ESP8266 hardware only has 32KB IRAM and ~80KB RAM, but for CI testing + # we pretend it has much larger memory to test that components compile together if CORE.testing_mode: cg.add_build_flag("-DESPHOME_TESTING_MODE") diff --git a/esphome/components/esp8266/iram_fix.py.script b/esphome/components/esp8266/iram_fix.py.script index 96bddc2ced..d6c4170a18 100644 --- a/esphome/components/esp8266/iram_fix.py.script +++ b/esphome/components/esp8266/iram_fix.py.script @@ -5,8 +5,108 @@ import re Import("env") # noqa +def apply_memory_patches(content): + """Apply IRAM, DRAM, and Flash patches to linker script content. + + Args: + content: Linker script content as string + + Returns: + Patched content as string + """ + patches_applied = [] + + # Replace IRAM size from 0x8000 (32KB) to 0x200000 (2MB) + # The line looks like: iram1_0_seg : org = 0x40100000, len = 0x8000 + new_content = re.sub( + r"(iram1_0_seg\s*:\s*org\s*=\s*0x40100000\s*,\s*len\s*=\s*)0x8000", + r"\g<1>0x200000", + content, + ) + if new_content != content: + patches_applied.append("IRAM: 32KB -> 2MB") + content = new_content + + # Replace DRAM (BSS) size to allow larger uninitialized data sections + # The line looks like: dram0_0_seg : org = 0x3FFE8000, len = 0x14000 + # Increase from 0x14000 (80KB) to 0x200000 (2MB) + new_content = re.sub( + r"(dram0_0_seg\s*:\s*org\s*=\s*0x3FFE8000\s*,\s*len\s*=\s*)0x14000", + r"\g<1>0x200000", + content, + ) + if new_content != content: + patches_applied.append("DRAM: 80KB -> 2MB") + content = new_content + + # Replace Flash/irom0 size to allow larger code sections + # The line looks like: irom0_0_seg : org = 0x40201010, len = 0xfeff0 + # Increase from 0xfeff0 (~1MB) to 0x2000000 (32MB) - fake huge flash for testing + new_content = re.sub( + r"(irom0_0_seg\s*:\s*org\s*=\s*0x40201010\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", + r"\g<1>0x2000000", + content, + ) + if new_content != content: + patches_applied.append("Flash: 1MB -> 32MB") + content = new_content + + if patches_applied: + print(f" Patches applied: {', '.join(patches_applied)}") + + return content + + +def patch_linker_script_file(filepath, description): + """Patch a single linker script file in place.""" + if not os.path.exists(filepath): + print(f"ESPHome: {description} not found at {filepath}") + return False + + print(f"ESPHome: Patching {description}...") + with open(filepath, "r") as f: + content = f.read() + + patched_content = apply_memory_patches(content) + + if patched_content != content: + with open(filepath, "w") as f: + f.write(patched_content) + print(f"ESPHome: Successfully patched {description}") + return True + else: + print(f"ESPHome: {description} already patched or no changes needed") + return False + + +def patch_sdk_linker_script_immediately(env): + """Patch SDK linker scripts immediately when script loads. + + This must happen BEFORE PlatformIO's builder calculates sizes. + """ + # Get the SDK linker script path + ldscript = env.GetProjectOption("board_build.ldscript", "") + if not ldscript: + return + + # Get the framework directory + framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266") + if not framework_dir: + return + + # Patch the main SDK linker script (flash layout) + sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript) + if os.path.exists(sdk_ld): + patch_linker_script_file(sdk_ld, f"SDK {ldscript}") + + # Also patch the local.eagle.app.v6.common.ld in SDK (contains IRAM and DRAM) + local_common = os.path.join(framework_dir, "tools", "sdk", "ld", "local.eagle.app.v6.common.ld") + if os.path.exists(local_common): + patch_linker_script_file(local_common, "SDK local.eagle.app.v6.common.ld") + + def patch_linker_script_after_preprocess(source, target, env): - """Patch the local linker script after PlatformIO preprocesses it.""" + """Patch linker scripts after PlatformIO preprocesses them.""" # Check if we're in testing mode by looking for the define build_flags = env.get("BUILD_FLAGS", []) testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags) @@ -14,29 +114,20 @@ def patch_linker_script_after_preprocess(source, target, env): if not testing_mode: return - # Get the local linker script path - build_dir = env.subst("$BUILD_DIR") - local_ld = os.path.join(build_dir, "ld", "local.eagle.app.v6.common.ld") + # Patch SDK linker scripts first (for size calculation) + patch_sdk_linker_script_immediately(env) - if not os.path.exists(local_ld): + # Patch build directory scripts + build_dir = env.subst("$BUILD_DIR") + ld_dir = os.path.join(build_dir, "ld") + + if not os.path.exists(ld_dir): return - # Read the linker script - with open(local_ld, "r") as f: - content = f.read() - - # Replace IRAM size from 0x8000 (32KB) to 0x200000 (2MB) - # The line looks like: iram1_0_seg : org = 0x40100000, len = 0x8000 - updated = re.sub( - r"(iram1_0_seg\s*:\s*org\s*=\s*0x40100000\s*,\s*len\s*=\s*)0x8000", - r"\g<1>0x200000", - content, - ) - - if updated != content: - with open(local_ld, "w") as f: - f.write(updated) - print("ESPHome: Patched IRAM size to 2MB for testing mode") + # Patch the local linker script (contains IRAM and DRAM definitions) + local_ld = os.path.join(ld_dir, "local.eagle.app.v6.common.ld") + if os.path.exists(local_ld): + patch_linker_script_file(local_ld, "build local.eagle.app.v6.common.ld") # Hook into the build process right before linking diff --git a/tests/test_build_components/build_components_base.esp8266-ard.yaml b/tests/test_build_components/build_components_base.esp8266-ard.yaml index e4d6607c86..8e2a5461f3 100644 --- a/tests/test_build_components/build_components_base.esp8266-ard.yaml +++ b/tests/test_build_components/build_components_base.esp8266-ard.yaml @@ -1,9 +1,13 @@ esphome: name: componenttestesp8266ard friendly_name: $component_name + platformio_options: + board_upload.flash_size: 16MB + board_upload.maximum_size: 16777216 + board_build.ldscript: eagle.flash.16m14m.ld esp8266: - board: d1_mini + board: d1_mini_pro logger: level: VERY_VERBOSE From 5b568073291101144aa51a673184f8eddc0cdb8a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:11:43 -1000 Subject: [PATCH 2/8] wip --- esphome/components/esp8266/__init__.py | 10 ++++++---- .../{iram_fix.py.script => testing_mode.py.script} | 0 2 files changed, 6 insertions(+), 4 deletions(-) rename esphome/components/esp8266/{iram_fix.py.script => testing_mode.py.script} (100%) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 8eab9946f2..a74f9ee8ce 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -190,7 +190,9 @@ async def to_code(config): cg.add_define("ESPHOME_VARIANT", "ESP8266") cg.add_define(ThreadModel.SINGLE) - cg.add_platformio_option("extra_scripts", ["pre:iram_fix.py", "post:post_build.py"]) + cg.add_platformio_option( + "extra_scripts", ["pre:testing_mode.py", "post:post_build.py"] + ) conf = config[CONF_FRAMEWORK] cg.add_platformio_option("framework", "arduino") @@ -271,8 +273,8 @@ def copy_files(): post_build_file, CORE.relative_build_path("post_build.py"), ) - iram_fix_file = dir / "iram_fix.py.script" + testing_mode_file = dir / "testing_mode.py.script" copy_file_if_changed( - iram_fix_file, - CORE.relative_build_path("iram_fix.py"), + testing_mode_file, + CORE.relative_build_path("testing_mode.py"), ) diff --git a/esphome/components/esp8266/iram_fix.py.script b/esphome/components/esp8266/testing_mode.py.script similarity index 100% rename from esphome/components/esp8266/iram_fix.py.script rename to esphome/components/esp8266/testing_mode.py.script From ce6d0cd8460cdd0ca02f5b1838a85f5511fcbb97 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:17:49 -1000 Subject: [PATCH 3/8] tweak --- .../components/esp8266/testing_mode.py.script | 87 +++++++++---------- .../build_components_base.esp8266-ard.yaml | 4 - 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/esphome/components/esp8266/testing_mode.py.script b/esphome/components/esp8266/testing_mode.py.script index d6c4170a18..0b59c2e000 100644 --- a/esphome/components/esp8266/testing_mode.py.script +++ b/esphome/components/esp8266/testing_mode.py.script @@ -79,57 +79,56 @@ def patch_linker_script_file(filepath, description): return False -def patch_sdk_linker_script_immediately(env): - """Patch SDK linker scripts immediately when script loads. - - This must happen BEFORE PlatformIO's builder calculates sizes. - """ - # Get the SDK linker script path - ldscript = env.GetProjectOption("board_build.ldscript", "") - if not ldscript: - return - - # Get the framework directory - framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266") - if not framework_dir: - return - - # Patch the main SDK linker script (flash layout) - sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript) - if os.path.exists(sdk_ld): - patch_linker_script_file(sdk_ld, f"SDK {ldscript}") - - # Also patch the local.eagle.app.v6.common.ld in SDK (contains IRAM and DRAM) - local_common = os.path.join(framework_dir, "tools", "sdk", "ld", "local.eagle.app.v6.common.ld") - if os.path.exists(local_common): - patch_linker_script_file(local_common, "SDK local.eagle.app.v6.common.ld") - - -def patch_linker_script_after_preprocess(source, target, env): - """Patch linker scripts after PlatformIO preprocesses them.""" - # Check if we're in testing mode by looking for the define +def patch_local_linker_script(source, target, env): + """Patch the local.eagle.app.v6.common.ld in build directory for IRAM.""" + # Check if we're in testing mode build_flags = env.get("BUILD_FLAGS", []) testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags) if not testing_mode: return - # Patch SDK linker scripts first (for size calculation) - patch_sdk_linker_script_immediately(env) - - # Patch build directory scripts + # Patch the local linker script if it exists build_dir = env.subst("$BUILD_DIR") ld_dir = os.path.join(build_dir, "ld") - - if not os.path.exists(ld_dir): - return - - # Patch the local linker script (contains IRAM and DRAM definitions) - local_ld = os.path.join(ld_dir, "local.eagle.app.v6.common.ld") - if os.path.exists(local_ld): - patch_linker_script_file(local_ld, "build local.eagle.app.v6.common.ld") + if os.path.exists(ld_dir): + local_ld = os.path.join(ld_dir, "local.eagle.app.v6.common.ld") + if os.path.exists(local_ld): + patch_linker_script_file(local_ld, "local.eagle.app.v6.common.ld") -# Hook into the build process right before linking -# This runs after PlatformIO has already preprocessed the linker scripts -env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_linker_script_after_preprocess) +# Check if we're in testing mode +build_flags = env.get("BUILD_FLAGS", []) +testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags) + +if testing_mode: + # Create custom linker script immediately (before linker command is built) + build_dir = env.subst("$BUILD_DIR") + ldscript = env.GetProjectOption("board_build.ldscript", "") + + if ldscript: + framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266") + if framework_dir: + sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript) + custom_ld = os.path.join(build_dir, f"testing_{ldscript}") + + if os.path.exists(sdk_ld) and not os.path.exists(custom_ld): + # Read and patch the SDK linker script + with open(sdk_ld, "r") as f: + content = f.read() + + patched_content = apply_memory_patches(content) + + # Write custom linker script + with open(custom_ld, "w") as f: + f.write(patched_content) + + print(f"ESPHome: Created custom linker script: {custom_ld}") + + # Tell the linker to use our custom script + if os.path.exists(custom_ld): + env.Replace(LDSCRIPT_PATH=custom_ld) + print(f"ESPHome: Using custom linker script with patched memory limits") + + # Hook to patch local.eagle.app.v6.common.ld after it's created + env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_local_linker_script) diff --git a/tests/test_build_components/build_components_base.esp8266-ard.yaml b/tests/test_build_components/build_components_base.esp8266-ard.yaml index 8e2a5461f3..1e2d614392 100644 --- a/tests/test_build_components/build_components_base.esp8266-ard.yaml +++ b/tests/test_build_components/build_components_base.esp8266-ard.yaml @@ -1,10 +1,6 @@ esphome: name: componenttestesp8266ard friendly_name: $component_name - platformio_options: - board_upload.flash_size: 16MB - board_upload.maximum_size: 16777216 - board_build.ldscript: eagle.flash.16m14m.ld esp8266: board: d1_mini_pro From 5bd7342ff434fc30498ab31b438073f7eb4f9218 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:19:06 -1000 Subject: [PATCH 4/8] wip --- .../components/esp8266/testing_mode.py.script | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/esphome/components/esp8266/testing_mode.py.script b/esphome/components/esp8266/testing_mode.py.script index 0b59c2e000..b1e476ca29 100644 --- a/esphome/components/esp8266/testing_mode.py.script +++ b/esphome/components/esp8266/testing_mode.py.script @@ -105,30 +105,31 @@ if testing_mode: # Create custom linker script immediately (before linker command is built) build_dir = env.subst("$BUILD_DIR") ldscript = env.GetProjectOption("board_build.ldscript", "") + assert ldscript, "No linker script configured in board_build.ldscript" - if ldscript: - framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266") - if framework_dir: - sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript) - custom_ld = os.path.join(build_dir, f"testing_{ldscript}") + framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266") + assert framework_dir is not None, "Could not find framework-arduinoespressif8266 package" - if os.path.exists(sdk_ld) and not os.path.exists(custom_ld): - # Read and patch the SDK linker script - with open(sdk_ld, "r") as f: - content = f.read() + sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript) + custom_ld = os.path.join(build_dir, f"testing_{ldscript}") - patched_content = apply_memory_patches(content) + if os.path.exists(sdk_ld) and not os.path.exists(custom_ld): + # Read and patch the SDK linker script + with open(sdk_ld, "r") as f: + content = f.read() - # Write custom linker script - with open(custom_ld, "w") as f: - f.write(patched_content) + patched_content = apply_memory_patches(content) - print(f"ESPHome: Created custom linker script: {custom_ld}") + # Write custom linker script + with open(custom_ld, "w") as f: + f.write(patched_content) - # Tell the linker to use our custom script - if os.path.exists(custom_ld): - env.Replace(LDSCRIPT_PATH=custom_ld) - print(f"ESPHome: Using custom linker script with patched memory limits") + print(f"ESPHome: Created custom linker script: {custom_ld}") + + # Tell the linker to use our custom script + assert os.path.exists(custom_ld), f"Custom linker script not found: {custom_ld}" + env.Replace(LDSCRIPT_PATH=custom_ld) + print(f"ESPHome: Using custom linker script with patched memory limits") # Hook to patch local.eagle.app.v6.common.ld after it's created env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_local_linker_script) From 6a042188c1a5d7d4709c2cdb81249b5337ebe6a4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:19:40 -1000 Subject: [PATCH 5/8] wip --- esphome/components/esp8266/testing_mode.py.script | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp8266/testing_mode.py.script b/esphome/components/esp8266/testing_mode.py.script index b1e476ca29..964304a69d 100644 --- a/esphome/components/esp8266/testing_mode.py.script +++ b/esphome/components/esp8266/testing_mode.py.script @@ -102,7 +102,8 @@ build_flags = env.get("BUILD_FLAGS", []) testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags) if testing_mode: - # Create custom linker script immediately (before linker command is built) + # Create a custom linker script in the build directory with patched memory limits + # This allows larger IRAM/DRAM/Flash for CI component grouping tests build_dir = env.subst("$BUILD_DIR") ldscript = env.GetProjectOption("board_build.ldscript", "") assert ldscript, "No linker script configured in board_build.ldscript" @@ -110,26 +111,29 @@ if testing_mode: framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266") assert framework_dir is not None, "Could not find framework-arduinoespressif8266 package" + # Read the original SDK linker script (read-only, SDK is never modified) sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript) + # Create a custom version in the build directory (isolated, temporary) custom_ld = os.path.join(build_dir, f"testing_{ldscript}") if os.path.exists(sdk_ld) and not os.path.exists(custom_ld): - # Read and patch the SDK linker script + # Read the SDK linker script with open(sdk_ld, "r") as f: content = f.read() + # Apply memory patches (IRAM: 2MB, DRAM: 2MB, Flash: 32MB) patched_content = apply_memory_patches(content) - # Write custom linker script + # Write the patched linker script to the build directory with open(custom_ld, "w") as f: f.write(patched_content) print(f"ESPHome: Created custom linker script: {custom_ld}") - # Tell the linker to use our custom script + # Tell the linker to use our custom script from the build directory assert os.path.exists(custom_ld), f"Custom linker script not found: {custom_ld}" env.Replace(LDSCRIPT_PATH=custom_ld) print(f"ESPHome: Using custom linker script with patched memory limits") - # Hook to patch local.eagle.app.v6.common.ld after it's created + # Also patch local.eagle.app.v6.common.ld after PlatformIO creates it env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_local_linker_script) From 09951d190c86bd142c87aa4eddd2b4f1b1d11e1a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:21:11 -1000 Subject: [PATCH 6/8] wip --- .../components/esp8266/testing_mode.py.script | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp8266/testing_mode.py.script b/esphome/components/esp8266/testing_mode.py.script index 964304a69d..1869a39df6 100644 --- a/esphome/components/esp8266/testing_mode.py.script +++ b/esphome/components/esp8266/testing_mode.py.script @@ -16,43 +16,41 @@ def apply_memory_patches(content): """ patches_applied = [] - # Replace IRAM size from 0x8000 (32KB) to 0x200000 (2MB) - # The line looks like: iram1_0_seg : org = 0x40100000, len = 0x8000 + # Patch IRAM segment to 2MB (for larger code in IRAM) + # Matches: iram1_0_seg : org = 0x..., len = 0x... new_content = re.sub( - r"(iram1_0_seg\s*:\s*org\s*=\s*0x40100000\s*,\s*len\s*=\s*)0x8000", + r"(iram1_0_seg\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", r"\g<1>0x200000", content, ) if new_content != content: - patches_applied.append("IRAM: 32KB -> 2MB") + patches_applied.append("IRAM") content = new_content - # Replace DRAM (BSS) size to allow larger uninitialized data sections - # The line looks like: dram0_0_seg : org = 0x3FFE8000, len = 0x14000 - # Increase from 0x14000 (80KB) to 0x200000 (2MB) + # Patch DRAM segment to 2MB (for larger BSS/data sections) + # Matches: dram0_0_seg : org = 0x..., len = 0x... new_content = re.sub( - r"(dram0_0_seg\s*:\s*org\s*=\s*0x3FFE8000\s*,\s*len\s*=\s*)0x14000", + r"(dram0_0_seg\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", r"\g<1>0x200000", content, ) if new_content != content: - patches_applied.append("DRAM: 80KB -> 2MB") + patches_applied.append("DRAM") content = new_content - # Replace Flash/irom0 size to allow larger code sections - # The line looks like: irom0_0_seg : org = 0x40201010, len = 0xfeff0 - # Increase from 0xfeff0 (~1MB) to 0x2000000 (32MB) - fake huge flash for testing + # Patch Flash segment to 32MB (for larger code sections) + # Matches: irom0_0_seg : org = 0x..., len = 0x... new_content = re.sub( - r"(irom0_0_seg\s*:\s*org\s*=\s*0x40201010\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", + r"(irom0_0_seg\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", r"\g<1>0x2000000", content, ) if new_content != content: - patches_applied.append("Flash: 1MB -> 32MB") + patches_applied.append("Flash") content = new_content if patches_applied: - print(f" Patches applied: {', '.join(patches_applied)}") + print(f" Patched memory segments: {', '.join(patches_applied)} (IRAM/DRAM: 2MB, Flash: 32MB)") return content From 4e629dfd899d6ed5b49666563c114ec0acbfe007 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:21:40 -1000 Subject: [PATCH 7/8] wip --- .../components/esp8266/testing_mode.py.script | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/esphome/components/esp8266/testing_mode.py.script b/esphome/components/esp8266/testing_mode.py.script index 1869a39df6..b1ff87b85d 100644 --- a/esphome/components/esp8266/testing_mode.py.script +++ b/esphome/components/esp8266/testing_mode.py.script @@ -5,6 +5,24 @@ import re Import("env") # noqa +def patch_segment_size(content, segment_name, new_size, label): + """Patch a memory segment's length in linker script. + + Args: + content: Linker script content + segment_name: Name of the segment (e.g., 'iram1_0_seg') + new_size: New size as hex string (e.g., '0x200000') + label: Human-readable label for logging (e.g., 'IRAM') + + Returns: + Tuple of (patched_content, was_patched) + """ + # Match: segment_name : org = 0x..., len = 0x... + pattern = rf"({segment_name}\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+" + new_content = re.sub(pattern, rf"\g<1>{new_size}", content) + return new_content, new_content != content + + def apply_memory_patches(content): """Apply IRAM, DRAM, and Flash patches to linker script content. @@ -16,38 +34,20 @@ def apply_memory_patches(content): """ patches_applied = [] - # Patch IRAM segment to 2MB (for larger code in IRAM) - # Matches: iram1_0_seg : org = 0x..., len = 0x... - new_content = re.sub( - r"(iram1_0_seg\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", - r"\g<1>0x200000", - content, - ) - if new_content != content: + # Patch IRAM to 2MB (for larger code in IRAM) + content, patched = patch_segment_size(content, "iram1_0_seg", "0x200000", "IRAM") + if patched: patches_applied.append("IRAM") - content = new_content - # Patch DRAM segment to 2MB (for larger BSS/data sections) - # Matches: dram0_0_seg : org = 0x..., len = 0x... - new_content = re.sub( - r"(dram0_0_seg\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", - r"\g<1>0x200000", - content, - ) - if new_content != content: + # Patch DRAM to 2MB (for larger BSS/data sections) + content, patched = patch_segment_size(content, "dram0_0_seg", "0x200000", "DRAM") + if patched: patches_applied.append("DRAM") - content = new_content - # Patch Flash segment to 32MB (for larger code sections) - # Matches: irom0_0_seg : org = 0x..., len = 0x... - new_content = re.sub( - r"(irom0_0_seg\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+", - r"\g<1>0x2000000", - content, - ) - if new_content != content: + # Patch Flash to 32MB (for larger code sections) + content, patched = patch_segment_size(content, "irom0_0_seg", "0x2000000", "Flash") + if patched: patches_applied.append("Flash") - content = new_content if patches_applied: print(f" Patched memory segments: {', '.join(patches_applied)} (IRAM/DRAM: 2MB, Flash: 32MB)") From c2147a57f19983d791cc4ab502572202d66f2cfd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 20 Oct 2025 15:30:04 -1000 Subject: [PATCH 8/8] bot review --- .../components/esp8266/testing_mode.py.script | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp8266/testing_mode.py.script b/esphome/components/esp8266/testing_mode.py.script index b1ff87b85d..44d84b765c 100644 --- a/esphome/components/esp8266/testing_mode.py.script +++ b/esphome/components/esp8266/testing_mode.py.script @@ -5,6 +5,12 @@ import re Import("env") # noqa +# Memory sizes for testing mode (allow larger builds for CI component grouping) +TESTING_IRAM_SIZE = "0x200000" # 2MB +TESTING_DRAM_SIZE = "0x200000" # 2MB +TESTING_FLASH_SIZE = "0x2000000" # 32MB + + def patch_segment_size(content, segment_name, new_size, label): """Patch a memory segment's length in linker script. @@ -34,29 +40,43 @@ def apply_memory_patches(content): """ patches_applied = [] - # Patch IRAM to 2MB (for larger code in IRAM) - content, patched = patch_segment_size(content, "iram1_0_seg", "0x200000", "IRAM") + # Patch IRAM (for larger code in IRAM) + content, patched = patch_segment_size(content, "iram1_0_seg", TESTING_IRAM_SIZE, "IRAM") if patched: patches_applied.append("IRAM") - # Patch DRAM to 2MB (for larger BSS/data sections) - content, patched = patch_segment_size(content, "dram0_0_seg", "0x200000", "DRAM") + # Patch DRAM (for larger BSS/data sections) + content, patched = patch_segment_size(content, "dram0_0_seg", TESTING_DRAM_SIZE, "DRAM") if patched: patches_applied.append("DRAM") - # Patch Flash to 32MB (for larger code sections) - content, patched = patch_segment_size(content, "irom0_0_seg", "0x2000000", "Flash") + # Patch Flash (for larger code sections) + content, patched = patch_segment_size(content, "irom0_0_seg", TESTING_FLASH_SIZE, "Flash") if patched: patches_applied.append("Flash") if patches_applied: - print(f" Patched memory segments: {', '.join(patches_applied)} (IRAM/DRAM: 2MB, Flash: 32MB)") + iram_mb = int(TESTING_IRAM_SIZE, 16) // (1024 * 1024) + dram_mb = int(TESTING_DRAM_SIZE, 16) // (1024 * 1024) + flash_mb = int(TESTING_FLASH_SIZE, 16) // (1024 * 1024) + print(f" Patched memory segments: {', '.join(patches_applied)} (IRAM/DRAM: {iram_mb}MB, Flash: {flash_mb}MB)") return content def patch_linker_script_file(filepath, description): - """Patch a single linker script file in place.""" + """Patch a linker script file in the build directory with enlarged memory segments. + + This function modifies linker scripts in the build directory only (never SDK files). + It patches IRAM, DRAM, and Flash segments to allow larger builds in testing mode. + + Args: + filepath: Path to the linker script file in the build directory + description: Human-readable description for logging + + Returns: + True if the file was patched, False if already patched or not found + """ if not os.path.exists(filepath): print(f"ESPHome: {description} not found at {filepath}") return False @@ -78,7 +98,16 @@ def patch_linker_script_file(filepath, description): def patch_local_linker_script(source, target, env): - """Patch the local.eagle.app.v6.common.ld in build directory for IRAM.""" + """Patch the local.eagle.app.v6.common.ld in build directory. + + This patches the preprocessed linker script that PlatformIO creates in the build + directory, enlarging IRAM, DRAM, and Flash segments for testing mode. + + Args: + source: SCons source nodes + target: SCons target nodes + env: SCons environment + """ # Check if we're in testing mode build_flags = env.get("BUILD_FLAGS", []) testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)