mirror of
https://github.com/esphome/esphome.git
synced 2026-08-31 01:56:01 +00:00
Stamp the compile-DB freshness, defer the silent-component failure past the summary, mark every undecoded address, honor all-platform fixtures
This commit is contained in:
@@ -97,13 +97,19 @@ def run_compile(config: ConfigType, verbose: bool) -> int:
|
||||
# (an interrupted previous run may have rewritten the manifest without
|
||||
# regenerating the DB).
|
||||
compdb = build_dir / "compile_commands.json"
|
||||
compdb_stamp = build_dir / ".compile_commands.stamp"
|
||||
ninja_file = build_dir / "build.ninja"
|
||||
# Freshness rides a stamp: the DB itself is written through
|
||||
# write_file_if_changed (its mtime feeds get_idedata's cache), so a
|
||||
# regeneration with identical content would stay "stale" forever
|
||||
if (
|
||||
ninja_changed
|
||||
or not compdb.is_file()
|
||||
or compdb.stat().st_mtime < ninja_file.stat().st_mtime
|
||||
or not compdb_stamp.is_file()
|
||||
or compdb_stamp.stat().st_mtime < ninja_file.stat().st_mtime
|
||||
):
|
||||
_write_compile_commands(paths.ninja, build_dir, env)
|
||||
compdb_stamp.touch()
|
||||
|
||||
cmd = [str(paths.ninja)]
|
||||
if verbose:
|
||||
|
||||
@@ -22,6 +22,8 @@ import subprocess
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import write_file
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Everything idedata generation may raise after a successful link; idedata
|
||||
# is a bonus artifact, so consumers warn instead of failing the build
|
||||
IDEDATA_BEST_EFFORT_ERRORS = (
|
||||
@@ -57,8 +59,6 @@ def warn_if_idedata_missing(get_idedata: Callable[[], dict | None]) -> None:
|
||||
_LOGGER.warning("Idedata failure detail", exc_info=True)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# C++ translation-unit suffixes used to identify ESPHome source files.
|
||||
_CXX_SUFFIXES = (".cpp", ".cc")
|
||||
# Suffixes of input/output files that appear bare on the command line (and so
|
||||
|
||||
@@ -627,15 +627,17 @@ ESP8266_EXCEPTION_CODES = {
|
||||
_DECODE_WARNED_AT: dict[str, float] = {}
|
||||
|
||||
|
||||
def _warn_decode_problem(key: str, message: str, *args) -> None:
|
||||
def _warn_decode_problem(key: str, message: str, *args) -> bool:
|
||||
"""Warn, deduplicated briefly so a burst of stack-dump addresses warns
|
||||
once but a later dump warns again."""
|
||||
once but a later dump warns again; returns whether it warned so the
|
||||
caller can mark suppressed addresses individually."""
|
||||
now = time.monotonic()
|
||||
last = _DECODE_WARNED_AT.get(key)
|
||||
if last is not None and now - last < 30:
|
||||
return
|
||||
return False
|
||||
_DECODE_WARNED_AT[key] = now
|
||||
_LOGGER.warning(message, *args)
|
||||
return True
|
||||
|
||||
|
||||
def _decode_pc(config: ConfigType, addr: str) -> None:
|
||||
@@ -647,6 +649,8 @@ def _decode_pc(config: ConfigType, addr: str) -> None:
|
||||
_warn_decode_problem(
|
||||
str(path), "Cannot decode crash addresses: %s missing", path
|
||||
)
|
||||
# The detailed warning names no address, so mark each one
|
||||
_LOGGER.warning("Not decoded %s (toolchain file missing)", addr)
|
||||
return
|
||||
addr2line, elf = str(addr2line), str(elf)
|
||||
else:
|
||||
@@ -658,6 +662,7 @@ def _decode_pc(config: ConfigType, addr: str) -> None:
|
||||
"no-addr2line",
|
||||
"Cannot decode crash addresses: no addr2line or ELF in idedata",
|
||||
)
|
||||
_LOGGER.warning("Not decoded %s (no addr2line or ELF)", addr)
|
||||
return
|
||||
addr2line, elf = idedata.addr2line_path, idedata.firmware_elf_path
|
||||
command = [addr2line, "-pfiaC", "-e", elf, addr]
|
||||
@@ -667,10 +672,12 @@ def _decode_pc(config: ConfigType, addr: str) -> None:
|
||||
# Warn, not debug: a failing addr2line must be visible. The warning
|
||||
# is rate-limited across a dump, so mark every undecoded address
|
||||
# inline or the rest read as merely unmappable
|
||||
_warn_decode_problem(
|
||||
if not _warn_decode_problem(
|
||||
"addr2line-failed", "Could not decode crash address %s (%s)", addr, err
|
||||
)
|
||||
_LOGGER.warning("Not decoded %s (addr2line failed)", addr)
|
||||
):
|
||||
# The detailed warning already named this address; mark only
|
||||
# the addresses whose warning was rate-limited away
|
||||
_LOGGER.warning("Not decoded %s (addr2line failed)", addr)
|
||||
_LOGGER.debug("Caught exception for command %s", command, exc_info=1)
|
||||
return
|
||||
|
||||
|
||||
@@ -692,6 +692,9 @@ def _esp8266_native_path_or_file_trigger(files: list[str]) -> bool:
|
||||
# base_python_changed covers the top-level esphome/*.py modules the
|
||||
# native backend imports directly (framework_helpers, helpers, writer,
|
||||
# __main__); without it a change there would silently skip this job.
|
||||
# base_python_changed is deliberately broad (any top-level esphome/*.py)
|
||||
# as belt-and-braces while the backend is new; narrow it to the modules
|
||||
# the backend imports once the toolchain has soaked a few releases
|
||||
return base_python_changed(files) or _path_or_file_trigger(
|
||||
files, ESP8266_NATIVE_TRIGGER_FILES, ESP8266_NATIVE_TRIGGER_PATH_PREFIXES
|
||||
)
|
||||
|
||||
@@ -1086,8 +1086,11 @@ def test_components(
|
||||
# check is per platform: a component whose fixture exists only for other
|
||||
# platforms contributes nothing to this leg.
|
||||
def _has_platform_test(component: str) -> bool:
|
||||
# "all" fixtures build on every platform (mirrors the run loop)
|
||||
return any(
|
||||
not platform_filter or test.stem.split(".")[-1].startswith(platform_filter)
|
||||
not platform_filter
|
||||
or (suffix := test.stem.split(".")[-1]) == "all"
|
||||
or suffix.startswith(platform_filter)
|
||||
for test in all_tests.get(component, [])
|
||||
)
|
||||
|
||||
@@ -1214,19 +1217,21 @@ def test_components(
|
||||
toolchain=toolchain,
|
||||
)
|
||||
|
||||
silent: list[str] = []
|
||||
if fail_on_no_tests:
|
||||
# A green run that built nothing for a requested component (renamed
|
||||
# fixture, missing base file, version-suffix mismatch) must not pass
|
||||
# CI. Per component: an all-or-nothing check would let one silent
|
||||
# component hide behind the others. Opt-in: some legs (the esp32-ard
|
||||
# smoke subset) legitimately match nothing.
|
||||
# smoke subset) legitimately match nothing. Failing is deferred past
|
||||
# the summary so a real failure's reproduce commands still print.
|
||||
built = {c for r in test_results for c in r.components}
|
||||
if silent := [
|
||||
silent = [
|
||||
p for p in component_patterns if p and "*" not in p and p not in built
|
||||
]:
|
||||
]
|
||||
if silent:
|
||||
print(f"No tests ran for requested component(s): {', '.join(silent)}")
|
||||
return 1
|
||||
if not test_results:
|
||||
elif not test_results:
|
||||
print("No tests matched the requested components/platform")
|
||||
return 1
|
||||
|
||||
@@ -1261,7 +1266,7 @@ def test_components(
|
||||
if os.environ.get("GITHUB_STEP_SUMMARY"):
|
||||
write_github_summary(test_results, toolchain=toolchain)
|
||||
|
||||
if failed_results:
|
||||
if failed_results or silent:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
@@ -7125,7 +7125,8 @@ def test_upload_using_esptool_arduino_toolchain(
|
||||
mock_run_external_command_main: Mock,
|
||||
) -> None:
|
||||
"""The native ESP8266 Arduino toolchain flashes its factory image at
|
||||
0x0, dispatched through the platform's native_toolchain_module hook."""
|
||||
0x0, resolved from the toolchain-keyed backend table (deliberately not
|
||||
the platform hook: that import would break the upload fast path)."""
|
||||
setup_core(platform=PLATFORM_ESP8266, tmp_path=tmp_path, name="test")
|
||||
CORE.toolchain = Toolchain.ARDUINO
|
||||
from esphome.arduino8266 import toolchain as native
|
||||
|
||||
Reference in New Issue
Block a user