mirror of
https://github.com/esphome/esphome.git
synced 2026-09-28 15:30:21 +00:00
[nrf52] Run analyze-memory on the sdk-nrf toolchain (#19730)
This commit is contained in:
+7
-7
@@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Protocol
|
||||
# cause them to be loaded before external components are processed, resulting
|
||||
# in the built-in version being used instead of the external component one.
|
||||
from esphome import const, platform_hooks
|
||||
from esphome.build_helpers.native import native_backend
|
||||
from esphome.build_helpers.native import analysis_backend, native_backend
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
ARGUMENT_HELP_DEVICE,
|
||||
@@ -2015,8 +2015,8 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
from esphome.analyze_memory.ram_strings import RamStringsAnalyzer
|
||||
|
||||
# Refuse an unsupported toolchain before paying for a full compile
|
||||
native_toolchain = native_backend()
|
||||
if native_toolchain is None and not CORE.using_toolchain_platformio:
|
||||
analysis_toolchain = analysis_backend()
|
||||
if analysis_toolchain is None and not CORE.using_toolchain_platformio:
|
||||
_LOGGER.error(
|
||||
"analyze-memory is not supported with the '%s' toolchain on %s; "
|
||||
"re-run with --toolchain platformio",
|
||||
@@ -2036,9 +2036,9 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
|
||||
# Get idedata for analysis
|
||||
idedata = None
|
||||
if native_toolchain is not None:
|
||||
objdump = native_toolchain.get_objdump_path()
|
||||
readelf = native_toolchain.get_readelf_path()
|
||||
if analysis_toolchain is not None:
|
||||
objdump = analysis_toolchain.get_objdump_path()
|
||||
readelf = analysis_toolchain.get_readelf_path()
|
||||
for tool in (objdump, readelf):
|
||||
if not tool.is_file():
|
||||
# The analyzer would silently fall back to host
|
||||
@@ -2052,7 +2052,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
objdump_path = str(objdump)
|
||||
readelf_path = str(readelf)
|
||||
|
||||
firmware_elf = native_toolchain.get_elf_path()
|
||||
firmware_elf = analysis_toolchain.get_elf_path()
|
||||
if not firmware_elf.is_file():
|
||||
# The analyzer swallows tool failures, so a missing ELF would
|
||||
# produce an exit-0 zeroed report
|
||||
|
||||
@@ -32,3 +32,21 @@ def native_backend() -> ModuleType | None:
|
||||
f"module for platform {CORE.target_platform}"
|
||||
)
|
||||
return importlib.import_module(module_path)
|
||||
|
||||
|
||||
# Binutils and the linked image for memory analysis, for toolchains that build
|
||||
# without PlatformIO but have no native build backend (which supplies them)
|
||||
ANALYSIS_TOOLCHAIN_MODULES = {
|
||||
("nrf52", Toolchain.SDK_NRF): "esphome.components.nrf52.toolchain",
|
||||
}
|
||||
|
||||
|
||||
def analysis_backend() -> ModuleType | None:
|
||||
"""The module giving objdump, readelf and the ELF of a non-PlatformIO build.
|
||||
|
||||
None means PlatformIO's idedata supplies them (or nothing can).
|
||||
"""
|
||||
if (native := native_backend()) is not None:
|
||||
return native
|
||||
module_path = ANALYSIS_TOOLCHAIN_MODULES.get((CORE.target_platform, CORE.toolchain))
|
||||
return importlib.import_module(module_path) if module_path else None
|
||||
|
||||
@@ -80,6 +80,17 @@ def _get_toolchain_path(version: str) -> Path:
|
||||
return get_sdk_nrf_tools_path() / "toolchains" / version
|
||||
|
||||
|
||||
def toolchain_tool(name: str) -> Path:
|
||||
"""Path to one of the pinned Zephyr SDK's tools (objdump, readelf, ...).
|
||||
|
||||
The single owner of the ``arm-zephyr-eabi/bin/arm-zephyr-eabi-<name>``
|
||||
layout and the Windows suffix.
|
||||
"""
|
||||
suffix = ".exe" if os.name == "nt" else ""
|
||||
bin_path = _get_toolchain_path(TOOLCHAIN_VERSION) / "arm-zephyr-eabi" / "bin"
|
||||
return bin_path / f"arm-zephyr-eabi-{name}{suffix}"
|
||||
|
||||
|
||||
_SITECUSTOMIZE = """\
|
||||
import os, stat, shutil
|
||||
_orig = shutil.rmtree
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Binutils and the linked image of an nRF52 sdk-nrf build, for memory analysis."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
|
||||
from esphome.core import CORE
|
||||
|
||||
from .framework import toolchain_tool
|
||||
|
||||
|
||||
def get_objdump_path() -> Path:
|
||||
return toolchain_tool("objdump")
|
||||
|
||||
|
||||
def get_readelf_path() -> Path:
|
||||
return toolchain_tool("readelf")
|
||||
|
||||
|
||||
def get_elf_path() -> Path:
|
||||
"""The linked Zephyr image, at the layout the configured SDK version writes.
|
||||
|
||||
Chosen by version rather than by probing so a leftover image from another
|
||||
SDK layout is never analyzed in place of the current build.
|
||||
"""
|
||||
if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] < cv.Version(2, 9, 2):
|
||||
return CORE.relative_pioenvs_path(CORE.name, "zephyr", "zephyr.elf")
|
||||
return CORE.relative_pioenvs_path(CORE.name, "zephyr", "zephyr", "zephyr.elf")
|
||||
@@ -7489,6 +7489,8 @@ def test_command_idedata_arduino_no_build_errors(tmp_path: Path) -> None:
|
||||
[
|
||||
(PLATFORM_ESP8266, Toolchain.ARDUINO, "esphome.arduino8266.toolchain"),
|
||||
(PLATFORM_ESP32, Toolchain.ESP_IDF, "esphome.espidf.toolchain"),
|
||||
# No native build backend, but its binutils and ELF are known
|
||||
(PLATFORM_NRF52, Toolchain.SDK_NRF, "esphome.components.nrf52.toolchain"),
|
||||
],
|
||||
)
|
||||
def test_command_analyze_memory_native_toolchains(
|
||||
@@ -7708,7 +7710,12 @@ def test_command_analyze_memory_unsupported_toolchain(
|
||||
mock_write_cpp.return_value = 0
|
||||
mock_compile_program.return_value = 0
|
||||
|
||||
result = command_analyze_memory(MockArgs(), {CONF_ESPHOME: {CONF_NAME: "t"}})
|
||||
# Every toolchain has analysis hooks today; drop sdk-nrf's to stand in for
|
||||
# one that does not
|
||||
with patch.dict(
|
||||
"esphome.build_helpers.native.ANALYSIS_TOOLCHAIN_MODULES", clear=True
|
||||
):
|
||||
result = command_analyze_memory(MockArgs(), {CONF_ESPHOME: {CONF_NAME: "t"}})
|
||||
|
||||
assert result == 1
|
||||
assert "analyze-memory is not supported" in caplog.text
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Tests for the nRF52 sdk-nrf memory analysis hooks."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.nrf52 import toolchain
|
||||
from esphome.components.nrf52.framework import TOOLCHAIN_VERSION, get_sdk_nrf_tools_path
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def nrf52_build(tmp_path: Path) -> Path:
|
||||
CORE.name = "test_device"
|
||||
CORE.build_path = tmp_path / "build" / "test_device"
|
||||
return CORE.build_path / ".pioenvs" / "test_device" / "zephyr"
|
||||
|
||||
|
||||
def _set_sdk_version(version: cv.Version) -> None:
|
||||
CORE.data.setdefault(KEY_CORE, {})[KEY_FRAMEWORK_VERSION] = version
|
||||
|
||||
|
||||
def test_binutils_come_from_the_pinned_zephyr_sdk() -> None:
|
||||
bin_path = (
|
||||
get_sdk_nrf_tools_path()
|
||||
/ "toolchains"
|
||||
/ TOOLCHAIN_VERSION
|
||||
/ "arm-zephyr-eabi"
|
||||
/ "bin"
|
||||
)
|
||||
# Windows hosts get the .exe binaries (CI covers both)
|
||||
suffix = ".exe" if os.name == "nt" else ""
|
||||
assert toolchain.get_objdump_path() == bin_path / f"arm-zephyr-eabi-objdump{suffix}"
|
||||
assert toolchain.get_readelf_path() == bin_path / f"arm-zephyr-eabi-readelf{suffix}"
|
||||
|
||||
|
||||
def test_elf_uses_the_nested_layout_from_sdk_2_9_2(nrf52_build: Path) -> None:
|
||||
_set_sdk_version(cv.Version(2, 9, 2))
|
||||
# A flat image left by an older SDK must not be picked over the current
|
||||
# layout, so the choice follows the version rather than what exists
|
||||
(nrf52_build / "zephyr.elf").parent.mkdir(parents=True)
|
||||
(nrf52_build / "zephyr.elf").write_text("")
|
||||
assert toolchain.get_elf_path() == nrf52_build / "zephyr" / "zephyr.elf"
|
||||
|
||||
|
||||
def test_elf_uses_the_flat_layout_before_sdk_2_9_2(nrf52_build: Path) -> None:
|
||||
_set_sdk_version(cv.Version(2, 6, 1))
|
||||
nested = nrf52_build / "zephyr" / "zephyr.elf"
|
||||
nested.parent.mkdir(parents=True)
|
||||
nested.write_text("")
|
||||
assert toolchain.get_elf_path() == nrf52_build / "zephyr.elf"
|
||||
Reference in New Issue
Block a user