Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer

This commit is contained in:
J. Nick Koston
2026-08-21 09:58:44 -05:00
9 changed files with 154 additions and 53 deletions
+4 -2
View File
@@ -2739,8 +2739,10 @@ def run_esphome(argv):
)
# An explicit CLI toolchain must run the per-platform validators; the
# cache was validated under whatever the last compile used. Only the
# read is gated: the refresh below may still save the freshly
# validated config, and its sidecar records the resolved toolchain.
# read is gated: the refresh below still saves the freshly validated
# config. The sidecar is only written when none exists; a
# compile-written one keeps the compile's toolchain (the firmware on
# disk was built by it), which upload/logs then restore.
cache_read_eligible = cache_write_eligible and args.toolchain is None
if cache_read_eligible:
from esphome.compiled_config import load_compiled_config
+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