diff --git a/.github/actions/cache-arduino8266/action.yml b/.github/actions/cache-arduino8266/action.yml index 917b7aa077..a9fff84d6c 100644 --- a/.github/actions/cache-arduino8266/action.yml +++ b/.github/actions/cache-arduino8266/action.yml @@ -10,11 +10,9 @@ runs: using: composite steps: - name: Resolve the native toolchain cache key - # The versions are pinned in code, not in a hashable file, so resolve - # them for the key (actions/cache never overwrites a key, so a bump - # must change it). Assignment form so errexit catches a resolver - # failure; a nested $(...) inside echo would silently yield a - # degenerate key. + # Versions are pinned in code, not a hashable file; resolve them so a + # bump changes the cache key. Assignment form so errexit catches a + # resolver failure. id: version shell: bash run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76bace8ecf..8f9e5dabab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1166,10 +1166,7 @@ jobs: - determine-jobs if: github.event_name == 'pull_request' && needs.determine-jobs.outputs.esp8266-native == 'true' env: - # Comma-joined subset of the native-ESP8266 representative component list, - # computed by script/determine-jobs.py (esp8266_native_components_to_test). - # Single source of truth -- the full list lives in - # script/determine-jobs.py::ESP8266_NATIVE_TEST_COMPONENTS. + # Computed by script/determine-jobs.py (ESP8266_NATIVE_TEST_COMPONENTS) TEST_COMPONENTS: ${{ needs.determine-jobs.outputs.esp8266-native-components }} ESPHOME_ARDUINO8266_PREFIX: ~/.esphome-arduino8266 steps: diff --git a/esphome/__main__.py b/esphome/__main__.py index db0eaa5a7a..ef5f637d93 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -814,9 +814,8 @@ def write_cpp_file() -> int: espidf.write_project() elif CORE.using_native_toolchain: - # Native builds generate their project at compile time (the ESP8266 - # ninja build needs the downloaded framework); nothing to write here, - # and never a platformio.ini (must agree with _add_platformio_options) + # Native builds generate their project at compile time; never write + # a platformio.ini pass else: from esphome.build_gen import platformio @@ -874,9 +873,6 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int: # failure must not fail a successful build. _LOGGER.warning("Could not generate idedata: %s", err) elif CORE.using_native_toolchain: - # A resolved native toolchain must be claimed by its platform hook - # or a branch above; falling through would build a mis-configured - # PlatformIO project raise EsphomeError( f"Toolchain '{CORE.toolchain.value}' resolved but no platform " "backend claimed the build" @@ -1943,18 +1939,12 @@ def command_update_all(args: ArgsProtocol) -> int | None: def _native_toolchain_module(): - """The native build backend module for the resolved toolchain, if any. - - Platform-owned toolchains resolve through the target platform's - ``native_toolchain_module`` hook (the same per-platform module seam - ``compile_program`` uses), so shared dispatch never names a backend. - """ + """The native build backend module for the resolved toolchain, via the + target platform's ``native_toolchain_module`` hook.""" module = importlib.import_module("esphome.components." + CORE.target_platform) get_native = getattr(module, "native_toolchain_module", None) native = get_native() if get_native is not None else None if native is None and CORE.using_native_toolchain: - # A missing/renamed hook must fail, not silently degrade the native - # build's tooling to the PlatformIO path raise EsphomeError( f"Platform {CORE.target_platform} resolved toolchain " f"'{CORE.toolchain.value}' but provides no native toolchain module" diff --git a/esphome/arduino8266/toolchain.py b/esphome/arduino8266/toolchain.py index 74e913fd23..6ae7b9c277 100644 --- a/esphome/arduino8266/toolchain.py +++ b/esphome/arduino8266/toolchain.py @@ -26,11 +26,9 @@ _MAX_RAM_SIZE = 81920 def _warn_ignored_platformio_options() -> None: """Warn for component-added platformio options the native build drops. - The consumed set derives from the routing constant in core/config.py so - the two lists cannot drift. YAML-set upload_speed never reaches - CORE.platformio_options under the native toolchain (it is read from the - raw config at upload time), so anything unconsumed came from a - component and genuinely is dropped. + The consumed set derives from NATIVE_ARDUINO_PIO_OPTIONS so the two + lists cannot drift; YAML upload_speed never reaches + CORE.platformio_options here. """ from esphome.core.config import NATIVE_ARDUINO_PIO_OPTIONS @@ -85,10 +83,8 @@ def run_compile(config: ConfigType, verbose: bool) -> int: build_dir = get_build_dir() env = framework.get_build_env(paths.toolchain, ccache) - # The compile database is a pure function of build.ninja (no compilation - # involved), so regenerate it before the build: a failed build can then - # never leave a stale database behind. Skip the ninja spawn plus MBs of - # text on unchanged builds. + # Regenerate the compile DB before the build (a pure function of + # build.ninja); skip when unchanged. if ninja_changed or not (build_dir / "compile_commands.json").is_file(): _write_compile_commands(paths.ninja, build_dir, env) @@ -107,10 +103,8 @@ def run_compile(config: ConfigType, verbose: bool) -> int: try: idedata = get_idedata(ccache) except (EsphomeError, LookupError, OSError, RuntimeError, ValueError) as err: - # The firmware already built; idedata is a bonus artifact here. - # Broad on purpose: a vanished compiler (OSError), a failed include - # probe (RuntimeError), or a truncated/structurally odd compile DB - # (ValueError/LookupError) must not fail a successful build either. + # Broad on purpose: idedata is a bonus artifact; nothing here may + # fail a successful build. _LOGGER.warning("Could not generate idedata: %s", err) else: if idedata is None: diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 234550c48e..94505144d5 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -551,9 +551,8 @@ def run_compile(args, config: ConfigType) -> bool: # Called by writer.py def copy_files() -> None: - # Positive check, matching run_compile: only the arduino native backend - # skips the PlatformIO/SCons extra scripts (their logic lives in the - # build generator there) + # Native builds skip the PlatformIO extra scripts; the build generator + # carries their logic if CORE.using_toolchain_arduino: return dir = Path(__file__).parent @@ -616,12 +615,8 @@ _DECODE_WARNED_AT: dict[str, float] = {} def _warn_decode_problem(key: str, message: str, *args) -> None: - """Warn, deduplicated per stack dump but not per process. - - A dump decodes dozens of addresses in a burst; one warning per burst is - enough. A long-running dashboard must still warn on the next dump, so - the suppression expires instead of living for the process lifetime. - """ + """Warn, deduplicated briefly so a burst of stack-dump addresses warns + once but a later dump warns again.""" now = time.monotonic() last = _DECODE_WARNED_AT.get(key) if last is not None and now - last < 30: @@ -646,8 +641,6 @@ def _decode_pc(config, addr): idedata = toolchain.get_idedata(config) if not idedata.addr2line_path or not idedata.firmware_elf_path: - # Same visibility as the native branch: raw undecoded addresses - # with no stated reason are undiagnosable at default log level _warn_decode_problem( "no-addr2line", "Cannot decode crash addresses: no addr2line or ELF in idedata", @@ -658,9 +651,7 @@ def _decode_pc(config, addr): try: translation = subprocess.check_output(command, close_fds=False).decode().strip() except Exception as err: # noqa: BLE001 # pylint: disable=broad-except - # A present-but-failing addr2line (stale ELF, bad install) must be - # visible on either toolchain, matching the missing-tool warning - # above, and the cause must not need debug logging to see + # Warn, not debug: a failing addr2line must be visible _warn_decode_problem( "addr2line-failed", "Could not decode crash address %s (%s)", addr, err ) diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 43150895ae..4da68c8eb0 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -633,12 +633,8 @@ def should_run_esp32_platformio(branch: str | None = None) -> bool: return bool(esp32_platformio_components_to_test(branch)) -# Components tested by the native (PlatformIO-free) ESP8266 Arduino toolchain -# compile-test job. The regular component matrix builds esp8266 with the -# default platformio toolchain; this list is the `--toolchain arduino` smoke -# test, chosen to exercise the core, the bundled libraries (ESP8266WiFi, -# ESP8266mDNS, Wire, SPI, DNSServer, Hash), the converted registry libraries -# (ESPAsyncTCP/WebServer, AsyncMqttClient, NeoPixelBus), and the waveform path. +# The `--toolchain arduino` smoke-test set: covers the core, the bundled and +# converted registry libraries, and the waveform path. ESP8266_NATIVE_TEST_COMPONENTS = frozenset( { "esp8266", diff --git a/tests/unit_tests/components/esp8266/test_toolchain_validation.py b/tests/unit_tests/components/esp8266/test_toolchain_validation.py index 889f8d7666..f7ef786ea1 100644 --- a/tests/unit_tests/components/esp8266/test_toolchain_validation.py +++ b/tests/unit_tests/components/esp8266/test_toolchain_validation.py @@ -3,10 +3,12 @@ from __future__ import annotations from pathlib import Path +from types import SimpleNamespace from unittest.mock import patch import pytest +from esphome.components import esp8266 from esphome.components.esp8266 import ( ARDUINO_FRAMEWORK_SCHEMA, _resolve_toolchain, @@ -120,7 +122,6 @@ def test_decode_pc_native_missing_tools_warns_once( tmp_path: Path, caplog: pytest.LogCaptureFixture ) -> None: """A stack dump of many addresses produces one missing-tool warning.""" - from esphome.components import esp8266 esp8266._DECODE_WARNED_AT.clear() with ( @@ -145,9 +146,6 @@ def test_decode_pc_platformio_missing_tools_warns_once( """The PlatformIO branch reports a missing addr2line/ELF at the same warning level as the native one; raw undecoded addresses with no stated reason are undiagnosable at default log level.""" - from types import SimpleNamespace - - from esphome.components import esp8266 esp8266._DECODE_WARNED_AT.clear() CORE.toolchain = Toolchain.PLATFORMIO @@ -161,7 +159,6 @@ def test_decode_pc_platformio_missing_tools_warns_once( def test_resolve_toolchain_rejects_unsupported() -> None: """ESP8266 rejects a CLI toolchain it cannot serve, like every platform.""" - from esphome.components.esp8266 import _resolve_toolchain CORE.toolchain = Toolchain.SDK_NRF with pytest.raises(cv.Invalid, match="Unsupported toolchain 'sdk-nrf'"): diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 9f49533ef6..83fcb887ba 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -23,6 +23,7 @@ from esphome.__main__ import ( Purpose, _get_configured_xtal_freq, _make_crystal_freq_callback, + _native_toolchain_module, _redact_with_legacy_fallback, _resolve_network_devices, _split_network_devices, @@ -7341,8 +7342,6 @@ def test_compile_program_espidf_idedata_none_warns( def test_native_toolchain_module_missing_hook_raises(tmp_path: Path) -> None: """A resolved native toolchain whose platform lacks the hook is a bug and must fail, not silently degrade to the PlatformIO path.""" - from esphome.__main__ import _native_toolchain_module - setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path, name="test_device") CORE.toolchain = Toolchain.ARDUINO # esp32 provides no hook with pytest.raises(EsphomeError, match="no native toolchain module"):