diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index 389803c417..731f52c2a5 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -353,6 +353,12 @@ def _resolve_build_config(defines: dict[str, str]) -> _BuildConfig: "PIO_FRAMEWORK_ARDUINO_MMU_CUSTOM requires MMU_IRAM_SIZE and " "MMU_ICACHE_SIZE build flags" ) + for name in _MMU_SEGMENT_SIZE_NAMES: + # A bare -Dname would preprocess to len = 1 and fail far away + if "=" not in defines[name]: + raise EsphomeError( + f"{name} must be a hex literal (e.g. 0x8000), got (no value)" + ) for name, body in defines.items(): if not name.startswith("MMU_") or "=" not in body: # Valueless flags (MMU_IRAM_HEAP) are legitimate switches @@ -488,6 +494,12 @@ def _project_flags( elif tok.startswith("-l"): libs.append(tok[2:]) else: + if tok.startswith(_PLAIN_DRIVER_LINK_PREFIXES): + # Driver options with no -Wl, spelling; ld would reject them + raise EsphomeError( + f"Link flag {tok} in build_flags is not supported by the " + "native toolchain" + ) if tok in _PLAIN_LINKER_FLAGS or tok.startswith(_PLAIN_LINKER_PREFIXES): raise EsphomeError( f"Linker flag {tok} in build_flags is not routed to the " @@ -511,7 +523,9 @@ _PLAIN_LINKER_FLAGS = ( "-nostdlib", "-rdynamic", ) -_PLAIN_LINKER_PREFIXES = ("-T", "-Xlinker", "-fuse-ld=", "--specs=") +_PLAIN_LINKER_PREFIXES = ("-T", "-Xlinker") +# Driver options, not ld options: -Wl, has no equivalent for these +_PLAIN_DRIVER_LINK_PREFIXES = ("-fuse-ld=", "--specs=", "-specs=") def _stat_sig(path: Path) -> str: @@ -641,7 +655,13 @@ def generate_ld_scripts( except OSError as err: # A kept stale note would re-emit an obsolete diagnostic on # every cache hit; skip the stamp so -E re-derives the truth - _LOGGER.debug("Could not remove %s: %s", stderr_note, err) + _LOGGER.warning( + "Could not remove %s (%s); the linker script will " + "regenerate every build until it is removable; %s", + stderr_note, + err, + _CLEAN_HINT, + ) note_persisted = False if "SECTIONS" not in result.stdout: # A degenerate zero-exit run must not be stamped as a good cache diff --git a/tests/unit_tests/build_gen/test_arduino8266.py b/tests/unit_tests/build_gen/test_arduino8266.py index 057c451807..c98360a2a2 100644 --- a/tests/unit_tests/build_gen/test_arduino8266.py +++ b/tests/unit_tests/build_gen/test_arduino8266.py @@ -702,6 +702,24 @@ def test_generate_ld_scripts_unremovable_stale_note_vetoes_the_stamp( run3.assert_called_once() +@pytest.mark.parametrize("name", ["MMU_IRAM_SIZE", "MMU_ICACHE_SIZE"]) +def test_mmu_custom_valueless_segment_size_raises(name: str) -> None: + """A bare -Dname would preprocess to len = 1 and fail far away in ld.""" + other = "MMU_ICACHE_SIZE" if name == "MMU_IRAM_SIZE" else "MMU_IRAM_SIZE" + with pytest.raises(EsphomeError, match=f"{name} must be a hex literal"): + _resolve("-DPIO_FRAMEWORK_ARDUINO_MMU_CUSTOM", f"-D{name}", f"-D{other}=0x8000") + + +@pytest.mark.parametrize( + "flag", ["-fuse-ld=lld", "--specs=nano.specs", "-specs=nano.specs"] +) +def test_driver_link_flags_rejected_without_wl_advice(flag: str) -> None: + """No -Wl, spelling exists for these; the message must not suggest one.""" + CORE.build_flags = {flag, "-DFOO"} + with pytest.raises(EsphomeError, match="not supported by the native toolchain"): + arduino8266._project_flags(set(), arduino8266._lexed_build_flags()) + + def test_build_config_mmu_knob_with_raw_mmu_flag_raises() -> None: """A variant knob plus a raw MMU_* define would split the compile line from the linker script; refuse like the no-knob case."""