micropython-stubber 1.24.1__py3-none-any.whl → 1.24.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/METADATA +9 -29
- micropython_stubber-1.24.4.dist-info/RECORD +107 -0
- {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/WHEEL +1 -1
- stubber/__init__.py +1 -1
- stubber/board/createstubs.py +44 -38
- stubber/board/createstubs_db.py +17 -12
- stubber/board/createstubs_db_min.py +63 -63
- stubber/board/createstubs_db_mpy.mpy +0 -0
- stubber/board/createstubs_mem.py +17 -12
- stubber/board/createstubs_mem_min.py +99 -99
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +111 -112
- stubber/board/createstubs_mpy.mpy +0 -0
- stubber/board/modulelist.txt +27 -27
- stubber/codemod/board.py +1 -1
- stubber/codemod/enrich.py +13 -13
- stubber/codemod/merge_docstub.py +83 -53
- stubber/codemod/visitors/type_helpers.py +143 -41
- stubber/commands/enrich_folder_cmd.py +17 -17
- stubber/commands/get_docstubs_cmd.py +27 -9
- stubber/commands/get_frozen_cmd.py +1 -0
- stubber/commands/merge_cmd.py +2 -4
- stubber/merge_config.py +5 -36
- stubber/minify.py +3 -3
- stubber/modcat.py +118 -0
- stubber/publish/merge_docstubs.py +22 -5
- stubber/publish/stubpackage.py +33 -28
- stubber/rst/lookup.py +6 -23
- stubber/rst/reader.py +8 -13
- stubber/stubs_from_docs.py +2 -1
- stubber/tools/manifestfile.py +2 -1
- stubber/{cst_transformer.py → typing_collector.py} +36 -4
- micropython_stubber-1.24.1.dist-info/RECORD +0 -161
- mpflash/README.md +0 -220
- mpflash/libusb_flash.ipynb +0 -203
- mpflash/mpflash/__init__.py +0 -0
- mpflash/mpflash/add_firmware.py +0 -98
- mpflash/mpflash/ask_input.py +0 -236
- mpflash/mpflash/basicgit.py +0 -324
- mpflash/mpflash/bootloader/__init__.py +0 -2
- mpflash/mpflash/bootloader/activate.py +0 -60
- mpflash/mpflash/bootloader/detect.py +0 -82
- mpflash/mpflash/bootloader/manual.py +0 -101
- mpflash/mpflash/bootloader/micropython.py +0 -12
- mpflash/mpflash/bootloader/touch1200.py +0 -36
- mpflash/mpflash/cli_download.py +0 -129
- mpflash/mpflash/cli_flash.py +0 -224
- mpflash/mpflash/cli_group.py +0 -111
- mpflash/mpflash/cli_list.py +0 -87
- mpflash/mpflash/cli_main.py +0 -39
- mpflash/mpflash/common.py +0 -217
- mpflash/mpflash/config.py +0 -44
- mpflash/mpflash/connected.py +0 -96
- mpflash/mpflash/download.py +0 -364
- mpflash/mpflash/downloaded.py +0 -138
- mpflash/mpflash/errors.py +0 -9
- mpflash/mpflash/flash/__init__.py +0 -55
- mpflash/mpflash/flash/esp.py +0 -59
- mpflash/mpflash/flash/stm32.py +0 -19
- mpflash/mpflash/flash/stm32_dfu.py +0 -104
- mpflash/mpflash/flash/uf2/__init__.py +0 -88
- mpflash/mpflash/flash/uf2/boardid.py +0 -15
- mpflash/mpflash/flash/uf2/linux.py +0 -136
- mpflash/mpflash/flash/uf2/macos.py +0 -42
- mpflash/mpflash/flash/uf2/uf2disk.py +0 -12
- mpflash/mpflash/flash/uf2/windows.py +0 -43
- mpflash/mpflash/flash/worklist.py +0 -170
- mpflash/mpflash/list.py +0 -106
- mpflash/mpflash/logger.py +0 -41
- mpflash/mpflash/mpboard_id/__init__.py +0 -98
- mpflash/mpflash/mpboard_id/add_boards.py +0 -262
- mpflash/mpflash/mpboard_id/board.py +0 -37
- mpflash/mpflash/mpboard_id/board_id.py +0 -90
- mpflash/mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpflash/mpboard_id/store.py +0 -48
- mpflash/mpflash/mpremoteboard/__init__.py +0 -271
- mpflash/mpflash/mpremoteboard/mpy_fw_info.py +0 -152
- mpflash/mpflash/mpremoteboard/runner.py +0 -140
- mpflash/mpflash/vendor/board_database.py +0 -185
- mpflash/mpflash/vendor/click_aliases.py +0 -91
- mpflash/mpflash/vendor/dfu.py +0 -165
- mpflash/mpflash/vendor/pydfu.py +0 -605
- mpflash/mpflash/vendor/readme.md +0 -12
- mpflash/mpflash/versions.py +0 -123
- mpflash/poetry.lock +0 -2603
- mpflash/pyproject.toml +0 -66
- mpflash/stm32_udev_rules.md +0 -63
- stubber/codemod/test_enrich.py +0 -87
- {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/LICENSE +0 -0
- {micropython_stubber-1.24.1.dist-info → micropython_stubber-1.24.4.dist-info}/entry_points.txt +0 -0
@@ -1,271 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Module to run mpremote commands, and retry on failure or timeout
|
3
|
-
"""
|
4
|
-
|
5
|
-
import sys
|
6
|
-
import time
|
7
|
-
from pathlib import Path
|
8
|
-
from typing import List, Optional, Union
|
9
|
-
|
10
|
-
import serial.tools.list_ports
|
11
|
-
from rich.progress import track
|
12
|
-
from tenacity import retry, stop_after_attempt, wait_fixed
|
13
|
-
|
14
|
-
from mpflash.errors import MPFlashError
|
15
|
-
from mpflash.logger import log
|
16
|
-
from mpflash.mpboard_id.board_id import find_board_id_by_description
|
17
|
-
from mpflash.mpremoteboard.runner import run
|
18
|
-
|
19
|
-
if sys.version_info >= (3, 11):
|
20
|
-
import tomllib # type: ignore
|
21
|
-
else:
|
22
|
-
import tomli as tomllib # type: ignore
|
23
|
-
|
24
|
-
###############################################################################################
|
25
|
-
HERE = Path(__file__).parent
|
26
|
-
|
27
|
-
OK = 0
|
28
|
-
ERROR = -1
|
29
|
-
RETRIES = 3
|
30
|
-
###############################################################################################
|
31
|
-
|
32
|
-
|
33
|
-
class MPRemoteBoard:
|
34
|
-
"""Class to run mpremote commands"""
|
35
|
-
|
36
|
-
def __init__(self, serialport: str = "", update: bool = False, *, location: str = ""):
|
37
|
-
"""
|
38
|
-
Initialize MPRemoteBoard object.
|
39
|
-
|
40
|
-
Parameters:
|
41
|
-
- serialport (str): The serial port to connect to. Default is an empty string.
|
42
|
-
- update (bool): Whether to update the MCU information. Default is False.
|
43
|
-
"""
|
44
|
-
self.serialport: str = serialport
|
45
|
-
self.firmware = {}
|
46
|
-
|
47
|
-
self.connected = False
|
48
|
-
self.path: Optional[Path] = None
|
49
|
-
self.family = "unknown"
|
50
|
-
self.description = ""
|
51
|
-
self.version = ""
|
52
|
-
self.port = ""
|
53
|
-
self.board = ""
|
54
|
-
self.cpu = ""
|
55
|
-
self.arch = ""
|
56
|
-
self.mpy = ""
|
57
|
-
self.build = ""
|
58
|
-
self.location = location # USB location
|
59
|
-
self.toml = {}
|
60
|
-
if update:
|
61
|
-
self.get_mcu_info()
|
62
|
-
|
63
|
-
def __str__(self):
|
64
|
-
"""
|
65
|
-
Return a string representation of the MPRemoteBoard object.
|
66
|
-
|
67
|
-
Returns:
|
68
|
-
- str: The string representation of the object.
|
69
|
-
"""
|
70
|
-
return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
|
71
|
-
|
72
|
-
@staticmethod
|
73
|
-
def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
|
74
|
-
# TODO: rename to connected_comports
|
75
|
-
"""
|
76
|
-
Get a list of connected comports.
|
77
|
-
|
78
|
-
Parameters:
|
79
|
-
- bluetooth (bool): Whether to include Bluetooth ports. Default is False.
|
80
|
-
|
81
|
-
Returns:
|
82
|
-
- List[str]: A list of connected board ports.
|
83
|
-
"""
|
84
|
-
comports = serial.tools.list_ports.comports()
|
85
|
-
|
86
|
-
if not bluetooth:
|
87
|
-
# filter out bluetooth ports
|
88
|
-
comports = [p for p in comports if "bluetooth" not in p.description.lower()]
|
89
|
-
comports = [p for p in comports if "BTHENUM" not in p.hwid]
|
90
|
-
if description:
|
91
|
-
output = [
|
92
|
-
f"{p.device} {(p.manufacturer + ' ') if p.manufacturer and not p.description.startswith(p.manufacturer) else ''}{p.description}"
|
93
|
-
for p in comports
|
94
|
-
]
|
95
|
-
else:
|
96
|
-
output = [p.device for p in comports]
|
97
|
-
|
98
|
-
if sys.platform == "win32":
|
99
|
-
# Windows sort of comports by number - but fallback to device name
|
100
|
-
return sorted(
|
101
|
-
output, key=lambda x: int(x.split()[0][3:]) if x.split()[0][3:].isdigit() else x
|
102
|
-
)
|
103
|
-
# sort by device name
|
104
|
-
return sorted(output)
|
105
|
-
|
106
|
-
@retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
|
107
|
-
def get_mcu_info(self, timeout: int = 2):
|
108
|
-
"""
|
109
|
-
Get MCU information from the connected board.
|
110
|
-
|
111
|
-
Parameters:
|
112
|
-
- timeout (int): The timeout value in seconds. Default is 2.
|
113
|
-
|
114
|
-
Raises:
|
115
|
-
- ConnectionError: If failed to get mcu_info for the serial port.
|
116
|
-
"""
|
117
|
-
rc, result = self.run_command(
|
118
|
-
["run", str(HERE / "mpy_fw_info.py")],
|
119
|
-
no_info=True,
|
120
|
-
timeout=timeout,
|
121
|
-
resume=False, # Avoid restarts
|
122
|
-
)
|
123
|
-
if rc:
|
124
|
-
log.debug(f"rc: {rc}, result: {result}")
|
125
|
-
raise ConnectionError(f"Failed to get mcu_info for {self.serialport}")
|
126
|
-
# Ok we have the info, now parse it
|
127
|
-
raw_info = result[0].strip()
|
128
|
-
if raw_info.startswith("{") and raw_info.endswith("}"):
|
129
|
-
info = eval(raw_info)
|
130
|
-
self.family = info["family"]
|
131
|
-
self.version = info["version"]
|
132
|
-
self.build = info["build"]
|
133
|
-
self.port = info["port"]
|
134
|
-
self.cpu = info["cpu"]
|
135
|
-
self.arch = info["arch"]
|
136
|
-
self.mpy = info["mpy"]
|
137
|
-
self.description = descr = info["board"]
|
138
|
-
pos = descr.rfind(" with")
|
139
|
-
short_descr = descr[:pos].strip() if pos != -1 else ""
|
140
|
-
if board_name := find_board_id_by_description(
|
141
|
-
descr, short_descr, version=self.version
|
142
|
-
):
|
143
|
-
self.board = board_name
|
144
|
-
else:
|
145
|
-
self.board = "UNKNOWN_BOARD"
|
146
|
-
# get the board_info.toml
|
147
|
-
self.get_board_info_toml()
|
148
|
-
# now we know the board is connected
|
149
|
-
self.connected = True
|
150
|
-
|
151
|
-
@retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(0.2), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
|
152
|
-
def get_board_info_toml(self, timeout: int = 1):
|
153
|
-
"""
|
154
|
-
Reads the content of the board_info.toml file from the connected board,
|
155
|
-
and adds that to the board object.
|
156
|
-
|
157
|
-
Parameters:
|
158
|
-
- timeout (int): The timeout value in seconds.
|
159
|
-
|
160
|
-
Raises:
|
161
|
-
- ConnectionError: If failed to communicate with the serial port.
|
162
|
-
"""
|
163
|
-
try:
|
164
|
-
rc, result = self.run_command(
|
165
|
-
["cat", ":board_info.toml"],
|
166
|
-
no_info=True,
|
167
|
-
timeout=timeout,
|
168
|
-
log_errors=False,
|
169
|
-
)
|
170
|
-
except Exception as e:
|
171
|
-
raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}: {e}")
|
172
|
-
# this is optional - so only parse if we got the file
|
173
|
-
self.toml = {}
|
174
|
-
if rc in [OK]: # sometimes we get an -9 ???
|
175
|
-
try:
|
176
|
-
# Ok we have the info, now parse it
|
177
|
-
self.toml = tomllib.loads("".join(result))
|
178
|
-
log.debug(f"board_info.toml: {self.toml}")
|
179
|
-
except Exception as e:
|
180
|
-
log.error(f"Failed to parse board_info.toml: {e}")
|
181
|
-
else:
|
182
|
-
log.trace(f"Did not find a board_info.toml: {result}")
|
183
|
-
|
184
|
-
def disconnect(self) -> bool:
|
185
|
-
"""
|
186
|
-
Disconnect from a board.
|
187
|
-
|
188
|
-
Returns:
|
189
|
-
- bool: True if successfully disconnected, False otherwise.
|
190
|
-
"""
|
191
|
-
if not self.connected:
|
192
|
-
return True
|
193
|
-
if not self.serialport:
|
194
|
-
log.error("No port connected")
|
195
|
-
self.connected = False
|
196
|
-
return False
|
197
|
-
log.info(f"Disconnecting from {self.serialport}")
|
198
|
-
result = self.run_command(["disconnect"])[0] == OK
|
199
|
-
self.connected = False
|
200
|
-
return result
|
201
|
-
|
202
|
-
@retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(2), reraise=True)
|
203
|
-
def run_command(
|
204
|
-
self,
|
205
|
-
cmd: Union[str, List[str]],
|
206
|
-
*,
|
207
|
-
log_errors: bool = True,
|
208
|
-
no_info: bool = False,
|
209
|
-
timeout: int = 60,
|
210
|
-
resume: Optional[bool] = None,
|
211
|
-
**kwargs,
|
212
|
-
):
|
213
|
-
"""
|
214
|
-
Run mpremote with the given command.
|
215
|
-
|
216
|
-
Parameters:
|
217
|
-
- cmd (Union[str, List[str]]): The command to run, either a string or a list of strings.
|
218
|
-
- log_errors (bool): Whether to log errors. Default is True.
|
219
|
-
- no_info (bool): Whether to skip printing info. Default is False.
|
220
|
-
- timeout (int): The timeout value in seconds. Default is 60.
|
221
|
-
|
222
|
-
Returns:
|
223
|
-
- bool: True if the command succeeded, False otherwise.
|
224
|
-
"""
|
225
|
-
if isinstance(cmd, str):
|
226
|
-
cmd = cmd.split(" ")
|
227
|
-
prefix = [sys.executable, "-m", "mpremote"]
|
228
|
-
if self.serialport:
|
229
|
-
prefix += ["connect", self.serialport]
|
230
|
-
# if connected add resume to keep state between commands
|
231
|
-
if (resume != False) and self.connected or resume:
|
232
|
-
prefix += ["resume"]
|
233
|
-
cmd = prefix + cmd
|
234
|
-
log.debug(" ".join(cmd))
|
235
|
-
result = run(cmd, timeout, log_errors, no_info, **kwargs)
|
236
|
-
self.connected = result[0] == OK
|
237
|
-
return result
|
238
|
-
|
239
|
-
@retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1))
|
240
|
-
def mip_install(self, name: str) -> bool:
|
241
|
-
"""
|
242
|
-
Install a micropython package.
|
243
|
-
|
244
|
-
Parameters:
|
245
|
-
- name (str): The name of the package to install.
|
246
|
-
|
247
|
-
Returns:
|
248
|
-
- bool: True if the installation succeeded, False otherwise.
|
249
|
-
"""
|
250
|
-
# install createstubs to the board
|
251
|
-
cmd = ["mip", "install", name]
|
252
|
-
result = self.run_command(cmd)[0] == OK
|
253
|
-
self.connected = True
|
254
|
-
return result
|
255
|
-
|
256
|
-
def wait_for_restart(self, timeout: int = 10):
|
257
|
-
"""wait for the board to restart"""
|
258
|
-
for _ in track(
|
259
|
-
range(timeout),
|
260
|
-
description=f"Waiting for the board to restart ({timeout}s)",
|
261
|
-
transient=True,
|
262
|
-
show_speed=False,
|
263
|
-
refresh_per_second=1,
|
264
|
-
total=timeout,
|
265
|
-
):
|
266
|
-
time.sleep(1)
|
267
|
-
try:
|
268
|
-
self.get_mcu_info()
|
269
|
-
break
|
270
|
-
except (ConnectionError, MPFlashError):
|
271
|
-
pass
|
@@ -1,152 +0,0 @@
|
|
1
|
-
# %%micropython
|
2
|
-
import os
|
3
|
-
import sys
|
4
|
-
|
5
|
-
|
6
|
-
def _build(s):
|
7
|
-
# extract build from sys.version or os.uname().version if available
|
8
|
-
# sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
|
9
|
-
# sys.implementation.version: 'v1.13-103-gb137d064e'
|
10
|
-
if not s:
|
11
|
-
return ""
|
12
|
-
s = s.split(" on ", 1)[0] if " on " in s else s
|
13
|
-
if s.startswith("v"):
|
14
|
-
if not "-" in s:
|
15
|
-
return ""
|
16
|
-
b = s.split("-")[1]
|
17
|
-
return b
|
18
|
-
if not "-preview" in s:
|
19
|
-
return ""
|
20
|
-
b = s.split("-preview")[1].split(".")[1]
|
21
|
-
return b
|
22
|
-
|
23
|
-
|
24
|
-
def _version_str(version: tuple): # -> str:
|
25
|
-
v_str = ".".join([str(n) for n in version[:3]])
|
26
|
-
if len(version) > 3 and version[3]:
|
27
|
-
v_str += "-" + version[3]
|
28
|
-
return v_str
|
29
|
-
|
30
|
-
|
31
|
-
def _info(): # type:() -> dict[str, str]
|
32
|
-
# sourcery skip: use-contextlib-suppress, use-fstring-for-formatting, use-named-expression
|
33
|
-
info = dict(
|
34
|
-
{
|
35
|
-
"family": sys.implementation[0], # type: ignore
|
36
|
-
"version": "",
|
37
|
-
"build": "",
|
38
|
-
"ver": "",
|
39
|
-
"port": (
|
40
|
-
"stm32" if sys.platform.startswith("pyb") else sys.platform
|
41
|
-
), # port: esp32 / win32 / linux / stm32
|
42
|
-
"board": "GENERIC",
|
43
|
-
"cpu": "",
|
44
|
-
"mpy": "",
|
45
|
-
"arch": "",
|
46
|
-
}
|
47
|
-
)
|
48
|
-
try:
|
49
|
-
info["version"] = _version_str(sys.implementation.version)
|
50
|
-
except AttributeError:
|
51
|
-
pass
|
52
|
-
try:
|
53
|
-
machine = (
|
54
|
-
sys.implementation._machine
|
55
|
-
if "_machine" in dir(sys.implementation)
|
56
|
-
else os.uname().machine
|
57
|
-
)
|
58
|
-
info["board"] = machine.strip()
|
59
|
-
info["cpu"] = machine.split("with")[-1].strip() if "with" in machine else ""
|
60
|
-
info["mpy"] = (
|
61
|
-
sys.implementation._mpy
|
62
|
-
if "_mpy" in dir(sys.implementation)
|
63
|
-
else sys.implementation.mpy if "mpy" in dir(sys.implementation) else ""
|
64
|
-
)
|
65
|
-
except (AttributeError, IndexError):
|
66
|
-
pass
|
67
|
-
|
68
|
-
try:
|
69
|
-
if hasattr(sys, "version"):
|
70
|
-
info["build"] = _build(sys.version)
|
71
|
-
elif hasattr(os, "uname"):
|
72
|
-
info["build"] = _build(os.uname()[3])
|
73
|
-
if not info["build"]:
|
74
|
-
# extract build from uname().release if available
|
75
|
-
info["build"] = _build(os.uname()[2])
|
76
|
-
except (AttributeError, IndexError):
|
77
|
-
pass
|
78
|
-
# avoid build hashes
|
79
|
-
if info["build"] and len(info["build"]) > 5:
|
80
|
-
info["build"] = ""
|
81
|
-
|
82
|
-
if info["version"] == "" and sys.platform not in ("unix", "win32"):
|
83
|
-
try:
|
84
|
-
u = os.uname()
|
85
|
-
info["version"] = u.release
|
86
|
-
except (IndexError, AttributeError, TypeError):
|
87
|
-
pass
|
88
|
-
# detect families
|
89
|
-
for fam_name, mod_name, mod_thing in [
|
90
|
-
("pycopy", "pycopy", "const"),
|
91
|
-
("pycom", "pycom", "FAT"),
|
92
|
-
("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
|
93
|
-
]:
|
94
|
-
try:
|
95
|
-
_t = __import__(mod_name, None, None, (mod_thing))
|
96
|
-
info["family"] = fam_name
|
97
|
-
del _t
|
98
|
-
break
|
99
|
-
except (ImportError, KeyError):
|
100
|
-
pass
|
101
|
-
|
102
|
-
if info["family"] == "ev3-pybricks":
|
103
|
-
info["release"] = "2.0.0"
|
104
|
-
|
105
|
-
if info["family"] == "micropython":
|
106
|
-
if (
|
107
|
-
info["version"]
|
108
|
-
and info["version"].endswith(".0")
|
109
|
-
and info["version"]
|
110
|
-
>= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
|
111
|
-
and info["version"] <= "1.19.9"
|
112
|
-
):
|
113
|
-
# drop the .0 for newer releases
|
114
|
-
info["version"] = info["version"][:-2]
|
115
|
-
|
116
|
-
# spell-checker: disable
|
117
|
-
if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
|
118
|
-
sys_mpy = int(info["mpy"])
|
119
|
-
# .mpy architecture
|
120
|
-
try:
|
121
|
-
arch = [
|
122
|
-
None,
|
123
|
-
"x86",
|
124
|
-
"x64",
|
125
|
-
"armv6",
|
126
|
-
"armv6m",
|
127
|
-
"armv7m",
|
128
|
-
"armv7em",
|
129
|
-
"armv7emsp",
|
130
|
-
"armv7emdp",
|
131
|
-
"xtensa",
|
132
|
-
"xtensawin",
|
133
|
-
"hazard3riscv", # assumed
|
134
|
-
][sys_mpy >> 10]
|
135
|
-
except IndexError:
|
136
|
-
arch = "unknown"
|
137
|
-
if arch:
|
138
|
-
info["arch"] = arch
|
139
|
-
# .mpy version.minor
|
140
|
-
info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
|
141
|
-
# simple to use version[-build] string avoiding f-strings for backward compat
|
142
|
-
info["ver"] = (
|
143
|
-
"v{version}-{build}".format(version=info["version"], build=info["build"])
|
144
|
-
if info["build"]
|
145
|
-
else "v{version}".format(version=info["version"])
|
146
|
-
)
|
147
|
-
|
148
|
-
return info
|
149
|
-
|
150
|
-
|
151
|
-
print(_info())
|
152
|
-
del _info, _build, _version_str
|
@@ -1,140 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Run a command and return the output and return code as a tuple
|
3
|
-
"""
|
4
|
-
|
5
|
-
import subprocess
|
6
|
-
from dataclasses import dataclass
|
7
|
-
from threading import Timer
|
8
|
-
from typing import List, Optional, Tuple
|
9
|
-
|
10
|
-
from loguru import logger as log
|
11
|
-
|
12
|
-
LogTagList = List[str]
|
13
|
-
|
14
|
-
|
15
|
-
@dataclass
|
16
|
-
class LogTags:
|
17
|
-
reset_tags: LogTagList
|
18
|
-
error_tags: LogTagList
|
19
|
-
warning_tags: LogTagList
|
20
|
-
success_tags: LogTagList
|
21
|
-
ignore_tags: LogTagList
|
22
|
-
|
23
|
-
|
24
|
-
DEFAULT_RESET_TAGS = [
|
25
|
-
# ESP32 reset causes
|
26
|
-
"rst cause:1, boot mode:", # 1 -> hardware watch dog reset
|
27
|
-
"rst cause:2, boot mode:", # 2 -> software watch dog reset (From an exception)
|
28
|
-
"rst cause:3, boot mode:", # 3 -> software watch dog reset system_restart (Possibly unfed watchdog got angry)
|
29
|
-
"rst cause:4, boot mode:", # 4 -> soft restart (Possibly with a restart command)
|
30
|
-
"boot.esp32: PRO CPU has been reset by WDT.",
|
31
|
-
"rst:0x10 (RTCWDT_RTC_RESET)",
|
32
|
-
]
|
33
|
-
|
34
|
-
|
35
|
-
def run(
|
36
|
-
cmd: List[str],
|
37
|
-
timeout: int = 60,
|
38
|
-
log_errors: bool = True,
|
39
|
-
no_info: bool = False,
|
40
|
-
*,
|
41
|
-
log_warnings: bool = False,
|
42
|
-
reset_tags: Optional[LogTagList] = None,
|
43
|
-
error_tags: Optional[LogTagList] = None,
|
44
|
-
warning_tags: Optional[LogTagList] = None,
|
45
|
-
success_tags: Optional[LogTagList] = None,
|
46
|
-
ignore_tags: Optional[LogTagList] = None,
|
47
|
-
) -> Tuple[int, List[str]]:
|
48
|
-
# sourcery skip: no-long-functions
|
49
|
-
"""
|
50
|
-
Run a command and return the output and return code as a tuple
|
51
|
-
Parameters
|
52
|
-
----------
|
53
|
-
cmd : List[str]
|
54
|
-
The command to run
|
55
|
-
timeout : int, optional
|
56
|
-
The timeout in seconds, by default 60
|
57
|
-
log_errors : bool, optional
|
58
|
-
If False, don't log errors, Default: true
|
59
|
-
no_info : bool, optional
|
60
|
-
If True, don't log info, by default False
|
61
|
-
error_tags : Optional[LogTagList], optional
|
62
|
-
A list of strings to look for in the output to log as errors, by default None
|
63
|
-
warning_tags : Optional[LogTagList], optional
|
64
|
-
A list of strings to look for in the output to log as warnings, by default None
|
65
|
-
Returns
|
66
|
-
-------
|
67
|
-
Tuple[int, List[str]]
|
68
|
-
The return code and the output as a list of strings
|
69
|
-
"""
|
70
|
-
if not reset_tags:
|
71
|
-
reset_tags = DEFAULT_RESET_TAGS
|
72
|
-
if not error_tags:
|
73
|
-
error_tags = ["Traceback ", "Error: ", "Exception: ", "ERROR :", "CRIT :"]
|
74
|
-
if not warning_tags:
|
75
|
-
warning_tags = ["WARN :", "TRACE :"]
|
76
|
-
if not success_tags:
|
77
|
-
success_tags = []
|
78
|
-
if not ignore_tags:
|
79
|
-
ignore_tags = [' File "<stdin>",']
|
80
|
-
|
81
|
-
replace_tags = ["\x1b[1A"]
|
82
|
-
|
83
|
-
output = []
|
84
|
-
try:
|
85
|
-
proc = subprocess.Popen(
|
86
|
-
cmd,
|
87
|
-
stdout=subprocess.PIPE,
|
88
|
-
stderr=subprocess.PIPE,
|
89
|
-
universal_newlines=True,
|
90
|
-
encoding="utf-8",
|
91
|
-
)
|
92
|
-
except FileNotFoundError as e:
|
93
|
-
raise FileNotFoundError(f"Failed to start {cmd[0]}") from e
|
94
|
-
|
95
|
-
def timed_out():
|
96
|
-
proc.kill()
|
97
|
-
if log_warnings:
|
98
|
-
log.warning(f"Command {cmd} timed out after {timeout} seconds")
|
99
|
-
|
100
|
-
timer = Timer(timeout, timed_out)
|
101
|
-
try:
|
102
|
-
timer.start()
|
103
|
-
# stdout has most of the output, assign log categories based on text tags
|
104
|
-
if proc.stdout:
|
105
|
-
for line in proc.stdout:
|
106
|
-
if not line or not line.strip():
|
107
|
-
continue
|
108
|
-
for tag in replace_tags:
|
109
|
-
line = line.replace(tag, "")
|
110
|
-
output.append(line) # full output, no trimming
|
111
|
-
if any(tag in line for tag in reset_tags):
|
112
|
-
raise RuntimeError("Board reset detected")
|
113
|
-
|
114
|
-
line = line.rstrip("\n")
|
115
|
-
# if any of the error tags in the line
|
116
|
-
if any(tag in line for tag in error_tags):
|
117
|
-
if not log_errors:
|
118
|
-
continue
|
119
|
-
log.error(line)
|
120
|
-
elif any(tag in line for tag in warning_tags):
|
121
|
-
log.warning(line)
|
122
|
-
elif any(tag in line for tag in success_tags):
|
123
|
-
log.success(line)
|
124
|
-
elif any(tag in line for tag in ignore_tags):
|
125
|
-
continue
|
126
|
-
else:
|
127
|
-
if not no_info:
|
128
|
-
if line.startswith(("INFO : ", "WARN : ", "ERROR : ")):
|
129
|
-
line = line[8:].lstrip()
|
130
|
-
log.info(line)
|
131
|
-
if proc.stderr and log_errors:
|
132
|
-
for line in proc.stderr:
|
133
|
-
log.warning(line)
|
134
|
-
except UnicodeDecodeError as e:
|
135
|
-
log.error(f"Failed to decode output: {e}")
|
136
|
-
finally:
|
137
|
-
timer.cancel()
|
138
|
-
|
139
|
-
proc.wait(timeout=1)
|
140
|
-
return proc.returncode or 0, output
|