micropython-stubber 1.20.4__py3-none-any.whl → 1.20.6__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.20.4.dist-info → micropython_stubber-1.20.6.dist-info}/LICENSE +30 -30
- {micropython_stubber-1.20.4.dist-info → micropython_stubber-1.20.6.dist-info}/METADATA +4 -4
- micropython_stubber-1.20.6.dist-info/RECORD +159 -0
- mpflash/README.md +184 -184
- mpflash/libusb_flash.ipynb +203 -203
- mpflash/mpflash/add_firmware.py +98 -98
- mpflash/mpflash/ask_input.py +236 -226
- mpflash/mpflash/bootloader/__init__.py +37 -0
- mpflash/mpflash/bootloader/manual.py +102 -0
- mpflash/mpflash/bootloader/micropython.py +10 -0
- mpflash/mpflash/bootloader/touch1200.py +45 -0
- mpflash/mpflash/cli_download.py +129 -128
- mpflash/mpflash/cli_flash.py +219 -212
- mpflash/mpflash/cli_group.py +98 -92
- mpflash/mpflash/cli_list.py +81 -77
- mpflash/mpflash/cli_main.py +41 -38
- mpflash/mpflash/common.py +164 -151
- mpflash/mpflash/config.py +47 -31
- mpflash/mpflash/connected.py +74 -74
- mpflash/mpflash/download.py +360 -361
- mpflash/mpflash/downloaded.py +129 -129
- mpflash/mpflash/errors.py +9 -5
- mpflash/mpflash/flash.py +52 -69
- mpflash/mpflash/flash_esp.py +59 -59
- mpflash/mpflash/flash_stm32.py +24 -24
- mpflash/mpflash/flash_stm32_cube.py +111 -111
- mpflash/mpflash/flash_stm32_dfu.py +101 -101
- mpflash/mpflash/flash_uf2.py +67 -67
- mpflash/mpflash/flash_uf2_boardid.py +15 -15
- mpflash/mpflash/flash_uf2_linux.py +123 -123
- mpflash/mpflash/flash_uf2_macos.py +34 -37
- mpflash/mpflash/flash_uf2_windows.py +34 -34
- mpflash/mpflash/list.py +89 -89
- mpflash/mpflash/logger.py +41 -41
- mpflash/mpflash/mpboard_id/__init__.py +93 -93
- mpflash/mpflash/mpboard_id/add_boards.py +255 -255
- mpflash/mpflash/mpboard_id/board.py +37 -37
- mpflash/mpflash/mpboard_id/board_id.py +86 -86
- mpflash/mpflash/mpboard_id/store.py +43 -43
- mpflash/mpflash/mpremoteboard/__init__.py +221 -221
- mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -141
- mpflash/mpflash/mpremoteboard/runner.py +140 -140
- mpflash/mpflash/uf2disk.py +12 -12
- mpflash/mpflash/vendor/basicgit.py +288 -288
- mpflash/mpflash/vendor/click_aliases.py +91 -0
- mpflash/mpflash/vendor/dfu.py +165 -165
- mpflash/mpflash/vendor/pydfu.py +605 -605
- mpflash/mpflash/vendor/readme.md +2 -2
- mpflash/mpflash/vendor/versions.py +119 -117
- mpflash/mpflash/worklist.py +170 -170
- mpflash/poetry.lock +1588 -1623
- mpflash/pyproject.toml +60 -60
- mpflash/stm32_udev_rules.md +62 -62
- stubber/__init__.py +3 -3
- stubber/basicgit.py +294 -288
- stubber/board/board_info.csv +193 -193
- stubber/board/boot.py +34 -34
- stubber/board/createstubs.py +986 -987
- stubber/board/createstubs_db.py +825 -826
- stubber/board/createstubs_db_min.py +331 -331
- stubber/board/createstubs_db_mpy.mpy +0 -0
- stubber/board/createstubs_lvgl.py +741 -741
- stubber/board/createstubs_lvgl_min.py +741 -741
- stubber/board/createstubs_mem.py +766 -767
- stubber/board/createstubs_mem_min.py +306 -306
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +294 -294
- stubber/board/createstubs_mpy.mpy +0 -0
- stubber/board/fw_info.py +141 -141
- stubber/board/info.py +183 -183
- stubber/board/main.py +19 -19
- stubber/board/modulelist.txt +247 -247
- stubber/board/pyrightconfig.json +34 -34
- stubber/bulk/mcu_stubber.py +454 -455
- stubber/codemod/_partials/__init__.py +48 -50
- stubber/codemod/_partials/db_main.py +147 -147
- stubber/codemod/_partials/lvgl_main.py +77 -77
- stubber/codemod/_partials/modules_reader.py +80 -80
- stubber/codemod/add_comment.py +53 -53
- stubber/codemod/add_method.py +65 -65
- stubber/codemod/board.py +317 -317
- stubber/codemod/enrich.py +145 -145
- stubber/codemod/merge_docstub.py +284 -284
- stubber/codemod/modify_list.py +54 -54
- stubber/codemod/utils.py +57 -57
- stubber/commands/build_cmd.py +94 -94
- stubber/commands/cli.py +51 -51
- stubber/commands/clone_cmd.py +66 -66
- stubber/commands/config_cmd.py +29 -29
- stubber/commands/enrich_folder_cmd.py +70 -70
- stubber/commands/get_core_cmd.py +69 -69
- stubber/commands/get_docstubs_cmd.py +87 -87
- stubber/commands/get_frozen_cmd.py +112 -112
- stubber/commands/get_mcu_cmd.py +56 -56
- stubber/commands/merge_cmd.py +66 -66
- stubber/commands/publish_cmd.py +119 -119
- stubber/commands/stub_cmd.py +30 -30
- stubber/commands/switch_cmd.py +54 -54
- stubber/commands/variants_cmd.py +48 -48
- stubber/cst_transformer.py +178 -178
- stubber/data/board_info.csv +193 -193
- stubber/data/board_info.json +1729 -1729
- stubber/data/micropython_tags.csv +15 -15
- stubber/data/requirements-core-micropython.txt +38 -38
- stubber/data/requirements-core-pycopy.txt +39 -39
- stubber/downloader.py +36 -36
- stubber/freeze/common.py +68 -68
- stubber/freeze/freeze_folder.py +69 -69
- stubber/freeze/freeze_manifest_2.py +113 -113
- stubber/freeze/get_frozen.py +127 -127
- stubber/get_cpython.py +101 -101
- stubber/get_lobo.py +59 -59
- stubber/minify.py +418 -418
- stubber/publish/bump.py +86 -86
- stubber/publish/candidates.py +262 -283
- stubber/publish/database.py +18 -18
- stubber/publish/defaults.py +45 -45
- stubber/publish/enums.py +24 -30
- stubber/publish/helpers.py +29 -29
- stubber/publish/merge_docstubs.py +130 -130
- stubber/publish/missing_class_methods.py +49 -49
- stubber/publish/package.py +146 -177
- stubber/publish/pathnames.py +51 -51
- stubber/publish/publish.py +120 -121
- stubber/publish/pypi.py +38 -38
- stubber/publish/stubpackage.py +1029 -1029
- stubber/rst/__init__.py +9 -9
- stubber/rst/classsort.py +77 -77
- stubber/rst/lookup.py +530 -530
- stubber/rst/output_dict.py +401 -401
- stubber/rst/reader.py +822 -823
- stubber/rst/report_return.py +69 -69
- stubber/rst/rst_utils.py +540 -540
- stubber/stubber.py +38 -38
- stubber/stubs_from_docs.py +90 -90
- stubber/tools/manifestfile.py +610 -610
- stubber/tools/readme.md +5 -5
- stubber/update_fallback.py +117 -117
- stubber/update_module_list.py +123 -125
- stubber/utils/__init__.py +5 -5
- stubber/utils/config.py +127 -127
- stubber/utils/makeversionhdr.py +54 -54
- stubber/utils/manifest.py +92 -92
- stubber/utils/post.py +79 -79
- stubber/utils/repos.py +157 -154
- stubber/utils/stubmaker.py +139 -139
- stubber/utils/typed_config_toml.py +77 -77
- stubber/utils/versions.py +128 -120
- stubber/variants.py +106 -106
- micropython_stubber-1.20.4.dist-info/RECORD +0 -154
- {micropython_stubber-1.20.4.dist-info → micropython_stubber-1.20.6.dist-info}/WHEEL +0 -0
- {micropython_stubber-1.20.4.dist-info → micropython_stubber-1.20.6.dist-info}/entry_points.txt +0 -0
@@ -1,288 +1,288 @@
|
|
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, Github
|
14
|
-
from loguru import logger as log
|
15
|
-
from packaging.version import parse
|
16
|
-
|
17
|
-
# from stubber.utils.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" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
|
23
|
-
)
|
24
|
-
PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
|
25
|
-
GH_CLIENT = Github(auth=Auth.Token(PAT))
|
26
|
-
|
27
|
-
|
28
|
-
def _run_local_git(
|
29
|
-
cmd: List[str],
|
30
|
-
repo: Optional[Union[Path, str]] = None,
|
31
|
-
expect_stderr=False,
|
32
|
-
capture_output=True,
|
33
|
-
echo_output=True,
|
34
|
-
):
|
35
|
-
"run a external (git) command in the repo's folder and deal with some of the errors"
|
36
|
-
try:
|
37
|
-
if repo:
|
38
|
-
if isinstance(repo, str):
|
39
|
-
repo = Path(repo)
|
40
|
-
result = subprocess.run(
|
41
|
-
cmd, capture_output=capture_output, check=True, cwd=repo.absolute().as_posix(), encoding="utf-8"
|
42
|
-
)
|
43
|
-
else:
|
44
|
-
result = subprocess.run(cmd, capture_output=capture_output, check=True, encoding="utf-8")
|
45
|
-
except (NotADirectoryError, FileNotFoundError) as e: # pragma: no cover
|
46
|
-
return None
|
47
|
-
except subprocess.CalledProcessError as e: # pragma: no cover
|
48
|
-
# add some logging for github actions
|
49
|
-
log.error(f"{str(e)} : { e.stderr}")
|
50
|
-
return None
|
51
|
-
if result.stderr and result.stderr != b"":
|
52
|
-
stderr = result.stderr
|
53
|
-
if "cloning into" in stderr.lower():
|
54
|
-
# log.info(stderr)
|
55
|
-
expect_stderr = True
|
56
|
-
if "warning" in stderr.lower():
|
57
|
-
log.warning(stderr)
|
58
|
-
expect_stderr = True
|
59
|
-
elif capture_output and echo_output: # pragma: no cover
|
60
|
-
log.info(stderr)
|
61
|
-
if not expect_stderr:
|
62
|
-
raise ChildProcessError(stderr)
|
63
|
-
|
64
|
-
if result.returncode and result.returncode < 0:
|
65
|
-
raise ChildProcessError(result.stderr)
|
66
|
-
return result
|
67
|
-
|
68
|
-
|
69
|
-
def clone(remote_repo: str, path: Path, shallow: bool = False, tag: Optional[str] = None) -> bool:
|
70
|
-
"""git clone [--depth 1] [--branch <tag_name>] <remote> <directory>"""
|
71
|
-
cmd = ["git", "clone"]
|
72
|
-
if shallow:
|
73
|
-
cmd += ["--depth", "1"]
|
74
|
-
if tag in {"preview", "latest", "master"}:
|
75
|
-
tag = None
|
76
|
-
cmd += [remote_repo, "--branch", tag, str(path)] if tag else [remote_repo, str(path)]
|
77
|
-
if result := _run_local_git(cmd, expect_stderr=True, capture_output=False):
|
78
|
-
return result.returncode == 0
|
79
|
-
else:
|
80
|
-
return False
|
81
|
-
|
82
|
-
|
83
|
-
def get_local_tag(repo: Optional[Union[str, Path]] = None, abbreviate: bool = True) -> Union[str, None]:
|
84
|
-
"""
|
85
|
-
get the most recent git version tag of a local repo
|
86
|
-
repo Path should be in the form of : repo = "./repo/micropython"
|
87
|
-
|
88
|
-
returns the tag or None
|
89
|
-
"""
|
90
|
-
if not repo:
|
91
|
-
repo = Path(".")
|
92
|
-
elif isinstance(repo, str):
|
93
|
-
repo = Path(repo)
|
94
|
-
|
95
|
-
result = _run_local_git(
|
96
|
-
# ["git", "describe", "--tags"],
|
97
|
-
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
98
|
-
repo=repo.as_posix(),
|
99
|
-
expect_stderr=True,
|
100
|
-
)
|
101
|
-
if not result:
|
102
|
-
return None
|
103
|
-
tag: str = result.stdout
|
104
|
-
tag = tag.replace("\r", "").replace("\n", "")
|
105
|
-
if not abbreviate or "-" not in tag:
|
106
|
-
return tag
|
107
|
-
if "-preview" in tag:
|
108
|
-
tag = tag.split("-preview")[0] + "-preview"
|
109
|
-
return tag
|
110
|
-
|
111
|
-
|
112
|
-
def get_local_tags(repo: Optional[Path] = None, minver: Optional[str] = None) -> List[str]:
|
113
|
-
"""
|
114
|
-
get list of all tags of a local repo
|
115
|
-
"""
|
116
|
-
if not repo:
|
117
|
-
repo = Path(".")
|
118
|
-
|
119
|
-
result = _run_local_git(["git", "tag", "-l"], repo=repo.as_posix(), expect_stderr=True)
|
120
|
-
if not result or result.returncode != 0:
|
121
|
-
return []
|
122
|
-
tags = result.stdout.replace("\r", "").split("\n")
|
123
|
-
tags = [tag for tag in tags if tag.startswith("v")]
|
124
|
-
if minver:
|
125
|
-
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
126
|
-
return sorted(tags)
|
127
|
-
|
128
|
-
|
129
|
-
@cachetools.func.ttl_cache(maxsize=16, ttl=60) # 60 seconds
|
130
|
-
def get_tags(repo: str, minver: Optional[str] = None) -> List[str]:
|
131
|
-
"""
|
132
|
-
Get list of tag of a repote github repo
|
133
|
-
"""
|
134
|
-
if not repo or not isinstance(repo, str) or "/" not in repo: # type: ignore
|
135
|
-
return []
|
136
|
-
try:
|
137
|
-
gh_repo = GH_CLIENT.get_repo(repo)
|
138
|
-
except ConnectionError as e:
|
139
|
-
# TODO: unable to capture the exeption
|
140
|
-
log.warning(f"Unable to get tags - {e}")
|
141
|
-
return []
|
142
|
-
tags = [tag.name for tag in gh_repo.get_tags()]
|
143
|
-
if minver:
|
144
|
-
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
145
|
-
return sorted(tags)
|
146
|
-
|
147
|
-
|
148
|
-
def checkout_tag(tag: str, repo: Optional[Union[str, Path]] = None) -> bool:
|
149
|
-
"""
|
150
|
-
checkout a specific git tag
|
151
|
-
"""
|
152
|
-
cmd = ["git", "checkout", tag, "--quiet", "--force"]
|
153
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True, capture_output=True)
|
154
|
-
if not result:
|
155
|
-
return False
|
156
|
-
# actually a good result
|
157
|
-
msg = {result.stdout}
|
158
|
-
if msg != {""}:
|
159
|
-
log.warning(f"git message: {msg}")
|
160
|
-
return True
|
161
|
-
|
162
|
-
|
163
|
-
def sync_submodules(repo: Optional[Union[Path, str]] = None) -> bool:
|
164
|
-
"""
|
165
|
-
make sure any submodules are in sync
|
166
|
-
"""
|
167
|
-
cmds = [
|
168
|
-
["git", "submodule", "sync", "--quiet"],
|
169
|
-
# ["git", "submodule", "update", "--quiet"],
|
170
|
-
["git", "submodule", "update", "--init", "lib/micropython-lib"],
|
171
|
-
]
|
172
|
-
for cmd in cmds:
|
173
|
-
if result := _run_local_git(cmd, repo=repo, expect_stderr=True):
|
174
|
-
# actually a good result
|
175
|
-
log.debug(result.stderr)
|
176
|
-
else:
|
177
|
-
return False
|
178
|
-
return True
|
179
|
-
|
180
|
-
|
181
|
-
def checkout_commit(commit_hash: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
182
|
-
"""
|
183
|
-
Checkout a specific commit
|
184
|
-
"""
|
185
|
-
cmd = ["git", "checkout", commit_hash, "--quiet", "--force"]
|
186
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
187
|
-
if not result:
|
188
|
-
return False
|
189
|
-
# actually a good result
|
190
|
-
log.debug(result.stderr)
|
191
|
-
return True
|
192
|
-
|
193
|
-
|
194
|
-
def switch_tag(tag: Union[str, Path], repo: Optional[Union[Path, str]] = None) -> bool:
|
195
|
-
"""
|
196
|
-
switch to the specified version tag of a local repo
|
197
|
-
repo should be in the form of : path/.git
|
198
|
-
repo = '../micropython/.git'
|
199
|
-
returns None
|
200
|
-
"""
|
201
|
-
|
202
|
-
cmd = ["git", "switch", "--detach", tag, "--quiet", "--force"]
|
203
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
204
|
-
if not result:
|
205
|
-
return False
|
206
|
-
# actually a good result
|
207
|
-
log.debug(result.stderr)
|
208
|
-
return True
|
209
|
-
|
210
|
-
|
211
|
-
def switch_branch(branch: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
212
|
-
"""
|
213
|
-
switch to the specified branch in a local repo"
|
214
|
-
repo should be in the form of : path/.git
|
215
|
-
repo = '../micropython/.git'
|
216
|
-
returns None
|
217
|
-
"""
|
218
|
-
cmd = ["git", "switch", branch, "--quiet", "--force"]
|
219
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
220
|
-
if not result:
|
221
|
-
return False
|
222
|
-
# actually a good result
|
223
|
-
log.debug(result.stderr)
|
224
|
-
return True
|
225
|
-
|
226
|
-
|
227
|
-
def fetch(repo: Union[Path, str]) -> bool:
|
228
|
-
"""
|
229
|
-
fetches a repo
|
230
|
-
repo should be in the form of : path/.git
|
231
|
-
repo = '../micropython/.git'
|
232
|
-
returns True on success
|
233
|
-
"""
|
234
|
-
if not repo:
|
235
|
-
raise NotADirectoryError
|
236
|
-
|
237
|
-
cmd = ["git", "fetch", "--all", "--tags", "--quiet"]
|
238
|
-
result = _run_local_git(cmd, repo=repo, echo_output=False)
|
239
|
-
return result.returncode == 0 if result else False
|
240
|
-
|
241
|
-
|
242
|
-
def pull(repo: Union[Path, str], branch: str = "main") -> bool:
|
243
|
-
"""
|
244
|
-
pull a repo origin into main
|
245
|
-
repo should be in the form of : path/.git
|
246
|
-
repo = '../micropython/.git'
|
247
|
-
returns True on success
|
248
|
-
"""
|
249
|
-
if not repo:
|
250
|
-
raise NotADirectoryError
|
251
|
-
repo = Path(repo)
|
252
|
-
# first checkout HEAD
|
253
|
-
cmd = ["git", "checkout", branch, "--quiet", "--force"]
|
254
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
255
|
-
if not result:
|
256
|
-
log.error("error during git checkout main", result)
|
257
|
-
return False
|
258
|
-
|
259
|
-
cmd = ["git", "pull", "origin", branch, "--quiet", "--autostash"]
|
260
|
-
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
261
|
-
if not result:
|
262
|
-
log.error("error durign pull", result)
|
263
|
-
return False
|
264
|
-
return result.returncode == 0
|
265
|
-
|
266
|
-
|
267
|
-
def get_git_describe(folder: Optional[str] = None):
|
268
|
-
"""
|
269
|
-
Based on MicroPython makeversionhdr.py
|
270
|
-
returns : current git tag, commits ,commit hash : "v1.19.1-841-g3446"
|
271
|
-
"""
|
272
|
-
# Note: git describe doesn't work if no tag is available
|
273
|
-
try:
|
274
|
-
git_describe = subprocess.check_output(
|
275
|
-
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
276
|
-
stderr=subprocess.STDOUT,
|
277
|
-
universal_newlines=True,
|
278
|
-
cwd=folder,
|
279
|
-
).strip()
|
280
|
-
except subprocess.CalledProcessError as er:
|
281
|
-
if er.returncode == 128:
|
282
|
-
# git exit code of 128 means no repository found
|
283
|
-
return None
|
284
|
-
git_describe = ""
|
285
|
-
except OSError:
|
286
|
-
return None
|
287
|
-
# format
|
288
|
-
return git_describe
|
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, Github
|
14
|
+
from loguru import logger as log
|
15
|
+
from packaging.version import parse
|
16
|
+
|
17
|
+
# from stubber.utils.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" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
|
23
|
+
)
|
24
|
+
PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
|
25
|
+
GH_CLIENT = Github(auth=Auth.Token(PAT))
|
26
|
+
|
27
|
+
|
28
|
+
def _run_local_git(
|
29
|
+
cmd: List[str],
|
30
|
+
repo: Optional[Union[Path, str]] = None,
|
31
|
+
expect_stderr=False,
|
32
|
+
capture_output=True,
|
33
|
+
echo_output=True,
|
34
|
+
):
|
35
|
+
"run a external (git) command in the repo's folder and deal with some of the errors"
|
36
|
+
try:
|
37
|
+
if repo:
|
38
|
+
if isinstance(repo, str):
|
39
|
+
repo = Path(repo)
|
40
|
+
result = subprocess.run(
|
41
|
+
cmd, capture_output=capture_output, check=True, cwd=repo.absolute().as_posix(), encoding="utf-8"
|
42
|
+
)
|
43
|
+
else:
|
44
|
+
result = subprocess.run(cmd, capture_output=capture_output, check=True, encoding="utf-8")
|
45
|
+
except (NotADirectoryError, FileNotFoundError) as e: # pragma: no cover
|
46
|
+
return None
|
47
|
+
except subprocess.CalledProcessError as e: # pragma: no cover
|
48
|
+
# add some logging for github actions
|
49
|
+
log.error(f"{str(e)} : { e.stderr}")
|
50
|
+
return None
|
51
|
+
if result.stderr and result.stderr != b"":
|
52
|
+
stderr = result.stderr
|
53
|
+
if "cloning into" in stderr.lower():
|
54
|
+
# log.info(stderr)
|
55
|
+
expect_stderr = True
|
56
|
+
if "warning" in stderr.lower():
|
57
|
+
log.warning(stderr)
|
58
|
+
expect_stderr = True
|
59
|
+
elif capture_output and echo_output: # pragma: no cover
|
60
|
+
log.info(stderr)
|
61
|
+
if not expect_stderr:
|
62
|
+
raise ChildProcessError(stderr)
|
63
|
+
|
64
|
+
if result.returncode and result.returncode < 0:
|
65
|
+
raise ChildProcessError(result.stderr)
|
66
|
+
return result
|
67
|
+
|
68
|
+
|
69
|
+
def clone(remote_repo: str, path: Path, shallow: bool = False, tag: Optional[str] = None) -> bool:
|
70
|
+
"""git clone [--depth 1] [--branch <tag_name>] <remote> <directory>"""
|
71
|
+
cmd = ["git", "clone"]
|
72
|
+
if shallow:
|
73
|
+
cmd += ["--depth", "1"]
|
74
|
+
if tag in {"preview", "latest", "master"}:
|
75
|
+
tag = None
|
76
|
+
cmd += [remote_repo, "--branch", tag, str(path)] if tag else [remote_repo, str(path)]
|
77
|
+
if result := _run_local_git(cmd, expect_stderr=True, capture_output=False):
|
78
|
+
return result.returncode == 0
|
79
|
+
else:
|
80
|
+
return False
|
81
|
+
|
82
|
+
|
83
|
+
def get_local_tag(repo: Optional[Union[str, Path]] = None, abbreviate: bool = True) -> Union[str, None]:
|
84
|
+
"""
|
85
|
+
get the most recent git version tag of a local repo
|
86
|
+
repo Path should be in the form of : repo = "./repo/micropython"
|
87
|
+
|
88
|
+
returns the tag or None
|
89
|
+
"""
|
90
|
+
if not repo:
|
91
|
+
repo = Path(".")
|
92
|
+
elif isinstance(repo, str):
|
93
|
+
repo = Path(repo)
|
94
|
+
|
95
|
+
result = _run_local_git(
|
96
|
+
# ["git", "describe", "--tags"],
|
97
|
+
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
98
|
+
repo=repo.as_posix(),
|
99
|
+
expect_stderr=True,
|
100
|
+
)
|
101
|
+
if not result:
|
102
|
+
return None
|
103
|
+
tag: str = result.stdout
|
104
|
+
tag = tag.replace("\r", "").replace("\n", "")
|
105
|
+
if not abbreviate or "-" not in tag:
|
106
|
+
return tag
|
107
|
+
if "-preview" in tag:
|
108
|
+
tag = tag.split("-preview")[0] + "-preview"
|
109
|
+
return tag
|
110
|
+
|
111
|
+
|
112
|
+
def get_local_tags(repo: Optional[Path] = None, minver: Optional[str] = None) -> List[str]:
|
113
|
+
"""
|
114
|
+
get list of all tags of a local repo
|
115
|
+
"""
|
116
|
+
if not repo:
|
117
|
+
repo = Path(".")
|
118
|
+
|
119
|
+
result = _run_local_git(["git", "tag", "-l"], repo=repo.as_posix(), expect_stderr=True)
|
120
|
+
if not result or result.returncode != 0:
|
121
|
+
return []
|
122
|
+
tags = result.stdout.replace("\r", "").split("\n")
|
123
|
+
tags = [tag for tag in tags if tag.startswith("v")]
|
124
|
+
if minver:
|
125
|
+
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
126
|
+
return sorted(tags)
|
127
|
+
|
128
|
+
|
129
|
+
@cachetools.func.ttl_cache(maxsize=16, ttl=60) # 60 seconds
|
130
|
+
def get_tags(repo: str, minver: Optional[str] = None) -> List[str]:
|
131
|
+
"""
|
132
|
+
Get list of tag of a repote github repo
|
133
|
+
"""
|
134
|
+
if not repo or not isinstance(repo, str) or "/" not in repo: # type: ignore
|
135
|
+
return []
|
136
|
+
try:
|
137
|
+
gh_repo = GH_CLIENT.get_repo(repo)
|
138
|
+
except ConnectionError as e:
|
139
|
+
# TODO: unable to capture the exeption
|
140
|
+
log.warning(f"Unable to get tags - {e}")
|
141
|
+
return []
|
142
|
+
tags = [tag.name for tag in gh_repo.get_tags()]
|
143
|
+
if minver:
|
144
|
+
tags = [tag for tag in tags if parse(tag) >= parse(minver)]
|
145
|
+
return sorted(tags)
|
146
|
+
|
147
|
+
|
148
|
+
def checkout_tag(tag: str, repo: Optional[Union[str, Path]] = None) -> bool:
|
149
|
+
"""
|
150
|
+
checkout a specific git tag
|
151
|
+
"""
|
152
|
+
cmd = ["git", "checkout", tag, "--quiet", "--force"]
|
153
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True, capture_output=True)
|
154
|
+
if not result:
|
155
|
+
return False
|
156
|
+
# actually a good result
|
157
|
+
msg = {result.stdout}
|
158
|
+
if msg != {""}:
|
159
|
+
log.warning(f"git message: {msg}")
|
160
|
+
return True
|
161
|
+
|
162
|
+
|
163
|
+
def sync_submodules(repo: Optional[Union[Path, str]] = None) -> bool:
|
164
|
+
"""
|
165
|
+
make sure any submodules are in sync
|
166
|
+
"""
|
167
|
+
cmds = [
|
168
|
+
["git", "submodule", "sync", "--quiet"],
|
169
|
+
# ["git", "submodule", "update", "--quiet"],
|
170
|
+
["git", "submodule", "update", "--init", "lib/micropython-lib"],
|
171
|
+
]
|
172
|
+
for cmd in cmds:
|
173
|
+
if result := _run_local_git(cmd, repo=repo, expect_stderr=True):
|
174
|
+
# actually a good result
|
175
|
+
log.debug(result.stderr)
|
176
|
+
else:
|
177
|
+
return False
|
178
|
+
return True
|
179
|
+
|
180
|
+
|
181
|
+
def checkout_commit(commit_hash: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
182
|
+
"""
|
183
|
+
Checkout a specific commit
|
184
|
+
"""
|
185
|
+
cmd = ["git", "checkout", commit_hash, "--quiet", "--force"]
|
186
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
187
|
+
if not result:
|
188
|
+
return False
|
189
|
+
# actually a good result
|
190
|
+
log.debug(result.stderr)
|
191
|
+
return True
|
192
|
+
|
193
|
+
|
194
|
+
def switch_tag(tag: Union[str, Path], repo: Optional[Union[Path, str]] = None) -> bool:
|
195
|
+
"""
|
196
|
+
switch to the specified version tag of a local repo
|
197
|
+
repo should be in the form of : path/.git
|
198
|
+
repo = '../micropython/.git'
|
199
|
+
returns None
|
200
|
+
"""
|
201
|
+
|
202
|
+
cmd = ["git", "switch", "--detach", tag, "--quiet", "--force"]
|
203
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
204
|
+
if not result:
|
205
|
+
return False
|
206
|
+
# actually a good result
|
207
|
+
log.debug(result.stderr)
|
208
|
+
return True
|
209
|
+
|
210
|
+
|
211
|
+
def switch_branch(branch: str, repo: Optional[Union[Path, str]] = None) -> bool:
|
212
|
+
"""
|
213
|
+
switch to the specified branch in a local repo"
|
214
|
+
repo should be in the form of : path/.git
|
215
|
+
repo = '../micropython/.git'
|
216
|
+
returns None
|
217
|
+
"""
|
218
|
+
cmd = ["git", "switch", branch, "--quiet", "--force"]
|
219
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
220
|
+
if not result:
|
221
|
+
return False
|
222
|
+
# actually a good result
|
223
|
+
log.debug(result.stderr)
|
224
|
+
return True
|
225
|
+
|
226
|
+
|
227
|
+
def fetch(repo: Union[Path, str]) -> bool:
|
228
|
+
"""
|
229
|
+
fetches a repo
|
230
|
+
repo should be in the form of : path/.git
|
231
|
+
repo = '../micropython/.git'
|
232
|
+
returns True on success
|
233
|
+
"""
|
234
|
+
if not repo:
|
235
|
+
raise NotADirectoryError
|
236
|
+
|
237
|
+
cmd = ["git", "fetch", "--all", "--tags", "--quiet"]
|
238
|
+
result = _run_local_git(cmd, repo=repo, echo_output=False)
|
239
|
+
return result.returncode == 0 if result else False
|
240
|
+
|
241
|
+
|
242
|
+
def pull(repo: Union[Path, str], branch: str = "main") -> bool:
|
243
|
+
"""
|
244
|
+
pull a repo origin into main
|
245
|
+
repo should be in the form of : path/.git
|
246
|
+
repo = '../micropython/.git'
|
247
|
+
returns True on success
|
248
|
+
"""
|
249
|
+
if not repo:
|
250
|
+
raise NotADirectoryError
|
251
|
+
repo = Path(repo)
|
252
|
+
# first checkout HEAD
|
253
|
+
cmd = ["git", "checkout", branch, "--quiet", "--force"]
|
254
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
255
|
+
if not result:
|
256
|
+
log.error("error during git checkout main", result)
|
257
|
+
return False
|
258
|
+
|
259
|
+
cmd = ["git", "pull", "origin", branch, "--quiet", "--autostash"]
|
260
|
+
result = _run_local_git(cmd, repo=repo, expect_stderr=True)
|
261
|
+
if not result:
|
262
|
+
log.error("error durign pull", result)
|
263
|
+
return False
|
264
|
+
return result.returncode == 0
|
265
|
+
|
266
|
+
|
267
|
+
def get_git_describe(folder: Optional[str] = None):
|
268
|
+
"""
|
269
|
+
Based on MicroPython makeversionhdr.py
|
270
|
+
returns : current git tag, commits ,commit hash : "v1.19.1-841-g3446"
|
271
|
+
"""
|
272
|
+
# Note: git describe doesn't work if no tag is available
|
273
|
+
try:
|
274
|
+
git_describe = subprocess.check_output(
|
275
|
+
["git", "describe", "--tags", "--dirty", "--always", "--match", "v[1-9].*"],
|
276
|
+
stderr=subprocess.STDOUT,
|
277
|
+
universal_newlines=True,
|
278
|
+
cwd=folder,
|
279
|
+
).strip()
|
280
|
+
except subprocess.CalledProcessError as er:
|
281
|
+
if er.returncode == 128:
|
282
|
+
# git exit code of 128 means no repository found
|
283
|
+
return None
|
284
|
+
git_describe = ""
|
285
|
+
except OSError:
|
286
|
+
return None
|
287
|
+
# format
|
288
|
+
return git_describe
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright (c) 2016 Robbin Bonthond
|
2
|
+
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
11
|
+
# copies or substantial portions of the Software.
|
12
|
+
# ------------------------------------------------------------------------------------
|
13
|
+
# modified to avoid conflcts with rich_click
|
14
|
+
|
15
|
+
# sourcery skip: assign-if-exp, use-named-expression
|
16
|
+
|
17
|
+
import rich_click as click
|
18
|
+
|
19
|
+
_click7 = click.__version__[0] >= "7"
|
20
|
+
|
21
|
+
|
22
|
+
class ClickAliasedGroup(click.RichGroup):
|
23
|
+
def __init__(self, *args, **kwargs):
|
24
|
+
super().__init__(*args, **kwargs)
|
25
|
+
self._commands = {}
|
26
|
+
self._aliases = {}
|
27
|
+
|
28
|
+
def add_command(self, *args, **kwargs):
|
29
|
+
aliases = kwargs.pop("aliases", [])
|
30
|
+
super().add_command(*args, **kwargs)
|
31
|
+
if aliases:
|
32
|
+
cmd = args[0]
|
33
|
+
name = args[1] if len(args) > 1 else None
|
34
|
+
name = name or cmd.name
|
35
|
+
if name is None:
|
36
|
+
raise TypeError("Command has no name.")
|
37
|
+
|
38
|
+
self._commands[name] = aliases
|
39
|
+
for alias in aliases:
|
40
|
+
self._aliases[alias] = cmd.name
|
41
|
+
|
42
|
+
def command(self, *args, **kwargs):
|
43
|
+
aliases = kwargs.pop("aliases", [])
|
44
|
+
decorator = super().command(*args, **kwargs)
|
45
|
+
if not aliases:
|
46
|
+
return decorator
|
47
|
+
|
48
|
+
def _decorator(f):
|
49
|
+
cmd = decorator(f)
|
50
|
+
if aliases:
|
51
|
+
self._commands[cmd.name] = aliases
|
52
|
+
for alias in aliases:
|
53
|
+
self._aliases[alias] = cmd.name
|
54
|
+
return cmd
|
55
|
+
|
56
|
+
return _decorator
|
57
|
+
|
58
|
+
def group(self, *args, **kwargs):
|
59
|
+
aliases = kwargs.pop("aliases", [])
|
60
|
+
decorator = super().group(*args, **kwargs)
|
61
|
+
if not aliases:
|
62
|
+
return decorator
|
63
|
+
|
64
|
+
def _decorator(f):
|
65
|
+
cmd = decorator(f)
|
66
|
+
if aliases:
|
67
|
+
self._commands[cmd.name] = aliases
|
68
|
+
for alias in aliases:
|
69
|
+
self._aliases[alias] = cmd.name
|
70
|
+
return cmd
|
71
|
+
|
72
|
+
return _decorator
|
73
|
+
|
74
|
+
def resolve_alias(self, cmd_name):
|
75
|
+
if cmd_name in self._aliases:
|
76
|
+
return self._aliases[cmd_name]
|
77
|
+
return cmd_name
|
78
|
+
|
79
|
+
def get_command(self, ctx, cmd_name):
|
80
|
+
cmd_name = self.resolve_alias(cmd_name)
|
81
|
+
command = super().get_command(ctx, cmd_name)
|
82
|
+
if command:
|
83
|
+
return command
|
84
|
+
return None
|
85
|
+
|
86
|
+
# def format_commands(self, ctx, formatter):
|
87
|
+
# TODO: output alias with commands - but that is a significant re-write
|
88
|
+
# for now add alias to help text
|
89
|
+
|
90
|
+
|
91
|
+
# ------------------------------------------------------------------------------------
|