From d348284d9e9ab844ff745e652145472a39f34ac5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Aug 2026 12:17:05 -0500 Subject: [PATCH 1/2] Hoist the manifest shape gate into a helper --- esphome/platformio/library.py | 39 ++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index a61f3880a3..bf9c323b84 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -445,6 +445,27 @@ def split_list_by_condition( return matched, non_matched +def _valid_manifest_shape(data: Any) -> bool: + """Whether the manifest has the dict shapes every backend dereferences. + + A bare json.load imposes no shape; validating once here means a + malformed third-party manifest fails by library name instead of a raw + TypeError/AttributeError in a backend. + """ + if not isinstance(data, dict): + return False + build = data.get("build", {}) + esphome_data = data.get(ESPHOME_DATA_KEY, {}) + return ( + isinstance(build, dict) + and isinstance(esphome_data, dict) + and isinstance(esphome_data.get(ESPHOME_DATA_LINK_FLAGS_KEY, []), list) + and isinstance(build.get("srcDir", ""), str) + and isinstance(build.get("includeDir", ""), str) + and isinstance(build.get("srcFilter", ""), (str, list)) + ) + + def check_library_data(data: dict, platform: str | None, framework: str): """ Check whether a library manifest is compatible with the target toolchain. @@ -1131,23 +1152,7 @@ def convert_libraries( f"library.properties in {source_dir}" ) - # A bare json.load imposes no shape; every backend dereferences - # these fields, so validate once here and name the library - malformed = not isinstance(component.data, dict) - if not malformed: - build = component.data.get("build", {}) - esphome_data = component.data.get(ESPHOME_DATA_KEY, {}) - malformed = ( - not isinstance(build, dict) - or not isinstance(esphome_data, dict) - or not isinstance( - esphome_data.get(ESPHOME_DATA_LINK_FLAGS_KEY, []), list - ) - or not isinstance(build.get("srcDir", ""), str) - or not isinstance(build.get("includeDir", ""), str) - or not isinstance(build.get("srcFilter", ""), (str, list)) - ) - if malformed: + if not _valid_manifest_shape(component.data): # Fail fast only for a library the user asked for; a defect # in an unrequested corner of the graph must not block the # build From 29c290166b3b458f4d72e5e42516af262f227b0e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 24 Aug 2026 12:20:14 -0500 Subject: [PATCH 2/2] Apply simplify pass: flatten the batch worker, drop a dead guard, skip the unread backoff probe --- esphome/espidf/framework.py | 2 +- esphome/framework_helpers.py | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/esphome/espidf/framework.py b/esphome/espidf/framework.py index eeebec76f3..179346e072 100644 --- a/esphome/espidf/framework.py +++ b/esphome/espidf/framework.py @@ -791,7 +791,7 @@ def _prefetch_idf_tool_archives( # 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) - if failures and len(failures) == len(entries): + if len(failures) == len(entries): # A systematic fault, not one flaky mirror: the resume # workaround (#17703) is off for this whole install _LOGGER.error( diff --git a/esphome/framework_helpers.py b/esphome/framework_helpers.py index fd3f3bd1b8..11d98986c9 100644 --- a/esphome/framework_helpers.py +++ b/esphome/framework_helpers.py @@ -776,16 +776,14 @@ def run_batch_downloads( try: fetch(checked) except (_BatchDownloadCancelled, Exception) as err: # noqa: BLE001 # pylint: disable=broad-exception-caught - # A cancelled job reports like a failure: an abandoned download - # must never read as completed if a caller sees the list after - # Ctrl-C - failure = (name, err) - else: - return None - # A bar-frame write failure must not displace the download error - with suppress(Exception): - tracker(0) - return failure + # The cancelled arm exists for the tracker rollback below; the + # batch re-raises the interrupt, so the list is never returned + # after Ctrl-C. A bar-frame write failure must not displace the + # download error. + with suppress(Exception): + tracker(0) + return (name, err) + return None ex = ThreadPoolExecutor(max_workers=max_workers) try: @@ -1316,11 +1314,13 @@ def download_from_mirrors( ) # Tick with the bytes already on disk so a combined bar holds # steady during the backoff instead of rewinding to zero - if f is not None: - done = f.tell() - else: - part = _part_path(path_target) - done = part.stat().st_size if part.is_file() else 0 + done = 0 + if progress is not None: + if f is not None: + done = f.tell() + else: + part = _part_path(path_target) + done = part.stat().st_size if part.is_file() else 0 _cancellable_sleep(delay, progress, done) # 4. Report every attempted URL if all mirrors failed. failures spans