diff --git a/esphome/espidf/framework.py b/esphome/espidf/framework.py index afb58611c7..adb5c41ec9 100644 --- a/esphome/espidf/framework.py +++ b/esphome/espidf/framework.py @@ -21,6 +21,7 @@ from esphome.build_helpers.tools_cache import IDF_TOOLS_CACHE, tools_cache_path from esphome.core import Version from esphome.framework_helpers import ( PathType, + _failure_reason, archive_extract_all, create_venv, download_from_mirrors, @@ -779,7 +780,8 @@ def _prefetch_idf_tool_archives( ], ) for name, e in failures: - _LOGGER.warning("Could not prefetch %s: %s", name, e) + # _failure_reason: a message-less exception must not log blank + _LOGGER.warning("Could not prefetch %s: %s", name, _failure_reason(e)) _LOGGER.debug("Prefetch failure detail", exc_info=e) except Exception as e: # noqa: BLE001 # pylint: disable=broad-exception-caught # The installer downloads anything missing itself; never let the diff --git a/esphome/framework_helpers.py b/esphome/framework_helpers.py index f1353939b0..1a128bb12b 100644 --- a/esphome/framework_helpers.py +++ b/esphome/framework_helpers.py @@ -2,7 +2,7 @@ from collections.abc import Callable, Iterable, Iterator from concurrent.futures import ThreadPoolExecutor -from contextlib import ExitStack, contextmanager +from contextlib import ExitStack, contextmanager, suppress import hashlib import io import json @@ -770,14 +770,15 @@ def run_batch_downloads( header: str, jobs: list[tuple[str, int, Callable[[Callable[[int], None]], None]]], max_workers: int = BATCH_DOWNLOAD_WORKERS, -) -> list[tuple[str, Exception]]: +) -> list[tuple[str, BaseException]]: """Run ``(name, size, fetch)`` download jobs concurrently under one bar. Each ``fetch(tracker)`` reports absolute byte counts; the bar total is the sum of the sizes. Failures are returned after the bar is done so warnings never land on its row. Ctrl-C drops queued jobs and aborts in-flight ones at their next progress tick or backoff boundary (a - parked socket read defers that by its timeout); resumable destinations + parked socket read defers that by its timeout, and an in-progress + archive extraction runs to completion); resumable destinations (``download_with_resume``) keep their fetched ``.part`` bytes. ``jobs`` must be non-empty. """ @@ -786,7 +787,7 @@ def run_batch_downloads( def _run( name: str, fetch: Callable[[Callable[[int], None]], None] - ) -> tuple[str, Exception] | None: + ) -> tuple[str, BaseException] | None: tracker = progress.tracker() def checked(done: int) -> None: @@ -799,12 +800,15 @@ def run_batch_downloads( except _BatchDownloadCancelled as err: # Reported like a failure: an abandoned job must never read as # a completed download if a caller sees the list after Ctrl-C - tracker(0) - return (name, err) + failure = (name, err) except Exception as err: # noqa: BLE001 # pylint: disable=broad-exception-caught + failure = (name, err) + else: + return None + # A bar-frame write failure must not displace the download error + with suppress(Exception): tracker(0) - return (name, err) - return None + return failure ex = ThreadPoolExecutor(max_workers=max_workers) try: diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index e0c9d8c1f4..787d7c76dd 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -32,6 +32,7 @@ from urllib.request import url2pathname from esphome import git from esphome.core import CORE, EsphomeError, Library from esphome.framework_helpers import ( + _failure_reason, archive_extract_all, download_from_mirrors, rmdir, @@ -954,7 +955,9 @@ def _prefetch_wave( The walk's own ``download()`` stays authoritative; duplicate URLs prefetch once so two threads never share a cache directory. Archives whose size the registry did not report are left to the sequential - loop, whose per-file bars don't interleave. + loop, whose per-file bars don't interleave. A node a sibling in the + same wave supersedes has its archive fetched in vain (knowing better + would need the manifests being downloaded). """ try: components: list[ConvertedLibrary] = [] @@ -997,7 +1000,9 @@ def _prefetch_wave( for name, err in failures: # The sequential call below retries and raises the real error _LOGGER.warning( - "Prefetch of %s failed (retrying sequentially): %s", name, err + "Prefetch of %s failed (retrying sequentially): %s", + name, + _failure_reason(err), ) _LOGGER.debug("Prefetch failure detail", exc_info=err) except Exception as err: # noqa: BLE001 # pylint: disable=broad-exception-caught