|
2 | 2 | # This is implemented as a pio post-script to ensure that we can |
3 | 3 | # place our linker script at the correct point in the command arguments. |
4 | 4 | Import("env") |
| 5 | +import shutil |
5 | 6 | from pathlib import Path |
6 | 7 |
|
7 | | -platform = env.get("PIOPLATFORM") |
8 | | -script_file = Path(f"tools/dynarray_{platform}.ld") |
9 | | -if script_file.is_file(): |
10 | | - linker_script = f"-T{script_file}" |
11 | | - if platform == "espressif32": |
12 | | - # For ESP32, the script must be added at the right point in the list |
13 | | - linkflags = env.get("LINKFLAGS", []) |
14 | | - idx = linkflags.index("memory.ld") |
15 | | - linkflags.insert(idx+1, linker_script) |
16 | | - env.Replace(LINKFLAGS=linkflags) |
| 8 | +# Linker script fragment injected into the rodata output section of whichever |
| 9 | +# platform we're building for. Placed just before the end-of-rodata marker so |
| 10 | +# that the dynarray entries land in flash rodata and are correctly sorted. |
| 11 | +DYNARRAY_INJECTION = ( |
| 12 | + "\n /* dynarray: WLED dynamic module arrays */\n" |
| 13 | + " . = ALIGN(0x10);\n" |
| 14 | + " KEEP(*(SORT_BY_INIT_PRIORITY(.dynarray.*)))\n" |
| 15 | + " " |
| 16 | +) |
| 17 | + |
| 18 | + |
| 19 | +def inject_before_marker(path, marker): |
| 20 | + """Patch a linker script file in-place, inserting DYNARRAY_INJECTION before marker.""" |
| 21 | + original = path.read_text() |
| 22 | + marker_pos = original.find(marker) |
| 23 | + if marker_pos < 0: |
| 24 | + raise RuntimeError( |
| 25 | + f"DYNARRAY injection marker not found in linker script: path={path}, marker={marker!r}" |
| 26 | + ) |
| 27 | + patched = original[:marker_pos] + DYNARRAY_INJECTION + original[marker_pos:] |
| 28 | + path.write_text(patched) |
| 29 | + |
| 30 | + |
| 31 | +if env.get("PIOPLATFORM") == "espressif32": |
| 32 | + # Find sections.ld on the linker search path (LIBPATH). |
| 33 | + sections_ld_path = None |
| 34 | + for ld_dir in env.get("LIBPATH", []): |
| 35 | + candidate = Path(str(ld_dir)) / "sections.ld" |
| 36 | + if candidate.exists(): |
| 37 | + sections_ld_path = candidate |
| 38 | + break |
| 39 | + |
| 40 | + if sections_ld_path is not None: |
| 41 | + # Inject inside the existing .flash.rodata output section, just before |
| 42 | + # _rodata_end. IDF v5 enforces zero gaps between adjacent output |
| 43 | + # sections via ASSERT statements, so INSERT AFTER .flash.rodata would |
| 44 | + # fail. Injecting inside the section creates no new output section and |
| 45 | + # leaves the ASSERTs satisfied. |
| 46 | + build_dir = Path(env.subst("$BUILD_DIR")) |
| 47 | + patched_path = build_dir / "dynarray_sections.ld" |
| 48 | + shutil.copy(sections_ld_path, patched_path) |
| 49 | + inject_before_marker(patched_path, "_rodata_end = ABSOLUTE(.);") |
| 50 | + |
| 51 | + # Replace "sections.ld" in LINKFLAGS with an absolute path to our |
| 52 | + # patched copy. The flag may appear as a bare token, combined as |
| 53 | + # "-Tsections.ld", or split across two tokens ("-T", "sections.ld"). |
| 54 | + patched_str = str(patched_path) |
| 55 | + new_flags = [] |
| 56 | + skip_next = False |
| 57 | + for flag in env.get("LINKFLAGS", []): |
| 58 | + if skip_next: |
| 59 | + new_flags.append(patched_str if flag == "sections.ld" else flag) |
| 60 | + skip_next = False |
| 61 | + elif flag == "-T": |
| 62 | + new_flags.append(flag) |
| 63 | + skip_next = True |
| 64 | + else: |
| 65 | + new_flags.append(flag.replace("sections.ld", patched_str)) |
| 66 | + env.Replace(LINKFLAGS=new_flags) |
17 | 67 | else: |
18 | | - # For other platforms, put it in last |
19 | | - env.Append(LINKFLAGS=[linker_script]) |
| 68 | + # Assume sections.ld will be built (ESP-IDF format); add a post-action to patch it |
| 69 | + # TODO: consider using ESP-IDF linker fragment (https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/linker-script-generation.html) |
| 70 | + # For now, patch after building |
| 71 | + sections_ld = Path(env.subst("$BUILD_DIR")) / "sections.ld" |
| 72 | + def patch_sections_ld(target, source, env): |
| 73 | + inject_before_marker(sections_ld, "_rodata_end = ABSOLUTE(.);") |
| 74 | + env.AddPostAction(str(sections_ld), patch_sections_ld) |
| 75 | + |
| 76 | +elif env.get("PIOPLATFORM") == "espressif8266": |
| 77 | + # The ESP8266 framework preprocesses eagle.app.v6.common.ld.h into |
| 78 | + # local.eagle.app.v6.common.ld in $BUILD_DIR/ld/ at build time. Register |
| 79 | + # a post-action on that generated file so the injection happens after |
| 80 | + # C-preprocessing but before linking. |
| 81 | + build_ld = Path(env.subst("$BUILD_DIR")) / "ld" / "local.eagle.app.v6.common.ld" |
| 82 | + |
| 83 | + def patch_esp8266_ld(target, source, env): |
| 84 | + inject_before_marker(build_ld, "_irom0_text_end = ABSOLUTE(.);") |
| 85 | + |
| 86 | + env.AddPostAction(str(build_ld), patch_esp8266_ld) |
0 commit comments