fix: detect flash failure from output even when returncode is 0

program_flash can return exit code 0 while outputting errors like
'uboot failed' or 'cannot open'. Previously only returncode was
checked, so these silent failures caused the workflow to continue
past a failed erase/program step.

Now scans combined stdout+stderr for failure keywords
(uboot, failed, cannot, abort, invalid, unsupported, timeout)
and returns success=False. Also checks returncode != 0 as fallback.
This commit is contained in:
2026-06-11 18:00:49 +08:00
parent 4453fc8e12
commit 4ca15c1190
+60 -8
View File
@@ -10,6 +10,7 @@ Compatible with Xilinx 2018.3 — 2023.2.
from __future__ import annotations
import binascii
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
@@ -35,6 +36,27 @@ ProgressCallback = Callable[[str, str], None] | None
# ── Helpers ─────────────────────────────────────────────────────────
# Keywords in program_flash output that indicate failure even with rc=0
_FAIL_KEYWORDS = [
"uboot", "u-boot", "failed", "cannot", "abort",
"invalid", "unsupported", "timeout",
]
def _output_has_error(output: str) -> str:
"""Check combined stdout+stderr for known failure keywords.
Returns the first matching line, or empty string if clean.
"""
lowered = output.lower()
for kw in _FAIL_KEYWORDS:
if kw in lowered:
# Find the actual line
for line in output.splitlines():
if kw in line.lower():
return line.strip()[:200]
return ""
def _run_command(
cmd: list[str],
@@ -167,12 +189,27 @@ def wipe_flash(
callback=callback,
)
success = result.returncode == 0
combined = result.stdout + result.stderr
err_line = _output_has_error(combined)
if err_line:
return FlashResult(
step="wipe",
success=False,
message=f"Flash erase failed: {err_line}",
output=combined[:4000],
)
if result.returncode != 0:
return FlashResult(
step="wipe",
success=False,
message=f"Flash erase failed (exit {result.returncode})",
output=combined[:4000],
)
return FlashResult(
step="wipe",
success=success,
message="Flash erased successfully" if success else "Flash erase failed",
output=(result.stdout + result.stderr)[:4000],
success=True,
message="Flash erased successfully",
output=combined[:4000],
)
except subprocess.TimeoutExpired:
return FlashResult(step="wipe", success=False, message="Flash erase timed out")
@@ -252,12 +289,27 @@ def program_flash_bin(
try:
result = _run_command(cmd, timeout=config.step_timeouts.get("flash_program", 600), callback=callback)
success = result.returncode == 0
combined = result.stdout + result.stderr
err_line = _output_has_error(combined)
if err_line:
return FlashResult(
step="program",
success=False,
message=f"Flash programming failed: {err_line}",
output=combined[:4000],
)
if result.returncode != 0:
return FlashResult(
step="program",
success=False,
message=f"Flash programming failed (exit {result.returncode})",
output=combined[:4000],
)
return FlashResult(
step="program",
success=success,
message="Flash programmed successfully" if success else "Flash programming failed",
output=(result.stdout + result.stderr)[:4000],
success=True,
message="Flash programmed successfully",
output=combined[:4000],
)
except subprocess.TimeoutExpired:
return FlashResult(step="program", success=False, message="Flash programming timed out (600s)")