Memoize the skip decision, gate the serial rejection, fix a stale comment

The macro drift warning now prints once per run; the serial --device
rejection only applies where the flag does, so other platforms keep
their ignore and warn behavior; the added lines comment points at
idf_macro_matches instead of a removed module.
This commit is contained in:
J. Nick Koston
2026-09-30 08:50:29 +02:00
parent f4f303a589
commit 089b71d0c2
5 changed files with 23 additions and 6 deletions
+6 -2
View File
@@ -1770,10 +1770,14 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
if CORE.skip_bootloader and any(
device.startswith(("/dev/", "COM")) for device in (args.device or [])
if (
CORE.skip_bootloader
and CORE.is_esp32
and CORE.using_toolchain_esp_idf
and any(device.startswith(("/dev/", "COM")) for device in (args.device or []))
):
# Fail before the compile: the result could never flash over serial.
# Elsewhere the flag is ignored, so serial stays fine there.
_LOGGER.error("--skip-bootloader builds cannot be flashed over serial")
return 1
exit_code = write_cpp(config)
+2 -2
View File
@@ -116,8 +116,8 @@ if(ESPHOME_SKIP_BOOTLOADER)
endif()
"""
# The lines the override adds to IDF's macro; the staleness tripwire in
# esphome.espidf.bootloader strips them before comparing with the live macro.
# The lines the override adds to IDF's macro; idf_macro_matches() below
# strips them before comparing with the live macro.
BOOTLOADER_OVERRIDE_ADDED_LINES = (
'set(CONFIG_APP_BUILD_BOOTLOADER "")',
"idf_build_get_property(idf_target IDF_TARGET)",
+9 -1
View File
@@ -40,6 +40,7 @@ class _CacheData:
paths: dict[str, tuple] = field(default_factory=dict)
env: dict[str, dict[str, str]] = field(default_factory=dict)
cmake_output: dict[Path, str] = field(default_factory=dict)
skip_bootloader: bool | None = None
cmake_tools: dict[Path, dict[str, Path]] = field(default_factory=dict)
@@ -298,7 +299,14 @@ def tree_skips_bootloader(build_dir: Path) -> bool:
def _skip_bootloader() -> bool:
"""Whether this tree should not build a bootloader at all."""
"""Whether this tree should not build a bootloader at all; per-run memo."""
cache = _cache()
if cache.skip_bootloader is None:
cache.skip_bootloader = _compute_skip_bootloader()
return cache.skip_bootloader
def _compute_skip_bootloader() -> bool:
if not CORE.skip_bootloader:
return False
from esphome.build_gen.espidf import idf_macro_matches
+5 -1
View File
@@ -1118,17 +1118,21 @@ def test_skip_bootloader_requires_flag_and_matching_macro(
assert toolchain._skip_bootloader() is False
CORE.skip_bootloader = True
toolchain._cache().skip_bootloader = None # decision is memoized per run
with (
patch.object(toolchain, "_get_idf_path", return_value=Path("/idf")),
patch.object(build_gen, "idf_macro_matches", return_value=True),
):
assert toolchain._skip_bootloader() is True
toolchain._cache().skip_bootloader = None
with (
patch.object(toolchain, "_get_idf_path", return_value=Path("/idf")),
patch.object(build_gen, "idf_macro_matches", return_value=False),
patch.object(build_gen, "idf_macro_matches", return_value=False) as mock_match,
caplog.at_level("WARNING"),
):
assert toolchain._skip_bootloader() is False
assert toolchain._skip_bootloader() is False
mock_match.assert_called_once() # the memo also dedupes the warning
assert "--skip-bootloader ignored" in caplog.text
+1
View File
@@ -6870,6 +6870,7 @@ def test_command_run_rejects_serial_device_with_skip_bootloader(
"""The compile could never be flashed over serial; fail before it runs."""
setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path, name="test")
CORE.skip_bootloader = True
CORE.toolchain = Toolchain.ESP_IDF
args = MockArgs(device=["/dev/ttyUSB0"])
with patch("esphome.__main__.write_cpp") as mock_write:
assert command_run(args, {}) == 1