Name malformed registry elements, parse ESPHOME_CCACHE_ENABLE strictly, require a package layout check

Registry version and file entries that are not dicts now raise the same
Unexpected-response error as the other shape guards instead of an
AttributeError traceback. install_package rejects an empty expect so
every install is layout-validated before the marker is written.
ESPHOME_CCACHE_ENABLE values outside 1/true/yes/on and 0/false/no/off
warn and are treated as unset instead of bool(str) flipping them to
enabled. The strip_win_long_path_prefix test moved next to the function
in test_framework_helpers, and a stale comment naming its old module is
fixed.
This commit is contained in:
J. Nick Koston
2026-08-21 09:58:16 -05:00
parent 225c62dbf1
commit 0fa081cd80
7 changed files with 148 additions and 49 deletions
+17 -5
View File
@@ -54,16 +54,28 @@ def resolve_ccache_path() -> str | None:
Shared policy for every backend: on by default when a runnable ccache is
on PATH, ``ESPHOME_CCACHE_ENABLE=0`` opts out, and an explicit ``=1``
warns when no binary is found and skips the runnability probe. The
warns when no binary is found and skips the runnability probe;
any other value warns and is treated as unset. The
Windows extended-length prefix is stripped before probing so the probe
validates the exact string the build will execute (#18399).
"""
import shutil
from esphome.helpers import get_bool_env
explicit = "ESPHOME_CCACHE_ENABLE" in os.environ
if explicit and not get_bool_env("ESPHOME_CCACHE_ENABLE"):
# Strict parse: bool(str) truthiness would flip "no"/"off" to enabled
# and silently skip the probe
raw = os.environ.get("ESPHOME_CCACHE_ENABLE")
explicit: bool | None = None
if raw is not None:
lowered = raw.strip().lower()
if lowered in ("1", "true", "yes", "on"):
explicit = True
elif lowered in ("0", "false", "no", "off"):
explicit = False
else:
_LOGGER.warning(
"Ignoring unrecognized ESPHOME_CCACHE_ENABLE=%r; use 1 or 0", raw
)
if explicit is False:
return None
ccache = shutil.which("ccache")
if ccache is None:
+13
View File
@@ -79,6 +79,10 @@ def registry_download(package: str, version: str) -> tuple[str, str, int | None]
f"Unexpected package registry response for {package}: {str(data)[:200]}"
)
for ver in versions:
if not isinstance(ver, dict):
raise EsphomeError(
f"Unexpected package registry response for {package}: {str(data)[:200]}"
)
if ver.get("name") != version:
continue
files = ver.get("files")
@@ -87,6 +91,11 @@ def registry_download(package: str, version: str) -> tuple[str, str, int | None]
f"Unexpected package registry response for {package}: {str(ver)[:200]}"
)
for file in files:
if not isinstance(file, dict):
raise EsphomeError(
f"Unexpected package registry response for {package}: "
f"{str(ver)[:200]}"
)
# Only a MISSING key means "any system"; an explicitly empty
# list must not match (a wrong-architecture download would be
# cached as a good install). A bare string would make ``in`` a
@@ -133,6 +142,10 @@ def install_package(
substitution) is trusted as configured. ``downloads_dir`` holds the
archive between runs so an interrupted download resumes.
"""
if not expect:
# Layout validation before marker.touch() is the only guard against
# caching a truncated mirror archive as a good install
raise ValueError("install_package requires a non-empty expect")
marker = dest / ".esphome_extracted"
if marker.is_file():
return