From 6b2bc29e17593b5b018c10532261853725d642d2 Mon Sep 17 00:00:00 2001 From: Shun Date: Sat, 28 Mar 2026 15:11:17 -0700 Subject: [PATCH 01/10] changed brakelight hpp --- firmware/hexray/RSM/src/io/io_brakeLight.cpp | 1 + firmware/hexray/RSM/src/io/io_brakeLight.hpp | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/firmware/hexray/RSM/src/io/io_brakeLight.cpp b/firmware/hexray/RSM/src/io/io_brakeLight.cpp index af1d9f08a2..d39f574de6 100644 --- a/firmware/hexray/RSM/src/io/io_brakeLight.cpp +++ b/firmware/hexray/RSM/src/io/io_brakeLight.cpp @@ -1,4 +1,5 @@ #include "io_brakeLight.hpp" +#include "hw_gpios.hpp" namespace io::brakeLight { diff --git a/firmware/hexray/RSM/src/io/io_brakeLight.hpp b/firmware/hexray/RSM/src/io/io_brakeLight.hpp index e88d4dee97..6625803457 100644 --- a/firmware/hexray/RSM/src/io/io_brakeLight.hpp +++ b/firmware/hexray/RSM/src/io/io_brakeLight.hpp @@ -1,7 +1,5 @@ #pragma once -#include "hw_gpios.hpp" - namespace io::brakeLight { /* From 86d85bc5d048868c3ad1d95923e881ce20306476 Mon Sep 17 00:00:00 2001 From: Shun Date: Fri, 22 May 2026 13:30:05 -0700 Subject: [PATCH 02/10] added parallel to update ver1 --- scripts/canup/bootloader.py | 50 ++++++++++++++---- scripts/canup/update.py | 102 +++++++++++++++++++++++++++--------- 2 files changed, 118 insertions(+), 34 deletions(-) diff --git a/scripts/canup/bootloader.py b/scripts/canup/bootloader.py index bb112786ba..a7b7e13c3b 100644 --- a/scripts/canup/bootloader.py +++ b/scripts/canup/bootloader.py @@ -6,6 +6,8 @@ """ from typing import Callable, Optional +import queue +import threading import math import can import time @@ -55,6 +57,8 @@ def __init__( ih: intelhex.IntelHex = None, timeout: int = 1000, is_fd: bool = False, + inbox: Optional[queue.Queue] = None, + send_lock: Optional[threading.Lock] = None, ) -> None: self.bus: can.Bus = bus self.ih: intelhex.IntelHex = ih @@ -62,6 +66,24 @@ def __init__( self.timeout: int = timeout self.ui_callback: Callable = ui_callback self.is_fd = is_fd + # Optional inbox for routed messages (used when a receiver/dispatcher is active) + self.inbox: Optional[queue.Queue] = inbox + # Optional lock to protect bus.send across threads + self.send_lock: Optional[threading.Lock] = send_lock + + def _send(self, msg: can.Message, timeout: Optional[float] = None) -> None: + if self.send_lock: + with self.send_lock: + # preserve timeout semantics + if timeout is not None: + self.bus.send(msg, timeout=timeout) + else: + self.bus.send(msg) + else: + if timeout is not None: + self.bus.send(msg, timeout=timeout) + else: + self.bus.send(msg) def goto_bootloader(self) -> bool: """ @@ -69,9 +91,8 @@ def goto_bootloader(self) -> bool: :throws: TimeoutError if the boards do not respond :return: None """ - self.bus.send( + self._send( can.Message( - # arbitration_id=board_config.app_id_range_start + 8, arbitration_id=( self.board.boot_id_range_start | GO_TO_BOOT_CAN_ID_LOWBITS ), @@ -92,7 +113,7 @@ def goto_bootloader(self) -> bool: ) def goto_app(self) -> bool: - self.bus.send( + self._send( can.Message( arbitration_id=self.board.boot_id_range_start | GO_TO_APP_LOWBITS, data=[], @@ -130,7 +151,7 @@ def _validator(msg: can.Message) -> bool: else None ) - self.bus.send( + self._send( can.Message( arbitration_id=self.board.boot_id_range_start | START_UPDATE_ID_LOWBITS, data=[], @@ -172,7 +193,7 @@ def _validator(msg: can.Message): if sector.write_protect: raise RuntimeError(f"Attempted to write to a readonly memory sector!{sectors}") - self.bus.send( + self._send( can.Message( arbitration_id=self.board.boot_id_range_start | ERASE_SECTOR_CAN_ID_LOWBITS, @@ -214,7 +235,7 @@ def program(self) -> None: success = False while not success: try: - self.bus.send( + self._send( can.Message( arbitration_id=self.board.boot_id_range_start | PROGRAM_CAN_ID_LOWBITS, @@ -255,7 +276,7 @@ def _validator(msg: can.Message): else None ) - self.bus.send( + self._send( can.Message( arbitration_id=self.board.boot_id_range_start | VERIFY_CAN_ID_LOWBITS, data=[], @@ -374,9 +395,18 @@ def _await_can_msg( start = time.time() while time.time() - start < timeout: - rx_msg: can.Message = self.bus.recv(timeout=1) - if rx_msg is None: - continue + # If an inbox is present, read routed messages from it. Otherwise fall back to + # reading directly from the bus. + if self.inbox is not None: + try: + rx_msg: can.Message = self.inbox.get(timeout=1) + except queue.Empty: + continue + else: + rx_msg: can.Message = self.bus.recv(timeout=1) + if rx_msg is None: + continue + if validator(rx_msg): return rx_msg return None diff --git a/scripts/canup/update.py b/scripts/canup/update.py index 85b3b89c7e..6a648eb81a 100644 --- a/scripts/canup/update.py +++ b/scripts/canup/update.py @@ -8,6 +8,9 @@ import argparse import os from typing import List +import threading +import queue +from concurrent.futures import ThreadPoolExecutor, as_completed import can import intelhex @@ -58,7 +61,7 @@ def all_goto_app(live: Live, bootloaders: List[bootloader.Bootloader]): ) if not bootload_board.goto_app(): raise TimeoutError( - "Failed to send application command to {bootload_board.board.name}" + f"Failed to send application command to {bootload_board.board.name}" ) progress.remove_task(app_task) live.console.log( @@ -70,8 +73,18 @@ def update(configs: List[boards.Board], build_dir: str, is_fd: bool) -> None: """Update and handle UI.""" num_boards = len(configs) steps_task = progress.add_task("Steps") - bootloaders: List[bootloader.Bootloader] = [ - bootloader.Bootloader( + # Create per-board inboxes and a shared send lock. A single receiver will + # route incoming CAN messages to the appropriate inbox so workers can + # operate concurrently without stealing each other's replies. + send_lock = threading.Lock() + inbox_map: dict = {} + bootloaders: List[bootloader.Bootloader] = [] + for board in configs: + inbox_q: queue.Queue = queue.Queue() + # map both boot and app base IDs to the same inbox + inbox_map[board.boot_id_range_start] = inbox_q + inbox_map[board.app_id_range_start] = inbox_q + b = bootloader.Bootloader( bus=bus, board=board, ui_callback=lambda description, total, completed: progress.update( @@ -82,35 +95,76 @@ def update(configs: List[boards.Board], build_dir: str, is_fd: bool) -> None: is_fd=is_fd, ), ih=intelhex.IntelHex(os.path.join(build_dir, board.path)), + inbox=inbox_q, + send_lock=send_lock, ) - for board in configs - ] + bootloaders.append(b) + + # push all boards into bootloader and run updates concurrently + stop_event = threading.Event() + + def receiver_thread_fn(): + while not stop_event.is_set(): + try: + rx = bus.recv(timeout=1) + except Exception: + continue + if rx is None: + continue + # compute base id by zeroing low 8 bits (protocol uses low 8 bits for command) + base = rx.arbitration_id & ~0xFF + q = inbox_map.get(base) + if q is not None: + q.put(rx) + + receiver = threading.Thread(target=receiver_thread_fn, daemon=True) - # push all boards into bootloader with Live(Group(status, progress), transient=True) as live: - # push all boards into bootloader + receiver.start() + # put devices into bootloader first (they will respond to the receiver) all_goto_bootloader(live, bootloaders) live.console.log( f"Updating firmware for boards: [blue bold]{', '.join(board.name for board in configs)}" ) - for b_idx, bootload_board in enumerate(bootloaders): - # TODO do this in parallel - progress.update( - task_id=steps_task, - total=0, - completed=0, - description=f"Starting update for {bootload_board.board.name}", - ) - status.update( - f"Updating board [yellow]{b_idx + 1}/{num_boards}[/]: [blue bold]{bootload_board.board.name}" - ) - bootload_board.update() - live.console.log(f"[green]{bootload_board.board.name} updated successfully") + + exceptions = [] + + def worker(bootload_board: bootloader.Bootloader, idx: int) -> None: + try: + progress.update( + task_id=steps_task, + total=0, + completed=0, + description=f"Starting update for {bootload_board.board.name}", + ) + status.update( + f"Updating board [yellow]{idx + 1}/{num_boards}[/]: [blue bold]{bootload_board.board.name}" + ) + bootload_board.update() + live.console.log(f"[green]{bootload_board.board.name} updated successfully") + except Exception as e: + live.console.log(f"[red]Failed to update {bootload_board.board.name}: {e}") + exceptions.append(e) + + with ThreadPoolExecutor(max_workers=len(bootloaders)) as ex: + futures = [ex.submit(worker, b, i) for i, b in enumerate(bootloaders)] + for f in as_completed(futures): + try: + f.result() + except Exception: + pass + progress.remove_task(steps_task) - live.console.log( - f"[bold green]Firmware update successfully ({num_boards} board{'s' if num_boards > 1 else ''} updated)" - ) - # push all boards out of bootloader + if exceptions: + live.console.log(f"[red]One or more updates failed ({len(exceptions)} errors)") + else: + live.console.log( + f"[bold green]Firmware update successfully ({num_boards} board{'s' if num_boards > 1 else ''} updated)" + ) + + # stop receiver and return boards to application + stop_event.set() + receiver.join(timeout=2) all_goto_app(live, bootloaders) From 55a25a0c9f9b1a806ece7ef8c30dbe233e171aaa Mon Sep 17 00:00:00 2001 From: Shun Date: Fri, 22 May 2026 13:40:36 -0700 Subject: [PATCH 03/10] edits to update --- scripts/canup/bootloader.py | 6 ++++-- scripts/canup/update.py | 10 +++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/canup/bootloader.py b/scripts/canup/bootloader.py index a7b7e13c3b..33a8e82507 100644 --- a/scripts/canup/bootloader.py +++ b/scripts/canup/bootloader.py @@ -230,7 +230,7 @@ def program(self) -> None: "Programming data", self.size_bytes(), i * CAN_FRAME_SIZE ) - data = [self.ih[address + i] for i in range(0, 8)] + data = [self.ih[address + offset] for offset in range(0, CAN_FRAME_SIZE)] success = False while not success: @@ -423,6 +423,8 @@ def size_bytes(self) -> int: """ return int( - math.ceil((self.ih.maxaddr() - self.ih.minaddr()) / MIN_PROG_SIZE_BYTES) + math.ceil( + (self.ih.maxaddr() - self.ih.minaddr() + 1) / MIN_PROG_SIZE_BYTES + ) * MIN_PROG_SIZE_BYTES ) diff --git a/scripts/canup/update.py b/scripts/canup/update.py index 6a648eb81a..51943af9dc 100644 --- a/scripts/canup/update.py +++ b/scripts/canup/update.py @@ -95,6 +95,7 @@ def update(configs: List[boards.Board], build_dir: str, is_fd: bool) -> None: is_fd=is_fd, ), ih=intelhex.IntelHex(os.path.join(build_dir, board.path)), + is_fd=is_fd, inbox=inbox_q, send_lock=send_lock, ) @@ -162,13 +163,15 @@ def worker(bootload_board: bootloader.Bootloader, idx: int) -> None: f"[bold green]Firmware update successfully ({num_boards} board{'s' if num_boards > 1 else ''} updated)" ) - # stop receiver and return boards to application + all_goto_app(live, bootloaders) stop_event.set() receiver.join(timeout=2) - all_goto_app(live, bootloaders) + + if exceptions: + raise RuntimeError(f"Firmware update failed for {len(exceptions)} board(s)") -def erase(configs: List[boards.Board]) -> None: +def erase(configs: List[boards.Board], is_fd: bool) -> None: """Erase and handle UI.""" # push all boards into bootloader num_boards = len(configs) @@ -183,6 +186,7 @@ def erase(configs: List[boards.Board]) -> None: description=description, completed=completed, ), + is_fd=is_fd, ) for board in configs ] From ebc3c3b67665f7dd21a6111c26bd88c9604e1485 Mon Sep 17 00:00:00 2001 From: Shun Date: Sat, 23 May 2026 17:58:52 -0700 Subject: [PATCH 04/10] parallelized erase --- scripts/canup/bootloader.py | 7 +-- scripts/canup/update.py | 99 ++++++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/scripts/canup/bootloader.py b/scripts/canup/bootloader.py index 33a8e82507..fa7e3221e0 100644 --- a/scripts/canup/bootloader.py +++ b/scripts/canup/bootloader.py @@ -55,7 +55,7 @@ def __init__( board: boards.Board, ui_callback: Callable, ih: intelhex.IntelHex = None, - timeout: int = 1000, + timeout: int = 10, is_fd: bool = False, inbox: Optional[queue.Queue] = None, send_lock: Optional[threading.Lock] = None, @@ -125,7 +125,8 @@ def goto_app(self) -> bool: # TODO add retry protocol return ( self._await_can_msg( - lambda msg: msg.arbitration_id == self.board.app_id_range_start + 0, + lambda msg: msg.arbitration_id + == (self.board.app_id_range_start | MCU_10HZ_STATUS_CAN_ID_LOWBITS), 5, ) is not None @@ -289,7 +290,7 @@ def _validator(msg: can.Message): return None if rx_msg.dlc < 1: - raise RuntimeError("Zero Message recieved") + raise RuntimeError("Zero Message received") return rx_msg.data[0] diff --git a/scripts/canup/update.py b/scripts/canup/update.py index 51943af9dc..18d7e40e84 100644 --- a/scripts/canup/update.py +++ b/scripts/canup/update.py @@ -77,13 +77,13 @@ def update(configs: List[boards.Board], build_dir: str, is_fd: bool) -> None: # route incoming CAN messages to the appropriate inbox so workers can # operate concurrently without stealing each other's replies. send_lock = threading.Lock() - inbox_map: dict = {} + boot_inbox_map: dict = {} + app_inbox_map: dict = {} bootloaders: List[bootloader.Bootloader] = [] for board in configs: inbox_q: queue.Queue = queue.Queue() - # map both boot and app base IDs to the same inbox - inbox_map[board.boot_id_range_start] = inbox_q - inbox_map[board.app_id_range_start] = inbox_q + boot_inbox_map[board.boot_id_range_start] = inbox_q + app_inbox_map[board.app_id_range_start] = inbox_q b = bootloader.Bootloader( bus=bus, board=board, @@ -112,9 +112,10 @@ def receiver_thread_fn(): continue if rx is None: continue - # compute base id by zeroing low 8 bits (protocol uses low 8 bits for command) - base = rx.arbitration_id & ~0xFF - q = inbox_map.get(base) + q = app_inbox_map.get(rx.arbitration_id) + if q is None: + # Bootloader replies use the low 8 bits as the command field. + q = boot_inbox_map.get(rx.arbitration_id & ~0xFF) if q is not None: q.put(rx) @@ -176,41 +177,91 @@ def erase(configs: List[boards.Board], is_fd: bool) -> None: # push all boards into bootloader num_boards = len(configs) steps_task = progress.add_task("Steps") - bootloaders = [ - bootloader.Bootloader( + send_lock = threading.Lock() + boot_inbox_map: dict = {} + app_inbox_map: dict = {} + bootloaders: List[bootloader.Bootloader] = [] + for board in configs: + inbox_q: queue.Queue = queue.Queue() + boot_inbox_map[board.boot_id_range_start] = inbox_q + app_inbox_map[board.app_id_range_start] = inbox_q + b = bootloader.Bootloader( bus=bus, board=board, ui_callback=lambda description, total, completed: progress.update( task_id=steps_task, total=total, description=description, - completed=completed, + completed=completed ), is_fd=is_fd, + inbox=inbox_q, + send_lock=send_lock ) - for board in configs - ] + bootloaders.append(b) + + stop_event = threading.Event() + + def receiver_thread_fn(): + while not stop_event.is_set(): + try: + rx = bus.recv(timeout=1) + except Exception: + continue + if rx is None: + continue + + q = app_inbox_map.get(rx.arbitration_id) + if q is None: + q = boot_inbox_map.get(rx.arbitration_id & ~0xFF) + if q is not None: + q.put(rx) + + receiver = threading.Thread(target=receiver_thread_fn, daemon=True) with Live(Group(status, progress), transient=True) as live: + receiver.start() all_goto_bootloader(live, bootloaders) live.console.log( f"Erasing with config: [blue bold]{', '.join(board.name for board in configs)}" ) - for b_idx, bootloader_board in enumerate(bootloaders): - # TODO do this in parallel - status.update(f"Sending board {bootloader_board.board.name} to bootloader") - status.update( - f"Erasing board [yellow]{b_idx + 1}/{num_boards}[/]: [blue bold]{bootloader_board.board.name}" - ) - bootloader_board.erase() + + exceptions = [] + + def worker(bootload_board: bootloader.Bootloader, b_idx: int) -> None: + try: + status.update(f"Sending board {bootload_board.board.name} to bootloader") + status.update( + f"Erasing board [yellow]{b_idx + 1}/{num_boards}[/]: [blue bold]{bootload_board.board.name}" + ) + bootload_board.erase() + live.console.log(f"[green]{bootload_board.board.name} erased successfully") + except Exception as e: + live.console.log(f"[red]Failed to erase {bootload_board.board.name}: {e}") + exceptions.append(e) + + with ThreadPoolExecutor(max_workers=len(bootloaders)) as ex: + futures = [ex.submit(worker, b, i) for i, b in enumerate(bootloaders)] + for f in as_completed(futures): + try: + f.result() + except Exception: + pass + + progress.remove_task(steps_task) + if exceptions: + live.console.log(f"[red]One or more erases failed ({len(exceptions)} errors)") + else: live.console.log( - f"[green]{bootloader_board.board.name} erased successfully" + f"[bold green]Erase successful ({num_boards} board{'s' if num_boards > 1 else ''} erased)" ) - progress.remove_task(steps_task) - live.console.log( - f"[bold green]Erase successful ({num_boards} board{'s' if num_boards > 1 else ''} erased)" - ) + + stop_event.set() + receiver.join(timeout=2) + + if exceptions: + raise RuntimeError(f"Erase failed for {len(exceptions)} board(s)") if __name__ == "__main__": From d70a1008edc933d9ee715fce74cf3daaf63720d8 Mon Sep 17 00:00:00 2001 From: Shun Date: Sat, 23 May 2026 18:09:11 -0700 Subject: [PATCH 05/10] quick patches --- scripts/canup/update.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/canup/update.py b/scripts/canup/update.py index 18d7e40e84..40ec6cdfa8 100644 --- a/scripts/canup/update.py +++ b/scripts/canup/update.py @@ -92,7 +92,6 @@ def update(configs: List[boards.Board], build_dir: str, is_fd: bool) -> None: total=total, description=description, completed=completed, - is_fd=is_fd, ), ih=intelhex.IntelHex(os.path.join(build_dir, board.path)), is_fd=is_fd, From c61f9bd19c3110834c4e274fe4c65cbd72e89343 Mon Sep 17 00:00:00 2001 From: Saki2007 Date: Wed, 3 Jun 2026 01:13:02 -0700 Subject: [PATCH 06/10] small fixes --- python.cmake | 2 +- scripts/canup/boards.py | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/python.cmake b/python.cmake index f4eacd4117..757c37d880 100644 --- a/python.cmake +++ b/python.cmake @@ -10,7 +10,7 @@ message("") message("🐍 Python Configuration") # ====== Generate PYTHON_COMMAND ====== -find_package(Python3 3.10.0...3.13.7 COMPONENTS Interpreter REQUIRED) +find_package(Python3 3.10.0...3.14.5 COMPONENTS Interpreter REQUIRED) message(" â„šī¸ Found Python ${Python3_VERSION}") set(PYTHON_COMMAND ${Python3_EXECUTABLE}) diff --git a/scripts/canup/boards.py b/scripts/canup/boards.py index e264085c6b..7d47fa9301 100644 --- a/scripts/canup/boards.py +++ b/scripts/canup/boards.py @@ -181,29 +181,6 @@ def __hash__(self) -> int: path=os.path.join("firmware", "dev", "h5dev", "h5dev_app_metadata.hex") ) -hexray_CRIT = Board( - name="CRIT", - boot_id_range_start=0x18000000, - app_id_range_start=900, - mcu=STM32H562_MCU, - path=os.path.join("firmware", "hexray", "CRIT", "hexray_CRIT_app_metadata.hex"), -) - -hexray_BMS = Board( - name="BMS", - boot_id_range_start=0x04000000, - app_id_range_start=400, - mcu=STM32H733_MCU, - path=os.path.join("firmware", "hexray", "BMS", "hexray_BMS_app_metadata.hex"), -) - -hexray_FSM = Board( - name="FSM", - boot_id_range_start=0x10000000, - app_id_range_start=600, - mcu=STM32H562_MCU, - path=os.path.join("firmware", "hexray", "FSM", "hexray_FSM_app_metadata.hex"), -) CONFIGS = { "h7dev": [h7dev], From 8a9e5e51f75f8edc405a6cb3557bec58171a68a1 Mon Sep 17 00:00:00 2001 From: Saki2007 Date: Wed, 3 Jun 2026 02:03:25 -0700 Subject: [PATCH 07/10] idk what this is --- scripts/canup/boards.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/canup/boards.py b/scripts/canup/boards.py index 7d47fa9301..2acbe1d70e 100644 --- a/scripts/canup/boards.py +++ b/scripts/canup/boards.py @@ -193,5 +193,5 @@ def __hash__(self) -> int: "hexray_VC" : [hexray_VC], "hexray_FD": [hexray_DAM, hexray_VC, hexray_BMS], "hexray_Sx" : [hexray_CRIT, hexray_FSM, hexray_RSM], - "hexray" : [hexray_RSM, hexray_BMS, hexray_CRIT, hexray_DAM, hexray_CRIT, hexray_FSM, hexray_VC], + "hexray" : [hexray_RSM, hexray_BMS, hexray_CRIT, hexray_DAM, hexray_FSM, hexray_VC], } From 82df3f47081b5f82ac21d7370c2ac88c36c3439b Mon Sep 17 00:00:00 2001 From: Saki2007 Date: Wed, 3 Jun 2026 08:16:58 -0700 Subject: [PATCH 08/10] testing whats wrong --- scripts/canup/boards.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/canup/boards.py b/scripts/canup/boards.py index 2acbe1d70e..bbb09abefd 100644 --- a/scripts/canup/boards.py +++ b/scripts/canup/boards.py @@ -182,6 +182,7 @@ def __hash__(self) -> int: ) + CONFIGS = { "h7dev": [h7dev], "h5dev": [h5dev], From 1c4e034e78c048aec2133980e8f2e621ce0858a6 Mon Sep 17 00:00:00 2001 From: Saki2007 Date: Wed, 3 Jun 2026 10:48:37 -0700 Subject: [PATCH 09/10] fixes --- scripts/canup/boards.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/canup/boards.py b/scripts/canup/boards.py index bbb09abefd..6e6cb26508 100644 --- a/scripts/canup/boards.py +++ b/scripts/canup/boards.py @@ -181,8 +181,6 @@ def __hash__(self) -> int: path=os.path.join("firmware", "dev", "h5dev", "h5dev_app_metadata.hex") ) - - CONFIGS = { "h7dev": [h7dev], "h5dev": [h5dev], From a3fdc7a65405fbbf6ed12cba9ca898680f08aad5 Mon Sep 17 00:00:00 2001 From: Saki2007 Date: Wed, 10 Jun 2026 15:25:35 -0700 Subject: [PATCH 10/10] python version change --- python.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python.cmake b/python.cmake index 757c37d880..c121b280f9 100644 --- a/python.cmake +++ b/python.cmake @@ -10,7 +10,11 @@ message("") message("🐍 Python Configuration") # ====== Generate PYTHON_COMMAND ====== +<<<<<<< Updated upstream find_package(Python3 3.10.0...3.14.5 COMPONENTS Interpreter REQUIRED) +======= +find_package(Python3 3.10.0...3.14.6 COMPONENTS Interpreter REQUIRED) +>>>>>>> Stashed changes message(" â„šī¸ Found Python ${Python3_VERSION}") set(PYTHON_COMMAND ${Python3_EXECUTABLE})