mirror of
https://github.com/esphome/esphome.git
synced 2026-08-29 01:03:29 +00:00
Pin board_build.flash_mode unconditionally, dispatch native backends by toolchain, report a skipped size summary
This commit is contained in:
+18
-14
@@ -1936,24 +1936,27 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
return run_multiple_configs(files, build_command)
|
||||
|
||||
|
||||
# Native build backend per toolchain; keep in sync with NATIVE_TOOLCHAINS
|
||||
# in esphome.const. Keyed by toolchain rather than a platform hook so the
|
||||
# serial upload/logs fast path never imports the platform component package
|
||||
# (see the esp32 variant comment in upload_using_esptool).
|
||||
_NATIVE_TOOLCHAIN_MODULES = {
|
||||
Toolchain.ESP_IDF: "esphome.espidf.toolchain",
|
||||
Toolchain.ARDUINO: "esphome.arduino8266.toolchain",
|
||||
}
|
||||
|
||||
|
||||
def _native_toolchain_module():
|
||||
"""The native build backend module for the resolved toolchain, via the
|
||||
target platform's ``native_toolchain_module`` hook."""
|
||||
"""The native build backend module for the resolved toolchain."""
|
||||
if not CORE.using_native_toolchain:
|
||||
# Both hooks return None for PlatformIO anyway; returning early
|
||||
# keeps the serial upload/logs fast path from importing the
|
||||
# platform component package (see the esp32 variant comment in
|
||||
# upload_using_esptool)
|
||||
return None
|
||||
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:
|
||||
if (module_path := _NATIVE_TOOLCHAIN_MODULES.get(CORE.toolchain)) is None:
|
||||
# A native toolchain missing from the table is a bug; degrading to
|
||||
# the PlatformIO path would build with the wrong backend
|
||||
raise EsphomeError(
|
||||
f"Platform {CORE.target_platform} resolved toolchain "
|
||||
f"'{CORE.toolchain.value}' but provides no native toolchain module"
|
||||
f"Toolchain '{CORE.toolchain.value}' has no native build backend module"
|
||||
)
|
||||
return native
|
||||
return importlib.import_module(module_path)
|
||||
|
||||
|
||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
@@ -2007,7 +2010,8 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
native_toolchain = _native_toolchain_module()
|
||||
if native_toolchain is None and not CORE.using_toolchain_platformio:
|
||||
_LOGGER.error(
|
||||
"analyze-memory is not supported with the '%s' toolchain",
|
||||
"analyze-memory is not supported with the '%s' toolchain; it "
|
||||
"requires a PlatformIO, ESP-IDF, or native Arduino build",
|
||||
CORE.toolchain.value if CORE.toolchain else "unresolved",
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -158,7 +158,10 @@ def run_compile(config: ConfigType, verbose: bool) -> int:
|
||||
_LOGGER.error("Build produced no %s", artifact)
|
||||
return 1
|
||||
|
||||
_print_size_summary(build_dir, paths)
|
||||
if not _print_size_summary(build_dir, paths):
|
||||
# The cause was already warned; name the consequence so a build
|
||||
# contributing no RAM/Flash metric is visible to CI harnesses
|
||||
_LOGGER.warning("Firmware size summary unavailable for this build")
|
||||
from esphome.build_helpers.idedata import IDEDATA_BEST_EFFORT_ERRORS
|
||||
|
||||
try:
|
||||
@@ -243,8 +246,8 @@ def _parse_app_size(build_dir: Path, paths: framework.InstalledPaths) -> int | N
|
||||
return app_size
|
||||
|
||||
|
||||
def _print_size_summary(build_dir: Path, paths: framework.InstalledPaths) -> None:
|
||||
"""Print the PlatformIO-shaped RAM/Flash lines.
|
||||
def _print_size_summary(build_dir: Path, paths: framework.InstalledPaths) -> bool:
|
||||
"""Print the PlatformIO-shaped RAM/Flash lines; False when skipped.
|
||||
|
||||
The exact shape (including the bar) is parsed by
|
||||
``script/ci_memory_impact_extract.py``; ``print_size_line`` matches it.
|
||||
@@ -264,10 +267,10 @@ def _print_size_summary(build_dir: Path, paths: framework.InstalledPaths) -> Non
|
||||
# The summary is a bonus artifact like idedata; a truncated
|
||||
# toolchain extraction must not discard an already-linked build
|
||||
_LOGGER.warning("Could not summarize firmware size: %s", err)
|
||||
return
|
||||
return False
|
||||
if result.returncode != 0:
|
||||
_LOGGER.warning("Could not summarize firmware size: %s", result.stderr)
|
||||
return
|
||||
return False
|
||||
sections: dict[str, int] = {}
|
||||
for line in result.stdout.splitlines():
|
||||
parts = line.split()
|
||||
@@ -284,17 +287,18 @@ def _print_size_summary(build_dir: Path, paths: framework.InstalledPaths) -> Non
|
||||
"Size output is missing section(s) %s; skipping the size summary",
|
||||
", ".join(sorted(missing)),
|
||||
)
|
||||
return
|
||||
return False
|
||||
# Resolve the flash budget before printing anything: a RAM line without
|
||||
# its Flash line would let CI's memory-impact extraction sum the two
|
||||
# metrics over different build counts (_parse_app_size already warned).
|
||||
app_size = _parse_app_size(build_dir, paths)
|
||||
if not app_size:
|
||||
return
|
||||
return False
|
||||
ram = sum(sections[s] for s in _RAM_SECTIONS)
|
||||
flash = sum(sections[s] for s in _FLASH_SECTIONS)
|
||||
print_size_line("RAM", ram, _MAX_RAM_SIZE)
|
||||
print_size_line("Flash", flash, app_size)
|
||||
return True
|
||||
|
||||
|
||||
# Sentinel: "resolve for me"; None is a real value meaning disabled.
|
||||
|
||||
@@ -3446,20 +3446,6 @@ def process_stacktrace(config, line, backtrace_state):
|
||||
return backtrace_state
|
||||
|
||||
|
||||
def native_toolchain_module():
|
||||
"""The native build backend for the resolved toolchain, if any.
|
||||
|
||||
Hook for ``__main__``'s shared dispatch (idedata, analyze_memory);
|
||||
esp32's crash decode still branches inline in _decode_pc. Same seam
|
||||
the esp8266 component provides.
|
||||
"""
|
||||
if not CORE.using_toolchain_esp_idf:
|
||||
return None
|
||||
from esphome.espidf import toolchain
|
||||
|
||||
return toolchain
|
||||
|
||||
|
||||
# gpio.cpp only implements ESP32InternalGPIOPin and its ISR helpers, which
|
||||
# are instantiated solely by the pin schema codegen (esp32_pin_to_code)
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_defines(
|
||||
|
||||
@@ -323,7 +323,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
def native_toolchain_module():
|
||||
"""The native build backend for the resolved toolchain, if any.
|
||||
|
||||
Hook for ``__main__``'s shared dispatch (idedata, analyze_memory).
|
||||
``__main__`` dispatches from its own toolchain-keyed table; this helper
|
||||
serves the component's internal callers.
|
||||
"""
|
||||
if not CORE.using_toolchain_arduino:
|
||||
return None
|
||||
@@ -476,10 +477,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
# implementation in the Arduino ESP8266 core.
|
||||
cg.add_build_flag("-Wl,--wrap=millis")
|
||||
|
||||
if use_platformio:
|
||||
cg.add_platformio_option(
|
||||
"board_build.flash_mode", config[CONF_BOARD_FLASH_MODE]
|
||||
)
|
||||
# Unconditional: the native build generator reads the same option,
|
||||
# keeping one source of truth
|
||||
cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE])
|
||||
|
||||
ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
cg.add_define(
|
||||
|
||||
@@ -564,11 +564,13 @@ NATIVE_ARDUINO_PIO_OPTIONS = frozenset({"board_build.f_cpu", "board_build.ldscri
|
||||
# that is stored rather than translated away. Consumed by the esp8266 native
|
||||
# backend (later in this chain) for its ignored-option warning; defined here
|
||||
# so it stays adjacent to the routing.
|
||||
# build_src_flags: set unconditionally by esp8266/__init__ (throw_stubs) and
|
||||
# read by the native generator; not user-routable, so not in the set above
|
||||
# build_src_flags and board_build.flash_mode: set unconditionally by
|
||||
# esp8266/__init__ and read by the native generator; not user-routable, so
|
||||
# not in the set above
|
||||
NATIVE_ARDUINO_CONSUMED_PIO_OPTIONS = NATIVE_ARDUINO_PIO_OPTIONS | {
|
||||
"lib_ignore",
|
||||
"build_src_flags",
|
||||
"board_build.flash_mode",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -500,12 +500,13 @@ def test_print_size_summary_missing_section_skips_summary(
|
||||
|
||||
def test_warn_ignored_platformio_options(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Component-added options the native build drops are warned by name;
|
||||
the honored ones (lib_ignore, f_cpu, ldscript, build_src_flags) stay
|
||||
quiet."""
|
||||
the honored ones (lib_ignore, f_cpu, ldscript, build_src_flags,
|
||||
flash_mode) stay quiet."""
|
||||
CORE.platformio_options = {
|
||||
"board_build.ldscript": "eagle.flash.4m2m.ld",
|
||||
"board_build.f_cpu": "160000000L",
|
||||
"board_build.filesystem": "littlefs",
|
||||
"board_build.flash_mode": "dio",
|
||||
"build_src_flags": "-include throw_stubs.h",
|
||||
"lib_ignore": ["Updater"],
|
||||
"upload_speed": "460800",
|
||||
@@ -517,6 +518,7 @@ def test_warn_ignored_platformio_options(caplog: pytest.LogCaptureFixture) -> No
|
||||
assert "board_build.f_cpu is ignored" not in caplog.text
|
||||
assert "lib_ignore" not in caplog.text
|
||||
assert "build_src_flags" not in caplog.text
|
||||
assert "flash_mode" not in caplog.text
|
||||
# Component-added upload_speed never gets read under the native
|
||||
# toolchain, so it must warn
|
||||
assert "upload_speed" in caplog.text
|
||||
|
||||
@@ -7390,12 +7390,15 @@ def test_compile_program_espidf_idedata_none_warns(
|
||||
assert "No idedata was generated" in caplog.text
|
||||
|
||||
|
||||
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."""
|
||||
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"):
|
||||
def test_native_toolchain_module_missing_backend_raises(tmp_path: Path) -> None:
|
||||
"""A native toolchain missing from the backend table is a bug and must
|
||||
fail, not silently degrade to the PlatformIO path."""
|
||||
setup_core(platform=PLATFORM_ESP8266, tmp_path=tmp_path, name="test_device")
|
||||
CORE.toolchain = Toolchain.ARDUINO
|
||||
with (
|
||||
patch.dict(main._NATIVE_TOOLCHAIN_MODULES, clear=True),
|
||||
pytest.raises(EsphomeError, match="no native build backend"),
|
||||
):
|
||||
_native_toolchain_module()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user