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
mpflash/mpflash/add_firmware.py
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
import shutil
|
2
|
-
from pathlib import Path
|
3
|
-
from typing import Union
|
4
|
-
|
5
|
-
import jsonlines
|
6
|
-
import requests
|
7
|
-
from loguru import logger as log
|
8
|
-
|
9
|
-
# re-use logic from mpremote
|
10
|
-
from mpremote.mip import _rewrite_url as rewrite_url # type: ignore
|
11
|
-
|
12
|
-
from mpflash.common import FWInfo
|
13
|
-
from mpflash.config import config
|
14
|
-
from mpflash.versions import get_preview_mp_version, get_stable_mp_version
|
15
|
-
|
16
|
-
|
17
|
-
def add_firmware(
|
18
|
-
source: Union[Path, str],
|
19
|
-
new_fw: FWInfo,
|
20
|
-
*,
|
21
|
-
force: bool = False,
|
22
|
-
custom: bool = False,
|
23
|
-
description: str = "",
|
24
|
-
) -> bool:
|
25
|
-
"""Add a firmware to the firmware folder.
|
26
|
-
|
27
|
-
stored in the port folder, with the same filename as the source.
|
28
|
-
|
29
|
-
"""
|
30
|
-
# Check minimal info needed
|
31
|
-
if not new_fw.port or not new_fw.board:
|
32
|
-
log.error("Port and board are required")
|
33
|
-
return False
|
34
|
-
if not isinstance(source, Path) and not source.startswith("http"):
|
35
|
-
log.error(f"Invalid source {source}")
|
36
|
-
return False
|
37
|
-
|
38
|
-
# use sensible defaults
|
39
|
-
source_2 = Path(source)
|
40
|
-
new_fw.ext = new_fw.ext or source_2.suffix
|
41
|
-
new_fw.variant = new_fw.variant or new_fw.board
|
42
|
-
new_fw.custom = new_fw.custom or custom
|
43
|
-
new_fw.description = new_fw.description or description
|
44
|
-
if not new_fw.version:
|
45
|
-
# TODO: Get version from filename
|
46
|
-
# or use the last preview version
|
47
|
-
new_fw.version = get_preview_mp_version() if new_fw.preview else get_stable_mp_version()
|
48
|
-
|
49
|
-
config.firmware_folder.mkdir(exist_ok=True)
|
50
|
-
|
51
|
-
fw_filename = config.firmware_folder / new_fw.port / source_2.name
|
52
|
-
|
53
|
-
new_fw.filename = str(fw_filename.relative_to(config.firmware_folder))
|
54
|
-
new_fw.firmware = source.as_uri() if isinstance(source, Path) else source
|
55
|
-
|
56
|
-
if not copy_firmware(source, fw_filename, force):
|
57
|
-
log.error(f"Failed to copy {source} to {fw_filename}")
|
58
|
-
return False
|
59
|
-
# add to inventory
|
60
|
-
with jsonlines.open(config.firmware_folder / "firmware.jsonl", "a") as writer:
|
61
|
-
log.info(f"Adding {new_fw.port} {new_fw.board}")
|
62
|
-
log.info(f" to {fw_filename}")
|
63
|
-
|
64
|
-
writer.write(new_fw.to_dict())
|
65
|
-
return True
|
66
|
-
|
67
|
-
|
68
|
-
def copy_firmware(source: Union[Path, str], fw_filename: Path, force: bool = False):
|
69
|
-
"""Add a firmware to the firmware folder.
|
70
|
-
stored in the port folder, with the same filename as the source.
|
71
|
-
"""
|
72
|
-
if fw_filename.exists() and not force:
|
73
|
-
log.error(f" {fw_filename} already exists. Use --force to overwrite")
|
74
|
-
return False
|
75
|
-
fw_filename.parent.mkdir(exist_ok=True)
|
76
|
-
if isinstance(source, Path):
|
77
|
-
if not source.exists():
|
78
|
-
log.error(f"File {source} does not exist")
|
79
|
-
return False
|
80
|
-
# file copy
|
81
|
-
log.debug(f"Copy {source} to {fw_filename}")
|
82
|
-
shutil.copy(source, fw_filename)
|
83
|
-
return True
|
84
|
-
# handle github urls
|
85
|
-
url = rewrite_url(source)
|
86
|
-
if str(source).startswith("http://") or str(source).startswith("https://"):
|
87
|
-
log.debug(f"Download {url} to {fw_filename}")
|
88
|
-
response = requests.get(url)
|
89
|
-
|
90
|
-
if response.status_code == 200:
|
91
|
-
with open(fw_filename, "wb") as file:
|
92
|
-
file.write(response.content)
|
93
|
-
log.info("File downloaded and saved successfully.")
|
94
|
-
return True
|
95
|
-
else:
|
96
|
-
print("Failed to download the file.")
|
97
|
-
return False
|
98
|
-
return False
|
mpflash/mpflash/ask_input.py
DELETED
@@ -1,236 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Interactive input for mpflash.
|
3
|
-
|
4
|
-
Note: The prompts can use "{version}" and "{action}" to insert the version and action in the prompt without needing an f-string.
|
5
|
-
The values are provided from the answers dictionary.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import List, Sequence, Tuple, Union
|
9
|
-
|
10
|
-
from loguru import logger as log
|
11
|
-
|
12
|
-
from .common import DownloadParams, FlashParams, ParamType
|
13
|
-
from .config import config
|
14
|
-
from .mpboard_id import (get_known_boards_for_port, get_known_ports,
|
15
|
-
known_stored_boards)
|
16
|
-
from .mpremoteboard import MPRemoteBoard
|
17
|
-
from .versions import micropython_versions
|
18
|
-
|
19
|
-
|
20
|
-
def ask_missing_params(
|
21
|
-
params: ParamType,
|
22
|
-
) -> ParamType:
|
23
|
-
"""
|
24
|
-
Asks the user for parameters that have not been supplied on the commandline and returns the updated params.
|
25
|
-
|
26
|
-
Args:
|
27
|
-
params (ParamType): The parameters to be updated.
|
28
|
-
|
29
|
-
Returns:
|
30
|
-
ParamType: The updated parameters.
|
31
|
-
"""
|
32
|
-
if not config.interactive:
|
33
|
-
# no interactivity allowed
|
34
|
-
log.info("Interactive mode disabled. Skipping ask for user input.")
|
35
|
-
return params
|
36
|
-
|
37
|
-
import inquirer
|
38
|
-
|
39
|
-
log.trace(f"ask_missing_params: {params}")
|
40
|
-
|
41
|
-
# if action flash, single input
|
42
|
-
# if action download, multiple input
|
43
|
-
multi_select = isinstance(params, DownloadParams)
|
44
|
-
action = "download" if isinstance(params, DownloadParams) else "flash"
|
45
|
-
|
46
|
-
questions = []
|
47
|
-
answers: dict[str, Union[str, List]] = {"action": action}
|
48
|
-
if not multi_select:
|
49
|
-
if not params.serial or "?" in params.serial:
|
50
|
-
questions.append(ask_serialport(multi_select=False, bluetooth=False))
|
51
|
-
else:
|
52
|
-
answers["serial"] = params.serial
|
53
|
-
|
54
|
-
if params.versions == [] or "?" in params.versions:
|
55
|
-
questions.append(ask_mp_version(multi_select=multi_select, action=action))
|
56
|
-
else:
|
57
|
-
# versions is used to show only the boards for the selected versions
|
58
|
-
answers["versions"] = params.versions # type: ignore
|
59
|
-
|
60
|
-
if not params.boards or "?" in params.boards:
|
61
|
-
questions.extend(ask_port_board(multi_select=multi_select, action=action))
|
62
|
-
if questions:
|
63
|
-
answers = inquirer.prompt(questions, answers=answers) # type: ignore
|
64
|
-
if not answers:
|
65
|
-
# input cancelled by user
|
66
|
-
return [] # type: ignore
|
67
|
-
log.trace(f"answers: {answers}")
|
68
|
-
if isinstance(params, FlashParams) and "serial" in answers:
|
69
|
-
if isinstance(answers["serial"], str):
|
70
|
-
answers["serial"] = [answers["serial"]]
|
71
|
-
params.serial = [s.split()[0] for s in answers["serial"]] # split to remove the description
|
72
|
-
if "port" in answers:
|
73
|
-
# params.ports = [p for p in params.ports if p != "?"] # remove the "?" if present
|
74
|
-
if isinstance(answers["port"], str):
|
75
|
-
params.ports.append(answers["port"])
|
76
|
-
elif isinstance(answers["port"], list): # type: ignore
|
77
|
-
params.ports.extend(answers["port"])
|
78
|
-
else:
|
79
|
-
raise ValueError(f"Unexpected type for answers['port']: {type(answers['port'])}")
|
80
|
-
|
81
|
-
if "boards" in answers:
|
82
|
-
params.boards = [b for b in params.boards if b != "?"] # remove the "?" if present
|
83
|
-
params.boards.extend(answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]])
|
84
|
-
if "versions" in answers:
|
85
|
-
params.versions = [v for v in params.versions if v != "?"] # remove the "?" if present
|
86
|
-
# make sure it is a list
|
87
|
-
if isinstance(answers["versions"], (list, tuple)):
|
88
|
-
params.versions.extend(answers["versions"])
|
89
|
-
else:
|
90
|
-
params.versions.append(answers["versions"])
|
91
|
-
# remove duplicates
|
92
|
-
params.ports = list(set(params.ports))
|
93
|
-
params.boards = list(set(params.boards))
|
94
|
-
params.versions = list(set(params.versions))
|
95
|
-
log.trace(f"ask_missing_params returns: {params}")
|
96
|
-
|
97
|
-
return params
|
98
|
-
|
99
|
-
|
100
|
-
def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
|
101
|
-
"""
|
102
|
-
Filters the known boards based on the selected versions and returns the filtered boards.
|
103
|
-
|
104
|
-
Args:
|
105
|
-
answers (dict): The user's answers.
|
106
|
-
|
107
|
-
Returns:
|
108
|
-
Sequence[Tuple[str, str]]: The filtered boards.
|
109
|
-
"""
|
110
|
-
versions = None
|
111
|
-
# if version is not asked ; then need to get the version from the inputs
|
112
|
-
if "versions" in answers:
|
113
|
-
versions = list(answers["versions"])
|
114
|
-
if "stable" in versions:
|
115
|
-
versions.remove("stable")
|
116
|
-
versions.append(micropython_versions()[-2]) # latest stable
|
117
|
-
elif "preview" in versions:
|
118
|
-
versions.remove("preview")
|
119
|
-
versions.extend((micropython_versions()[-1], micropython_versions()[-2])) # latest preview and stable
|
120
|
-
|
121
|
-
some_boards = known_stored_boards(answers["port"], versions) # or known_mp_boards(answers["port"])
|
122
|
-
|
123
|
-
if some_boards:
|
124
|
-
# Create a dictionary where the keys are the second elements of the tuples
|
125
|
-
# This will automatically remove duplicates because dictionaries cannot have duplicate keys
|
126
|
-
unique_dict = {item[1]: item for item in some_boards}
|
127
|
-
# Get the values of the dictionary, which are the unique items from the original list
|
128
|
-
some_boards = list(unique_dict.values())
|
129
|
-
else:
|
130
|
-
some_boards = [(f"No {answers['port']} boards found for version(s) {versions}", "")]
|
131
|
-
return some_boards
|
132
|
-
|
133
|
-
|
134
|
-
def ask_port_board(*, multi_select: bool, action: str):
|
135
|
-
"""
|
136
|
-
Asks the user for the port and board selection.
|
137
|
-
|
138
|
-
Args:
|
139
|
-
questions (list): The list of questions to be asked.
|
140
|
-
action (str): The action to be performed.
|
141
|
-
|
142
|
-
Returns:
|
143
|
-
None
|
144
|
-
"""
|
145
|
-
# import only when needed to reduce load time
|
146
|
-
import inquirer
|
147
|
-
|
148
|
-
# if action flash, single input
|
149
|
-
# if action download, multiple input
|
150
|
-
inquirer_ux = inquirer.Checkbox if multi_select else inquirer.List
|
151
|
-
return [
|
152
|
-
inquirer.List(
|
153
|
-
"port",
|
154
|
-
message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
|
155
|
-
choices=get_known_ports(),
|
156
|
-
# autocomplete=True,
|
157
|
-
),
|
158
|
-
inquirer_ux(
|
159
|
-
"boards",
|
160
|
-
message=(
|
161
|
-
"Which {port} board firmware do you want to {action} " + "to {serial} ?" if action == "flash" else "?"
|
162
|
-
),
|
163
|
-
choices=filter_matching_boards,
|
164
|
-
validate=at_least_one_validation, # type: ignore
|
165
|
-
# validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
|
166
|
-
),
|
167
|
-
]
|
168
|
-
|
169
|
-
def at_least_one_validation(answers, current) -> bool:
|
170
|
-
import inquirer.errors
|
171
|
-
if not current:
|
172
|
-
raise inquirer.errors.ValidationError("", reason="Please select at least one item.")
|
173
|
-
if isinstance(current, list) and not any(current):
|
174
|
-
raise inquirer.errors.ValidationError("", reason="Please select at least one item.")
|
175
|
-
return True
|
176
|
-
|
177
|
-
def ask_mp_version(multi_select: bool, action: str):
|
178
|
-
"""
|
179
|
-
Asks the user for the version selection.
|
180
|
-
|
181
|
-
Args:
|
182
|
-
questions (list): The list of questions to be asked.
|
183
|
-
action (str): The action to be performed.
|
184
|
-
|
185
|
-
Returns:
|
186
|
-
|
187
|
-
"""
|
188
|
-
# import only when needed to reduce load time
|
189
|
-
import inquirer
|
190
|
-
import inquirer.errors
|
191
|
-
|
192
|
-
input_ux = inquirer.Checkbox if multi_select else inquirer.List
|
193
|
-
|
194
|
-
mp_versions: List[str] = micropython_versions()
|
195
|
-
mp_versions.reverse() # newest first
|
196
|
-
|
197
|
-
# remove the versions for which there are no known boards in the board_info.json
|
198
|
-
# todo: this may be a little slow
|
199
|
-
mp_versions = [v for v in mp_versions if "preview" in v or get_known_boards_for_port("stm32", [v])]
|
200
|
-
|
201
|
-
message = "Which version(s) do you want to {action} " + ("to {serial} ?" if action == "flash" else "?")
|
202
|
-
q = input_ux(
|
203
|
-
# inquirer.List(
|
204
|
-
"versions",
|
205
|
-
message=message,
|
206
|
-
# Hints would be nice , but needs a hint for each and every option
|
207
|
-
# hints=["Use space to select multiple options"],
|
208
|
-
choices=mp_versions,
|
209
|
-
autocomplete=True,
|
210
|
-
validate=at_least_one_validation, # type: ignore
|
211
|
-
)
|
212
|
-
return q
|
213
|
-
|
214
|
-
|
215
|
-
def ask_serialport(*, multi_select: bool = False, bluetooth: bool = False):
|
216
|
-
"""
|
217
|
-
Asks the user for the serial port selection.
|
218
|
-
|
219
|
-
Args:
|
220
|
-
questions (list): The list of questions to be asked.
|
221
|
-
action (str): The action to be performed.
|
222
|
-
|
223
|
-
Returns:
|
224
|
-
None
|
225
|
-
"""
|
226
|
-
# import only when needed to reduce load time
|
227
|
-
import inquirer
|
228
|
-
|
229
|
-
comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True)
|
230
|
-
return inquirer.List(
|
231
|
-
"serial",
|
232
|
-
message="Which serial port do you want to {action} ?",
|
233
|
-
choices=comports,
|
234
|
-
other=True,
|
235
|
-
validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
|
236
|
-
)
|
mpflash/mpflash/basicgit.py
DELETED
@@ -1,324 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Simple Git module, where needed via powershell
|
3
|
-
|
4
|
-
Some of the functions are based on the PyGithub module
|
5
|
-
"""
|
6
|
-
|
7
|
-
import os
|
8
|
-
import subprocess
|
9
|
-
from pathlib import Path
|
10
|
-
from typing import List, Optional, Union
|
11
|
-
|
12
|
-
import cachetools.func
|
13
|
-
from github import Auth, BadCredentialsException, Github
|
14
|
-
from loguru import logger as log
|
15
|
-
from packaging.version import parse
|
16
|
-
|
17
|
-
# from mpflash.versions import SET_PREVIEW
|
18
|
-
|
19
|
-
# Token with no permissions to avoid throttling
|
20
|
-
# https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#getting-a-higher-rate-limit
|
21
|
-
PAT_NO_ACCESS = (
|
22
|
-
"github_pat_"
|
23
|
-
+ "11AAHPVFQ0G4NTaQ73Bw5J"
|
24
|
-
+ "_fAp7K9sZ1qL8VFnI9g78eUlCdmOXHB3WzSdj2jtEYb4XF3N7PDJBl32qIxq"
|
25
|
-
)
|
26
|
-
PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
|
27
|
-
GH_CLIENT = Github(auth=Auth.Token(PAT))
|
28
|
-
|
29
|
-
|
30
|
-
def _run_local_git(
|
31
|
-
cmd: List[str],
|
32
|
-
repo: Optional[Union[Path, str]] = None,
|
33
|
-
expect_stderr=False,
|
34
|
-
capture_output=True,
|
35
|
-
echo_output=True,
|
36
|
-
):
|
37
|
-
"run a external (git) command in the repo's folder and deal with some of the errors"
|
38
|
-
try:
|
39
|
-
if repo:
|
40
|
-
if isinstance(repo, str):
|
41
|
-
repo = Path(repo)
|
42
|
-
result = subprocess.run(
|
43
|
-
cmd,
|
44
|
-
capture_output=capture_output,
|
45
|
-
check=True,
|
46
|
-
cwd=repo.absolute().as_posix(),
|
47
|
-
encoding="utf-8",
|
48
|
-
)
|
49
|
-
else:
|
50
|
-
result = subprocess.run(
|
51
|
-
cmd, capture_output=capture_output, check=True, encoding="utf-8"
|
52
|
-
)
|
53
|
-
except (NotADirectoryError, FileNotFoundError) as e: # pragma: no cover
|
54
|
-
return None
|
55
|
-
except subprocess.CalledProcessError as e: # pragma: no cover
|
56
|
-
# add some logging for github actions
|
57
|
-
log.error(f"{str(e)} : { e.stderr}")
|
58
|
-
return None
|
59
|
-
if result.stderr and result.stderr != b"":
|
60
|
-
stderr = result.stderr
|
61
|
-
if "cloning into" in stderr.lower():
|
62
|
-
# log.info(stderr)
|
63
|
-
expect_stderr = True
|
64
|
-
if "warning" in stderr.lower():
|
65
|
-
log.warning(stderr)
|
66
|
-
expect_stderr = True
|
67
|
-
elif capture_output and echo_output: # pragma: no cover
|
68
|
-
log.info(stderr)
|
69
|
-
if not expect_stderr:
|
70
|
-
raise ChildProcessError(stderr)
|
71
|
-
|
72
|
-
if result.returncode and result.returncode < 0:
|
73
|
-
raise ChildProcessError(result.stderr)
|
74
|
-
return result
|
75
|
-
|
76
|
-
|
77
|
-
def clone(remote_repo: str, path: Path, shallow: bool = False, tag: Optional[str] = None) -> bool:
|
78
|
-
"""git clone [--depth 1] [--branch <tag_name>] <remote> <directory>"""
|
79
|
-
cmd = ["git", "clone"]
|
80
|
-
if shallow:
|
81
|
-
cmd += ["--depth", "1"]
|
82
|
-
if tag in {"preview", "latest", "master"}:
|
83
|
-
tag = None
|
84
|
-
cmd += [remote_repo, "--branch", tag, str(path)] if tag else [remote_repo, str(path)]
|
85
|
-
if result := _run_local_git(cmd, expect_stderr=True, capture_output=False):
|
86
|
-
return result.returncode == 0
|
87
|
-
else:
|
88
|
-
return False
|
89
|
-
|
90
|
-
|
91
|
-
def get_local_tag(
|
92
|
-
repo: Optional[Union[str, Path]] = None, abbreviate: bool = True
|
93
|
-
) -> Union[str, None]:
|
94
|
-
"""
|
95
|
-
get the most recent git version tag of a local repo
|
96
|
-
repo Path should be in the form of : repo = "./repo/micropython"
|
97
|
-
|
98
|
-
returns the tag or None
|
99
|
-
"""
|
100
|
-
if not repo:
|
101
|
-
repo = Path(".")
|
102
|
-
elif isinstance(repo, str):
|
103
|
-
repo = Path(repo)
|
104
|
-
|
105
|
-
result = _run_local_git(
|
106
|
-
# ["git", "describe", "--tags"],
|
107
|
-
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
108
|
-
repo=repo.as_posix(),
|
109
|
-
expect_stderr=True,
|
110
|
-
)
|
111
|
-
if not result:
|
112
|
-
return None
|
113
|
-
tag: str = result.stdout
|
114
|
-
tag = tag.replace("\r", "").replace("\n", "")
|
115
|
-
if not abbreviate or "-" not in tag:
|
116
|
-
return tag
|
117
|
-
if "-preview" in tag:
|
118
|
-
tag = tag.split("-preview")[0] + "-preview"
|
119
|
-
return tag
|
120
|
-
|
121
|
-
|
122
|
-
def get_local_tags(repo: Optional[Path] = None, minver: Optional[str] = None) -> List[str]:
|
123
|
-
"""
|
124
|
-
get list of all tags of a local repo
|
125
|
-
"""
|
126
|
-
if not repo:
|
127
|
-
repo = Path(".")
|
128
|
-
|
129
|
-
result = _run_local_git(["git", "tag", "-l"], repo=repo.as_posix(), expect_stderr=True)
|
130
|
-
if not result or result.returncode != 0:
|
131
|
-
return []
|
132
|
-
tags = result.stdout.replace("\r", "").split("\n")
|
133
|
-
tags = [tag for tag in tags if tag.startswith("v")]
|
134
|
-
if minver:
|
135
|
-
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
136
|
-
return sorted(tags)
|
137
|
-
|
138
|
-
|
139
|
-
from github.GithubException import BadCredentialsException
|
140
|
-
|
141
|
-
|
142
|
-
@cachetools.func.ttl_cache(maxsize=16, ttl=60) # 60 seconds
|
143
|
-
def get_tags(repo: str, minver: Optional[str] = None) -> List[str]:
|
144
|
-
"""
|
145
|
-
Get list of tag of a repote github repo.
|
146
|
-
only the last -preview tag is kept
|
147
|
-
"""
|
148
|
-
if not repo or not isinstance(repo, str) or "/" not in repo: # type: ignore
|
149
|
-
return []
|
150
|
-
try:
|
151
|
-
gh_repo = GH_CLIENT.get_repo(repo)
|
152
|
-
except BadCredentialsException as e:
|
153
|
-
log.error(f"Github authentication error - {e}")
|
154
|
-
return []
|
155
|
-
except ConnectionError as e:
|
156
|
-
# TODO: unable to capture the exeption
|
157
|
-
log.warning(f"Unable to get tags - {e}")
|
158
|
-
return []
|
159
|
-
tags = [tag.name for tag in gh_repo.get_tags()]
|
160
|
-
if minver:
|
161
|
-
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
162
|
-
# remove all but the last preview
|
163
|
-
tags = [t for t in sorted(tags[:-1]) if "-preview" not in t] + sorted(tags)[-1:]
|
164
|
-
return tags
|
165
|
-
|
166
|
-
|
167
|
-
def checkout_tag(tag: str, repo: Optional[Union[str, Path]] = None) -> bool:
|
168
|
-
"""
|
169
|
-
checkout a specific git tag
|
170
|
-
"""
|
171
|
-
cmd = ["git", "checkout", tag, "--quiet", "--force"]
|
172
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True, capture_output=True)
|
173
|
-
if not result:
|
174
|
-
return False
|
175
|
-
# actually a good result
|
176
|
-
msg = {result.stdout}
|
177
|
-
if msg != {""}:
|
178
|
-
log.warning(f"git message: {msg}")
|
179
|
-
return True
|
180
|
-
|
181
|
-
|
182
|
-
def sync_submodules(repo: Union[Path, str]) -> bool:
|
183
|
-
"""
|
184
|
-
make sure any submodules are in sync
|
185
|
-
"""
|
186
|
-
cmds = [
|
187
|
-
["git", "submodule", "sync", "--quiet"],
|
188
|
-
# ["git", "submodule", "update", "--quiet"],
|
189
|
-
["git", "submodule", "update", "--init", "lib/micropython-lib"],
|
190
|
-
]
|
191
|
-
for cmd in cmds:
|
192
|
-
if result := _run_local_git(cmd, repo=repo, expect_stderr=True):
|
193
|
-
# actually a good result
|
194
|
-
log.debug(result.stderr)
|
195
|
-
else:
|
196
|
-
return False
|
197
|
-
checkout_arduino_lib(Path(repo))
|
198
|
-
return True
|
199
|
-
|
200
|
-
|
201
|
-
def checkout_arduino_lib(mpy_path: Path):
|
202
|
-
"""
|
203
|
-
Checkout the arduino-lib submodule repo if it exists
|
204
|
-
|
205
|
-
This is needed as some of the arduino boards freeze modules originationg from the arduino-lib
|
206
|
-
"""
|
207
|
-
# arduino_lib_path = mpy_path / "lib/arduino-lib"
|
208
|
-
if (mpy_path / "lib/arduino-lib").exists():
|
209
|
-
cmd = ["git", "submodule", "update", "--init", "lib/arduino-lib"]
|
210
|
-
try:
|
211
|
-
result = subprocess.run(cmd, cwd=mpy_path, check=True)
|
212
|
-
log.info(f"checkout arduino-lib: {result.returncode}")
|
213
|
-
except subprocess.CalledProcessError as e:
|
214
|
-
log.warning("Could not check out arduino-lib, error: ", e)
|
215
|
-
|
216
|
-
|
217
|
-
def checkout_commit(commit_hash: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
218
|
-
"""
|
219
|
-
Checkout a specific commit
|
220
|
-
"""
|
221
|
-
cmd = ["git", "checkout", commit_hash, "--quiet", "--force"]
|
222
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
223
|
-
if not result:
|
224
|
-
return False
|
225
|
-
# actually a good result
|
226
|
-
log.debug(result.stderr)
|
227
|
-
return True
|
228
|
-
|
229
|
-
|
230
|
-
def switch_tag(tag: Union[str, Path], repo: Optional[Union[Path, str]] = None) -> bool:
|
231
|
-
"""
|
232
|
-
switch to the specified version tag of a local repo
|
233
|
-
repo should be in the form of : path/.git
|
234
|
-
repo = '../micropython/.git'
|
235
|
-
returns None
|
236
|
-
"""
|
237
|
-
|
238
|
-
cmd = ["git", "switch", "--detach", tag, "--quiet", "--force"]
|
239
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
240
|
-
if not result:
|
241
|
-
return False
|
242
|
-
# actually a good result
|
243
|
-
log.debug(result.stderr)
|
244
|
-
return True
|
245
|
-
|
246
|
-
|
247
|
-
def switch_branch(branch: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
248
|
-
"""
|
249
|
-
switch to the specified branch in a local repo"
|
250
|
-
repo should be in the form of : path/.git
|
251
|
-
repo = '../micropython/.git'
|
252
|
-
returns None
|
253
|
-
"""
|
254
|
-
cmd = ["git", "switch", branch, "--quiet", "--force"]
|
255
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
256
|
-
if not result:
|
257
|
-
return False
|
258
|
-
# actually a good result
|
259
|
-
log.debug(result.stderr)
|
260
|
-
return True
|
261
|
-
|
262
|
-
|
263
|
-
def fetch(repo: Union[Path, str]) -> bool:
|
264
|
-
"""
|
265
|
-
fetches a repo and all tags
|
266
|
-
repo should be in the form of : path/.git
|
267
|
-
repo = '../micropython/.git'
|
268
|
-
returns True on success
|
269
|
-
"""
|
270
|
-
if not repo:
|
271
|
-
raise NotADirectoryError
|
272
|
-
|
273
|
-
cmd = ["git", "fetch", "--all", "--tags", "--quiet"]
|
274
|
-
result = _run_local_git(cmd, repo=repo, echo_output=False)
|
275
|
-
return result.returncode == 0 if result else False
|
276
|
-
|
277
|
-
|
278
|
-
def pull(repo: Union[Path, str], branch: str = "main") -> bool:
|
279
|
-
"""
|
280
|
-
pull a repo origin into main
|
281
|
-
repo should be in the form of : path/.git
|
282
|
-
repo = '../micropython/.git'
|
283
|
-
returns True on success
|
284
|
-
"""
|
285
|
-
if not repo:
|
286
|
-
raise NotADirectoryError
|
287
|
-
repo = Path(repo)
|
288
|
-
# first checkout HEAD
|
289
|
-
cmd = ["git", "checkout", branch, "--quiet", "--force"]
|
290
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
291
|
-
if not result:
|
292
|
-
log.error("error during git checkout main", result)
|
293
|
-
return False
|
294
|
-
|
295
|
-
cmd = ["git", "pull", "origin", branch, "--quiet", "--autostash"]
|
296
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
297
|
-
if not result:
|
298
|
-
log.error("error durign pull", result)
|
299
|
-
return False
|
300
|
-
return result.returncode == 0
|
301
|
-
|
302
|
-
|
303
|
-
def get_git_describe(folder: Optional[str] = None):
|
304
|
-
"""
|
305
|
-
Based on MicroPython makeversionhdr.py
|
306
|
-
returns : current git tag, commits ,commit hash : "v1.19.1-841-g3446"
|
307
|
-
"""
|
308
|
-
# Note: git describe doesn't work if no tag is available
|
309
|
-
try:
|
310
|
-
git_describe = subprocess.check_output(
|
311
|
-
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
312
|
-
stderr=subprocess.STDOUT,
|
313
|
-
universal_newlines=True,
|
314
|
-
cwd=folder,
|
315
|
-
).strip()
|
316
|
-
except subprocess.CalledProcessError as er:
|
317
|
-
if er.returncode == 128:
|
318
|
-
# git exit code of 128 means no repository found
|
319
|
-
return None
|
320
|
-
git_describe = ""
|
321
|
-
except OSError:
|
322
|
-
return None
|
323
|
-
# format
|
324
|
-
return git_describe
|