@@ -54,6 +54,8 @@ def flash_tasks(
5454):
5555 """Flash every entry in ``tasks`` and return the updated boards."""
5656
57+ attempted_backend_downloads : set [tuple [str , str , str , str , bool ]] = set ()
58+
5759 def _pick_backend_compatible_firmware (task , fw_info ):
5860 """Pick a firmware image matching the explicit backend's supported formats."""
5961 if fw_info is None :
@@ -89,34 +91,88 @@ def _pick_backend_compatible_firmware(task, fw_info):
8991 detected_board_id = f"{ board .board } -{ board .variant } " if board .variant else board .board
9092 board_ids = [getattr (fw_info , "board_id" , "" ), detected_board_id ]
9193
92- candidates = []
93- seen_files = set ()
94- for bid in board_ids :
95- if not bid :
96- continue
97- # First prefer exact port match, then broaden to any port.
98- for cand in find_downloaded_firmware (
99- board_id = bid ,
100- version = fw_info .version ,
101- port = board .port ,
102- custom = bool (fw_info .custom ),
103- ) + find_downloaded_firmware (
104- board_id = bid ,
105- version = fw_info .version ,
106- port = "" ,
107- custom = bool (fw_info .custom ),
108- ):
109- if cand .firmware_file not in seen_files :
110- seen_files .add (cand .firmware_file )
111- candidates .append (cand )
112-
113- for cand in reversed (candidates ):
114- if Path (cand .firmware_file ).suffix .lower () in backend .supported_formats :
94+ def _find_supported_candidate ():
95+ candidates = []
96+ seen_files = set ()
97+ for bid in board_ids :
98+ if not bid :
99+ continue
100+ # First prefer exact port match, then broaden to any port.
101+ for cand in find_downloaded_firmware (
102+ board_id = bid ,
103+ version = fw_info .version ,
104+ port = board .port ,
105+ custom = bool (fw_info .custom ),
106+ ) + find_downloaded_firmware (
107+ board_id = bid ,
108+ version = fw_info .version ,
109+ port = "" ,
110+ custom = bool (fw_info .custom ),
111+ ):
112+ if cand .firmware_file not in seen_files :
113+ seen_files .add (cand .firmware_file )
114+ candidates .append (cand )
115+
116+ for cand in reversed (candidates ):
117+ if Path (cand .firmware_file ).suffix .lower () in backend .supported_formats :
118+ return cand
119+ return None
120+
121+ if candidate := _find_supported_candidate ():
122+ log .info (
123+ f"Using { requested_name } compatible firmware { candidate .firmware_file } "
124+ f"instead of { fw_info .firmware_file } for { board .board } on { board .serialport } "
125+ )
126+ return candidate
127+
128+ # No local firmware matches backend-supported file types; try one
129+ # targeted download refresh before handing off to backend selection.
130+ download_key = (
131+ requested_name ,
132+ detected_board_id ,
133+ fw_info .version ,
134+ board .port or "" ,
135+ bool (fw_info .custom ),
136+ )
137+ if download_key not in attempted_backend_downloads :
138+ attempted_backend_downloads .add (download_key )
139+ try :
140+ from mpflash .download import download
141+ from mpflash .mpboard_id .alternate import alternate_board_names
142+
143+ log .info (
144+ f"No local { requested_name } firmware with suffix in "
145+ f"{ list (backend .supported_formats )} for { board .board } on { board .serialport } ; "
146+ "trying firmware download refresh"
147+ )
148+ download (
149+ ports = [board .port ] if board .port else [],
150+ boards = alternate_board_names (detected_board_id , board .port ),
151+ versions = [fw_info .version ],
152+ force = True ,
153+ clean = True ,
154+ )
155+ except Exception as exc : # noqa: BLE001 - fallback to original firmware below
156+ log .debug (
157+ f"Backend-compatible firmware refresh failed for { detected_board_id } "
158+ f"{ fw_info .version } : { exc } "
159+ )
160+
161+ if candidate := _find_supported_candidate ():
115162 log .info (
116- f"Using { requested_name } compatible firmware { cand .firmware_file } "
163+ f"Using downloaded { requested_name } compatible firmware { candidate .firmware_file } "
117164 f"instead of { fw_info .firmware_file } for { board .board } on { board .serialport } "
118165 )
119- return cand
166+ return candidate
167+
168+ raise MPFlashError (
169+ f"No firmware matching backend { requested_name !r} for "
170+ f"{ detected_board_id !r} { fw_info .version } . "
171+ f"Selected firmware is { fw_info .firmware_file !r} ({ current_suffix or '<none>' } ), "
172+ f"but { requested_name } supports { list (backend .supported_formats )} . "
173+ "A download refresh was attempted but no compatible firmware was found."
174+ )
175+
120176 return fw_info
121177
122178 flashed = []
@@ -174,12 +230,38 @@ def flash_mcu(
174230 :attr:`FlashContext.options` for the backend to consume.
175231 """
176232 requested = _resolve_backend_name (method )
177- try :
178- backend = select_backend (mcu , fw_file , requested_name = requested )
179- except MPFlashError :
180- raise
181- except Exception as e : # noqa: BLE001 - selection should never crash callers
182- raise MPFlashError (f"Failed to select flash backend: { e } " ) from e
233+ target_override = kwargs .get ("target_override" )
234+
235+ # When the user explicitly provides a pyOCD target override, skip
236+ # capability probing that depends on MCU metadata-derived target matching.
237+ if requested == "pyocd" and target_override :
238+ backend = get_backend ("pyocd" )
239+ if backend is None :
240+ raise MPFlashError ("Unknown flash method 'pyocd'." )
241+ platform = default_services .current_platform ()
242+ suffix = fw_file .suffix .lower ()
243+ if backend .supported_formats and suffix not in backend .supported_formats :
244+ raise MPFlashError (
245+ f"Backend 'pyocd' cannot flash { fw_file .name } : unsupported format { suffix or '<none>' } ."
246+ )
247+ if backend .supported_platforms and platform not in backend .supported_platforms :
248+ raise MPFlashError (
249+ f"Backend 'pyocd' does not run on { platform .value } ."
250+ )
251+ if not backend .is_available ():
252+ raise MPFlashError (
253+ "pyOCD is not installed (install with: uv sync --extra pyocd)."
254+ )
255+ log .info (
256+ f"Using explicit pyOCD target override '{ target_override } ' for { mcu .board_id or mcu .board } "
257+ )
258+ else :
259+ try :
260+ backend = select_backend (mcu , fw_file , requested_name = requested )
261+ except MPFlashError :
262+ raise
263+ except Exception as e : # noqa: BLE001 - selection should never crash callers
264+ raise MPFlashError (f"Failed to select flash backend: { e } " ) from e
183265
184266 log .debug (f"Using flash backend: { backend .name } for { mcu .board_id } " )
185267
0 commit comments