[analyze_memory] Attribute main.cpp setup()/loop() to esphome core (#16033)

This commit is contained in:
J. Nick Koston
2026-04-28 21:06:54 -05:00
committed by GitHub
parent c3bd38af77
commit 592486ae9a
2 changed files with 56 additions and 6 deletions
+13 -6
View File
@@ -793,8 +793,11 @@ class MemoryAnalyzer:
"""Scan ESPHome source object files to map extern "C" symbols to components. """Scan ESPHome source object files to map extern "C" symbols to components.
When no linker map file is available, this uses ``nm`` to scan ``.o`` files When no linker map file is available, this uses ``nm`` to scan ``.o`` files
under ``src/esphome/`` and build a symbol-to-component mapping. This catches under ``src/`` (including ``src/main.cpp.o`` and everything beneath
``extern "C"`` functions and other symbols that lack C++ namespace prefixes. ``src/esphome/``) and build a symbol-to-component mapping. This catches
``extern "C"`` functions, the ESPHome-generated ``setup()``/``loop()``
entry points in ``main.cpp``, and other symbols that lack C++ namespace
prefixes.
Skips scanning if ``_source_symbol_map`` was already populated by Skips scanning if ``_source_symbol_map`` was already populated by
``_parse_map_file()``. ``_parse_map_file()``.
@@ -806,12 +809,12 @@ class MemoryAnalyzer:
if obj_dir is None: if obj_dir is None:
return return
# Find ESPHome source object files # Scan all ESPHome-owned source object files: src/main.cpp.o and src/esphome/...
esphome_src_dir = obj_dir / "src" / "esphome" src_dir = obj_dir / "src"
if not esphome_src_dir.is_dir(): if not src_dir.is_dir():
return return
obj_files = sorted(esphome_src_dir.rglob("*.o")) obj_files = sorted(src_dir.rglob("*.o"))
if not obj_files: if not obj_files:
return return
@@ -1064,6 +1067,10 @@ class MemoryAnalyzer:
if component_name in self.external_components: if component_name in self.external_components:
return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}" return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}"
# ESPHome-generated entry point: src/main.cpp.o (contains setup()/loop())
if len(parts) >= 2 and parts[-2:] == ("src", "main.cpp.o"):
return _COMPONENT_CORE
# ESPHome core: src/esphome/core/... or src/esphome/... # ESPHome core: src/esphome/core/... or src/esphome/...
if "core" in parts and "esphome" in parts: if "core" in parts and "esphome" in parts:
return _COMPONENT_CORE return _COMPONENT_CORE
@@ -0,0 +1,43 @@
"""Tests for source-file-to-component attribution in memory analyzer."""
from unittest.mock import patch
from esphome.analyze_memory import MemoryAnalyzer
def _make_analyzer(external_components: set[str] | None = None) -> MemoryAnalyzer:
"""Create a MemoryAnalyzer with mocked dependencies."""
with patch.object(MemoryAnalyzer, "__init__", lambda self, *a, **kw: None):
analyzer = MemoryAnalyzer.__new__(MemoryAnalyzer)
analyzer.external_components = external_components or set()
analyzer._lib_hash_to_name = {}
return analyzer
def test_source_file_to_component_main_cpp_relative() -> None:
"""ESPHome-generated src/main.cpp.o (nm path form) attributes to core."""
analyzer = _make_analyzer()
assert analyzer._source_file_to_component("src/main.cpp.o") == "[esphome]core"
def test_source_file_to_component_main_cpp_pioenvs_path() -> None:
"""Linker map paths like .pioenvs/<env>/src/main.cpp.o attribute to core."""
analyzer = _make_analyzer()
result = analyzer._source_file_to_component(".pioenvs/drivewaygate/src/main.cpp.o")
assert result == "[esphome]core"
def test_source_file_to_component_esphome_core() -> None:
"""Sources under src/esphome/core/ attribute to core."""
analyzer = _make_analyzer()
result = analyzer._source_file_to_component("src/esphome/core/application.cpp.o")
assert result == "[esphome]core"
def test_source_file_to_component_known_component() -> None:
"""Known ESPHome components attribute to their component name."""
analyzer = _make_analyzer()
result = analyzer._source_file_to_component(
"src/esphome/components/wifi/wifi_component.cpp.o"
)
assert result == "[esphome]wifi"