micropython-stubber 1.23.1__py3-none-any.whl → 1.23.2__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.23.1.dist-info → micropython_stubber-1.23.2.dist-info}/LICENSE +30 -30
- {micropython_stubber-1.23.1.dist-info → micropython_stubber-1.23.2.dist-info}/METADATA +32 -15
- micropython_stubber-1.23.2.dist-info/RECORD +158 -0
- micropython_stubber-1.23.2.dist-info/entry_points.txt +5 -0
- mpflash/README.md +220 -194
- mpflash/libusb_flash.ipynb +203 -203
- mpflash/mpflash/add_firmware.py +98 -98
- mpflash/mpflash/ask_input.py +236 -236
- mpflash/mpflash/basicgit.py +284 -284
- mpflash/mpflash/bootloader/__init__.py +2 -2
- mpflash/mpflash/bootloader/activate.py +60 -60
- mpflash/mpflash/bootloader/detect.py +82 -82
- mpflash/mpflash/bootloader/manual.py +101 -101
- mpflash/mpflash/bootloader/micropython.py +12 -12
- mpflash/mpflash/bootloader/touch1200.py +36 -36
- mpflash/mpflash/cli_download.py +129 -129
- mpflash/mpflash/cli_flash.py +224 -219
- mpflash/mpflash/cli_group.py +111 -111
- mpflash/mpflash/cli_list.py +87 -81
- mpflash/mpflash/cli_main.py +39 -39
- mpflash/mpflash/common.py +210 -165
- mpflash/mpflash/config.py +44 -44
- mpflash/mpflash/connected.py +96 -78
- mpflash/mpflash/download.py +364 -364
- mpflash/mpflash/downloaded.py +130 -130
- mpflash/mpflash/errors.py +9 -9
- mpflash/mpflash/flash/__init__.py +55 -55
- mpflash/mpflash/flash/esp.py +59 -59
- mpflash/mpflash/flash/stm32.py +19 -19
- mpflash/mpflash/flash/stm32_dfu.py +104 -104
- mpflash/mpflash/flash/uf2/__init__.py +88 -88
- mpflash/mpflash/flash/uf2/boardid.py +15 -15
- mpflash/mpflash/flash/uf2/linux.py +136 -130
- mpflash/mpflash/flash/uf2/macos.py +42 -42
- mpflash/mpflash/flash/uf2/uf2disk.py +12 -12
- mpflash/mpflash/flash/uf2/windows.py +43 -43
- mpflash/mpflash/flash/worklist.py +170 -170
- mpflash/mpflash/list.py +106 -99
- mpflash/mpflash/logger.py +41 -41
- mpflash/mpflash/mpboard_id/__init__.py +93 -93
- mpflash/mpflash/mpboard_id/add_boards.py +251 -251
- 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 +266 -222
- mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -141
- mpflash/mpflash/mpremoteboard/runner.py +140 -140
- mpflash/mpflash/vendor/click_aliases.py +91 -91
- mpflash/mpflash/vendor/dfu.py +165 -165
- mpflash/mpflash/vendor/pydfu.py +605 -605
- mpflash/mpflash/vendor/readme.md +2 -2
- mpflash/mpflash/versions.py +135 -135
- mpflash/poetry.lock +1599 -1599
- mpflash/pyproject.toml +65 -65
- mpflash/stm32_udev_rules.md +62 -62
- stubber/__init__.py +3 -3
- stubber/board/board_info.csv +193 -193
- stubber/board/boot.py +34 -34
- stubber/board/createstubs.py +1004 -986
- stubber/board/createstubs_db.py +826 -825
- stubber/board/createstubs_db_min.py +332 -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 +767 -766
- stubber/board/createstubs_mem_min.py +307 -306
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +295 -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 +437 -454
- stubber/codemod/_partials/__init__.py +48 -48
- 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 +151 -145
- stubber/codemod/merge_docstub.py +284 -284
- stubber/codemod/modify_list.py +54 -54
- stubber/codemod/utils.py +56 -56
- stubber/commands/build_cmd.py +94 -94
- stubber/commands/cli.py +49 -55
- stubber/commands/clone_cmd.py +78 -78
- stubber/commands/config_cmd.py +29 -29
- stubber/commands/enrich_folder_cmd.py +71 -71
- stubber/commands/get_core_cmd.py +71 -71
- stubber/commands/get_docstubs_cmd.py +92 -89
- stubber/commands/get_frozen_cmd.py +117 -114
- stubber/commands/get_mcu_cmd.py +102 -61
- stubber/commands/merge_cmd.py +66 -66
- stubber/commands/publish_cmd.py +118 -118
- stubber/commands/stub_cmd.py +31 -31
- stubber/commands/switch_cmd.py +62 -62
- 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 +37 -36
- stubber/freeze/common.py +72 -68
- stubber/freeze/freeze_folder.py +69 -69
- stubber/freeze/freeze_manifest_2.py +126 -113
- stubber/freeze/get_frozen.py +131 -127
- stubber/get_cpython.py +112 -101
- stubber/get_lobo.py +59 -59
- stubber/minify.py +423 -419
- stubber/publish/bump.py +86 -86
- stubber/publish/candidates.py +275 -256
- stubber/publish/database.py +18 -18
- stubber/publish/defaults.py +40 -40
- stubber/publish/enums.py +24 -24
- stubber/publish/helpers.py +29 -29
- stubber/publish/merge_docstubs.py +136 -130
- stubber/publish/missing_class_methods.py +51 -49
- stubber/publish/package.py +150 -146
- stubber/publish/pathnames.py +51 -51
- stubber/publish/publish.py +120 -120
- stubber/publish/pypi.py +42 -38
- stubber/publish/stubpackage.py +1055 -1027
- stubber/rst/__init__.py +9 -9
- stubber/rst/classsort.py +78 -77
- stubber/rst/lookup.py +533 -530
- stubber/rst/output_dict.py +401 -401
- stubber/rst/reader.py +814 -814
- stubber/rst/report_return.py +77 -69
- stubber/rst/rst_utils.py +541 -540
- stubber/stubber.py +38 -38
- stubber/stubs_from_docs.py +90 -90
- stubber/tools/manifestfile.py +654 -654
- stubber/tools/readme.md +6 -6
- stubber/update_fallback.py +117 -117
- stubber/update_module_list.py +123 -123
- stubber/utils/__init__.py +6 -6
- stubber/utils/config.py +137 -125
- stubber/utils/makeversionhdr.py +54 -54
- stubber/utils/manifest.py +90 -90
- stubber/utils/post.py +80 -79
- stubber/utils/repos.py +156 -150
- stubber/utils/stubmaker.py +139 -139
- stubber/utils/typed_config_toml.py +80 -77
- stubber/variants.py +106 -106
- micropython_stubber-1.23.1.dist-info/RECORD +0 -159
- micropython_stubber-1.23.1.dist-info/entry_points.txt +0 -3
- mpflash/basicgit.py +0 -288
- {micropython_stubber-1.23.1.dist-info → micropython_stubber-1.23.2.dist-info}/WHEEL +0 -0
@@ -1,741 +1,741 @@
|
|
1
|
-
"""
|
2
|
-
Create stubs for the lvgl modules on a MicroPython board.
|
3
|
-
|
4
|
-
Note that the stubs can be very large, and it may be best to directly store them on an SD card if your device supports this.
|
5
|
-
|
6
|
-
This variant was generated from createstubs.py by micropython-stubber v1.16.3
|
7
|
-
"""
|
8
|
-
# Copyright (c) 2019-2023 Jos Verlinde
|
9
|
-
|
10
|
-
import gc
|
11
|
-
import os
|
12
|
-
import sys
|
13
|
-
from time import sleep
|
14
|
-
|
15
|
-
try:
|
16
|
-
from ujson import dumps
|
17
|
-
except:
|
18
|
-
from json import dumps
|
19
|
-
|
20
|
-
try:
|
21
|
-
from machine import reset # type: ignore
|
22
|
-
except ImportError:
|
23
|
-
pass
|
24
|
-
|
25
|
-
try:
|
26
|
-
from collections import OrderedDict
|
27
|
-
except ImportError:
|
28
|
-
from ucollections import OrderedDict # type: ignore
|
29
|
-
|
30
|
-
__version__ = "v1.16.3"
|
31
|
-
ENOENT = 2
|
32
|
-
_MAX_CLASS_LEVEL = 2 # Max class nesting
|
33
|
-
LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
|
34
|
-
|
35
|
-
|
36
|
-
# our own logging module to avoid dependency on and interfering with logging module
|
37
|
-
class logging:
|
38
|
-
DEBUG = 10
|
39
|
-
TRACE = 15
|
40
|
-
INFO = 20
|
41
|
-
WARNING = 30
|
42
|
-
ERROR = 40
|
43
|
-
CRITICAL = 50
|
44
|
-
level = INFO
|
45
|
-
prnt = print
|
46
|
-
|
47
|
-
@staticmethod
|
48
|
-
def getLogger(name):
|
49
|
-
return logging()
|
50
|
-
|
51
|
-
@classmethod
|
52
|
-
def basicConfig(cls, level):
|
53
|
-
cls.level = level
|
54
|
-
pass
|
55
|
-
|
56
|
-
def trace(self, msg):
|
57
|
-
if self.level <= logging.TRACE:
|
58
|
-
self.prnt("TRACE :", msg)
|
59
|
-
|
60
|
-
def debug(self, msg):
|
61
|
-
if self.level <= logging.DEBUG:
|
62
|
-
self.prnt("DEBUG :", msg)
|
63
|
-
|
64
|
-
def info(self, msg):
|
65
|
-
if self.level <= logging.INFO:
|
66
|
-
self.prnt("INFO :", msg)
|
67
|
-
|
68
|
-
def warning(self, msg):
|
69
|
-
if self.level <= logging.WARNING:
|
70
|
-
self.prnt("WARN :", msg)
|
71
|
-
|
72
|
-
def error(self, msg):
|
73
|
-
if self.level <= logging.ERROR:
|
74
|
-
self.prnt("ERROR :", msg)
|
75
|
-
|
76
|
-
def critical(self, msg):
|
77
|
-
if self.level <= logging.CRITICAL:
|
78
|
-
self.prnt("CRIT :", msg)
|
79
|
-
|
80
|
-
|
81
|
-
log = logging.getLogger("stubber")
|
82
|
-
logging.basicConfig(level=logging.INFO)
|
83
|
-
# logging.basicConfig(level=logging.TRACE)
|
84
|
-
# logging.basicConfig(level=logging.DEBUG)
|
85
|
-
|
86
|
-
|
87
|
-
class Stubber:
|
88
|
-
"Generate stubs for modules in firmware"
|
89
|
-
|
90
|
-
def __init__(self, path: str = None, firmware_id: str = None): # type: ignore
|
91
|
-
try:
|
92
|
-
if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore
|
93
|
-
raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
|
94
|
-
except AttributeError:
|
95
|
-
pass
|
96
|
-
self._report = [] # type: list[str]
|
97
|
-
self.info = _info()
|
98
|
-
log.info("Port: {}".format(self.info["port"]))
|
99
|
-
log.info("Board: {}".format(self.info["board"]))
|
100
|
-
gc.collect()
|
101
|
-
if firmware_id:
|
102
|
-
self._fwid = firmware_id.lower()
|
103
|
-
else:
|
104
|
-
if self.info["family"] == "micropython":
|
105
|
-
self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-")
|
106
|
-
else:
|
107
|
-
self._fwid = "{family}-v{version}-{port}".format(**self.info)
|
108
|
-
self._start_free = gc.mem_free() # type: ignore
|
109
|
-
|
110
|
-
if path:
|
111
|
-
if path.endswith("/"):
|
112
|
-
path = path[:-1]
|
113
|
-
else:
|
114
|
-
path = get_root()
|
115
|
-
|
116
|
-
self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
|
117
|
-
log.debug(self.path)
|
118
|
-
try:
|
119
|
-
ensure_folder(path + "/")
|
120
|
-
except OSError:
|
121
|
-
print("error creating stub folder {}".format(path))
|
122
|
-
self.problematic = [
|
123
|
-
"upip",
|
124
|
-
"upysh",
|
125
|
-
"webrepl_setup",
|
126
|
-
"http_client",
|
127
|
-
"http_client_ssl",
|
128
|
-
"http_server",
|
129
|
-
"http_server_ssl",
|
130
|
-
]
|
131
|
-
self.excluded = [
|
132
|
-
"webrepl",
|
133
|
-
"_webrepl",
|
134
|
-
"port_diag",
|
135
|
-
"example_sub_led.py",
|
136
|
-
"example_pub_button.py",
|
137
|
-
]
|
138
|
-
# there is no option to discover modules from micropython, list is read from an external file.
|
139
|
-
self.modules = [] # type: list[str]
|
140
|
-
|
141
|
-
def get_obj_attributes(self, item_instance: object):
|
142
|
-
"extract information of the objects members and attributes"
|
143
|
-
# name_, repr_(value), type as text, item_instance
|
144
|
-
_result = []
|
145
|
-
_errors = []
|
146
|
-
log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
|
147
|
-
for name in dir(item_instance):
|
148
|
-
if name.startswith("_") and not name in self.modules:
|
149
|
-
continue
|
150
|
-
log.debug("get attribute {}".format(name))
|
151
|
-
try:
|
152
|
-
val = getattr(item_instance, name)
|
153
|
-
# name , item_repr(value) , type as text, item_instance, order
|
154
|
-
log.debug("attribute {}:{}".format(name, val))
|
155
|
-
try:
|
156
|
-
type_text = repr(type(val)).split("'")[1]
|
157
|
-
except IndexError:
|
158
|
-
type_text = ""
|
159
|
-
if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}:
|
160
|
-
order = 1
|
161
|
-
elif type_text in {"function", "method"}:
|
162
|
-
order = 2
|
163
|
-
elif type_text in ("class"):
|
164
|
-
order = 3
|
165
|
-
else:
|
166
|
-
order = 4
|
167
|
-
_result.append((name, repr(val), repr(type(val)), val, order))
|
168
|
-
except AttributeError as e:
|
169
|
-
_errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e))
|
170
|
-
except MemoryError as e:
|
171
|
-
# print("MemoryError: {}".format(e))
|
172
|
-
sleep(1)
|
173
|
-
reset()
|
174
|
-
|
175
|
-
# remove internal __
|
176
|
-
# _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4])
|
177
|
-
_result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4])
|
178
|
-
gc.collect()
|
179
|
-
return _result, _errors
|
180
|
-
|
181
|
-
def add_modules(self, modules):
|
182
|
-
"Add additional modules to be exported"
|
183
|
-
self.modules = sorted(set(self.modules) | set(modules))
|
184
|
-
|
185
|
-
def create_all_stubs(self):
|
186
|
-
"Create stubs for all configured modules"
|
187
|
-
log.info("Start micropython-stubber v{} on {}".format(__version__, self._fwid))
|
188
|
-
gc.collect()
|
189
|
-
for module_name in self.modules:
|
190
|
-
self.create_one_stub(module_name)
|
191
|
-
log.info("Finally done")
|
192
|
-
|
193
|
-
def create_one_stub(self, module_name: str):
|
194
|
-
if module_name in self.problematic:
|
195
|
-
log.warning("Skip module: {:<25} : Known problematic".format(module_name))
|
196
|
-
return False
|
197
|
-
if module_name in self.excluded:
|
198
|
-
log.warning("Skip module: {:<25} : Excluded".format(module_name))
|
199
|
-
return False
|
200
|
-
|
201
|
-
file_name = "{}/{}.py".format(self.path, module_name.replace(".", "/"))
|
202
|
-
gc.collect()
|
203
|
-
result = False
|
204
|
-
try:
|
205
|
-
result = self.create_module_stub(module_name, file_name)
|
206
|
-
except OSError:
|
207
|
-
return False
|
208
|
-
gc.collect()
|
209
|
-
return result
|
210
|
-
|
211
|
-
def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore
|
212
|
-
"""Create a Stub of a single python module
|
213
|
-
|
214
|
-
Args:
|
215
|
-
- module_name (str): name of the module to document. This module will be imported.
|
216
|
-
- file_name (Optional[str]): the 'path/filename.py' to write to. If omitted will be created based on the module name.
|
217
|
-
"""
|
218
|
-
if file_name is None:
|
219
|
-
fname = module_name.replace(".", "_") + ".py"
|
220
|
-
file_name = self.path + "/" + fname
|
221
|
-
else:
|
222
|
-
fname = file_name.split("/")[-1]
|
223
|
-
|
224
|
-
if "/" in module_name:
|
225
|
-
# for nested modules
|
226
|
-
module_name = module_name.replace("/", ".")
|
227
|
-
|
228
|
-
# import the module (as new_module) to examine it
|
229
|
-
new_module = None
|
230
|
-
try:
|
231
|
-
new_module = __import__(module_name, None, None, ("*"))
|
232
|
-
m1 = gc.mem_free() # type: ignore
|
233
|
-
log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
|
234
|
-
|
235
|
-
except ImportError:
|
236
|
-
log.trace("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
|
237
|
-
return False
|
238
|
-
|
239
|
-
# Start a new file
|
240
|
-
ensure_folder(file_name)
|
241
|
-
with open(file_name, "w") as fp:
|
242
|
-
# todo: improve header
|
243
|
-
info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}")
|
244
|
-
s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(module_name, self._fwid, info_, __version__)
|
245
|
-
fp.write(s)
|
246
|
-
fp.write("from __future__ import annotations\nfrom typing import Any\nfrom _typeshed import Incomplete\n\n")
|
247
|
-
self.write_object_stub(fp, new_module, module_name, "")
|
248
|
-
|
249
|
-
self._report.append('{{"module": "{}", "file": "{}"}}'.format(module_name, file_name.replace("\\", "/")))
|
250
|
-
|
251
|
-
if module_name not in {"os", "sys", "logging", "gc"}:
|
252
|
-
# try to unload the module unless we use it
|
253
|
-
try:
|
254
|
-
del new_module
|
255
|
-
except (OSError, KeyError): # lgtm [py/unreachable-statement]
|
256
|
-
log.warning("could not del new_module")
|
257
|
-
# lets not try - most times it does not work anyway
|
258
|
-
# try:
|
259
|
-
# del sys.modules[module_name]
|
260
|
-
# except KeyError:
|
261
|
-
# log.warning("could not del sys.modules[{}]".format(module_name))
|
262
|
-
gc.collect()
|
263
|
-
return True
|
264
|
-
|
265
|
-
def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0):
|
266
|
-
"Write a module/object stub to an open file. Can be called recursive."
|
267
|
-
gc.collect()
|
268
|
-
if object_expr in self.problematic:
|
269
|
-
log.warning("SKIPPING problematic module:{}".format(object_expr))
|
270
|
-
return
|
271
|
-
|
272
|
-
# log.debug("DUMP : {}".format(object_expr))
|
273
|
-
items, errors = self.get_obj_attributes(object_expr)
|
274
|
-
|
275
|
-
if errors:
|
276
|
-
print(errors)
|
277
|
-
|
278
|
-
for item_name, item_repr, item_type_txt, item_instance, _ in items:
|
279
|
-
# name_, repr_(value), type as text, item_instance, order
|
280
|
-
if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]:
|
281
|
-
# do not create stubs for these primitives
|
282
|
-
continue
|
283
|
-
if item_name[0].isdigit():
|
284
|
-
log.warning("NameError: invalid name {}".format(item_name))
|
285
|
-
continue
|
286
|
-
# Class expansion only on first 3 levels (bit of a hack)
|
287
|
-
if (
|
288
|
-
item_type_txt == "<class 'type'>"
|
289
|
-
and len(indent) <= _MAX_CLASS_LEVEL * 4
|
290
|
-
# and not obj_name.endswith(".Pin")
|
291
|
-
# avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
|
292
|
-
):
|
293
|
-
log.trace("{0}class {1}:".format(indent, item_name))
|
294
|
-
superclass = ""
|
295
|
-
is_exception = (
|
296
|
-
item_name.endswith("Exception")
|
297
|
-
or item_name.endswith("Error")
|
298
|
-
or item_name
|
299
|
-
in [
|
300
|
-
"KeyboardInterrupt",
|
301
|
-
"StopIteration",
|
302
|
-
"SystemExit",
|
303
|
-
]
|
304
|
-
)
|
305
|
-
if is_exception:
|
306
|
-
superclass = "Exception"
|
307
|
-
s = "\n{}class {}({}):\n".format(indent, item_name, superclass)
|
308
|
-
# s += indent + " ''\n"
|
309
|
-
if is_exception:
|
310
|
-
s += indent + " ...\n"
|
311
|
-
fp.write(s)
|
312
|
-
continue
|
313
|
-
# write classdef
|
314
|
-
fp.write(s)
|
315
|
-
# first write the class literals and methods
|
316
|
-
log.debug("# recursion over class {0}".format(item_name))
|
317
|
-
self.write_object_stub(
|
318
|
-
fp,
|
319
|
-
item_instance,
|
320
|
-
"{0}.{1}".format(obj_name, item_name),
|
321
|
-
indent + " ",
|
322
|
-
in_class + 1,
|
323
|
-
)
|
324
|
-
# end with the __init__ method to make sure that the literals are defined
|
325
|
-
# Add __init__
|
326
|
-
s = indent + " def __init__(self, *argv, **kwargs) -> None:\n"
|
327
|
-
s += indent + " ...\n\n"
|
328
|
-
fp.write(s)
|
329
|
-
elif any(word in item_type_txt for word in ["method", "function", "closure"]):
|
330
|
-
log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
|
331
|
-
# module Function or class method
|
332
|
-
# will accept any number of params
|
333
|
-
# return type Any/Incomplete
|
334
|
-
ret = "Incomplete"
|
335
|
-
first = ""
|
336
|
-
# Self parameter only on class methods/functions
|
337
|
-
if in_class > 0:
|
338
|
-
first = "self, "
|
339
|
-
# class method - add function decoration
|
340
|
-
if "bound_method" in item_type_txt or "bound_method" in item_repr:
|
341
|
-
s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(indent, item_name, ret)
|
342
|
-
else:
|
343
|
-
s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
|
344
|
-
s += indent + " ...\n\n"
|
345
|
-
fp.write(s)
|
346
|
-
log.debug("\n" + s)
|
347
|
-
elif item_type_txt == "<class 'module'>":
|
348
|
-
# Skip imported modules
|
349
|
-
# fp.write("# import {}\n".format(item_name))
|
350
|
-
pass
|
351
|
-
|
352
|
-
elif item_type_txt.startswith("<class '"):
|
353
|
-
t = item_type_txt[8:-2]
|
354
|
-
s = ""
|
355
|
-
|
356
|
-
if t in ["str", "int", "float", "bool", "bytearray", "bytes"]:
|
357
|
-
# known type: use actual value
|
358
|
-
s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
|
359
|
-
elif t in ["dict", "list", "tuple"]:
|
360
|
-
# dict, list , tuple: use empty value
|
361
|
-
ev = {"dict": "{}", "list": "[]", "tuple": "()"}
|
362
|
-
s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
|
363
|
-
else:
|
364
|
-
# something else
|
365
|
-
if t in ["object", "set", "frozenset", "Pin", "FileIO"]:
|
366
|
-
# https://docs.python.org/3/tutorial/classes.html#item_instance-objects
|
367
|
-
# use these types for the attribute
|
368
|
-
s = "{0}{1} : {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
|
369
|
-
else:
|
370
|
-
# Requires Python 3.6 syntax, which is OK for the stubs/pyi
|
371
|
-
t = "Incomplete"
|
372
|
-
s = "{0}{1} : {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
|
373
|
-
fp.write(s)
|
374
|
-
log.debug("\n" + s)
|
375
|
-
else:
|
376
|
-
# keep only the name
|
377
|
-
log.debug("# all other, type = '{0}'".format(item_type_txt))
|
378
|
-
fp.write("# all other, type = '{0}'\n".format(item_type_txt))
|
379
|
-
|
380
|
-
fp.write(indent + item_name + " # type: Incomplete\n")
|
381
|
-
|
382
|
-
# del items
|
383
|
-
# del errors
|
384
|
-
# try:
|
385
|
-
# del item_name, item_repr, item_type_txt, item_instance # type: ignore
|
386
|
-
# except (OSError, KeyError, NameError):
|
387
|
-
# pass
|
388
|
-
|
389
|
-
@property
|
390
|
-
def flat_fwid(self):
|
391
|
-
"Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename"
|
392
|
-
s = self._fwid
|
393
|
-
# path name restrictions
|
394
|
-
chars = " .()/\\:$"
|
395
|
-
for c in chars:
|
396
|
-
s = s.replace(c, "_")
|
397
|
-
return s
|
398
|
-
|
399
|
-
def clean(self, path: str = None): # type: ignore
|
400
|
-
"Remove all files from the stub folder"
|
401
|
-
if path is None:
|
402
|
-
path = self.path
|
403
|
-
log.info("Clean/remove files in folder: {}".format(path))
|
404
|
-
try:
|
405
|
-
os.stat(path) # TEMP workaround mpremote listdir bug -
|
406
|
-
items = os.listdir(path)
|
407
|
-
except (OSError, AttributeError):
|
408
|
-
# os.listdir fails on unix
|
409
|
-
return
|
410
|
-
for fn in items:
|
411
|
-
item = "{}/{}".format(path, fn)
|
412
|
-
try:
|
413
|
-
os.remove(item)
|
414
|
-
except OSError:
|
415
|
-
try: # folder
|
416
|
-
self.clean(item)
|
417
|
-
os.rmdir(item)
|
418
|
-
except OSError:
|
419
|
-
pass
|
420
|
-
|
421
|
-
def report(self, filename: str = "modules.json"):
|
422
|
-
"create json with list of exported modules"
|
423
|
-
log.info("Created stubs for {} modules on board {}\nPath: {}".format(len(self._report), self._fwid, self.path))
|
424
|
-
f_name = "{}/{}".format(self.path, filename)
|
425
|
-
log.info("Report file: {}".format(f_name))
|
426
|
-
gc.collect()
|
427
|
-
try:
|
428
|
-
# write json by node to reduce memory requirements
|
429
|
-
with open(f_name, "w") as f:
|
430
|
-
self.write_json_header(f)
|
431
|
-
first = True
|
432
|
-
for n in self._report:
|
433
|
-
self.write_json_node(f, n, first)
|
434
|
-
first = False
|
435
|
-
self.write_json_end(f)
|
436
|
-
used = self._start_free - gc.mem_free() # type: ignore
|
437
|
-
log.trace("Memory used: {0} Kb".format(used // 1024))
|
438
|
-
except OSError:
|
439
|
-
print("Failed to create the report.")
|
440
|
-
|
441
|
-
def write_json_header(self, f):
|
442
|
-
f.write("{")
|
443
|
-
f.write(dumps({"firmware": self.info})[1:-1])
|
444
|
-
f.write(",\n")
|
445
|
-
f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
|
446
|
-
f.write(",\n")
|
447
|
-
f.write('"modules" :[\n')
|
448
|
-
|
449
|
-
def write_json_node(self, f, n, first):
|
450
|
-
if not first:
|
451
|
-
f.write(",\n")
|
452
|
-
f.write(n)
|
453
|
-
|
454
|
-
def write_json_end(self, f):
|
455
|
-
f.write("\n]}")
|
456
|
-
|
457
|
-
|
458
|
-
def ensure_folder(path: str):
|
459
|
-
"Create nested folders if needed"
|
460
|
-
i = start = 0
|
461
|
-
while i != -1:
|
462
|
-
i = path.find("/", start)
|
463
|
-
if i != -1:
|
464
|
-
p = path[0] if i == 0 else path[:i]
|
465
|
-
# p = partial folder
|
466
|
-
try:
|
467
|
-
_ = os.stat(p)
|
468
|
-
except OSError as e:
|
469
|
-
# folder does not exist
|
470
|
-
if e.args[0] == ENOENT:
|
471
|
-
try:
|
472
|
-
os.mkdir(p)
|
473
|
-
except OSError as e2:
|
474
|
-
print("failed to create folder {}".format(p))
|
475
|
-
raise e2
|
476
|
-
# next level deep
|
477
|
-
start = i + 1
|
478
|
-
|
479
|
-
|
480
|
-
def _build(s):
|
481
|
-
# extract build from sys.version or os.uname().version if available
|
482
|
-
# sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
|
483
|
-
# sys.implementation.version: 'v1.13-103-gb137d064e'
|
484
|
-
if not s:
|
485
|
-
return ""
|
486
|
-
s = s.split(" on ", 1)[0] if " on " in s else s
|
487
|
-
if s.startswith("v"):
|
488
|
-
if not "-" in s:
|
489
|
-
return ""
|
490
|
-
b = s.split("-")[1]
|
491
|
-
return b
|
492
|
-
if not "-preview" in s:
|
493
|
-
return ""
|
494
|
-
b = s.split("-preview")[1].split(".")[1]
|
495
|
-
return b
|
496
|
-
|
497
|
-
|
498
|
-
def _info(): # type:() -> dict[str, str]
|
499
|
-
info = OrderedDict(
|
500
|
-
{
|
501
|
-
"family": sys.implementation.name,
|
502
|
-
"version": "",
|
503
|
-
"build": "",
|
504
|
-
"ver": "",
|
505
|
-
"port": sys.platform, # port: esp32 / win32 / linux / stm32
|
506
|
-
"board": "UNKNOWN",
|
507
|
-
"cpu": "",
|
508
|
-
"mpy": "",
|
509
|
-
"arch": "",
|
510
|
-
}
|
511
|
-
)
|
512
|
-
# change port names to be consistent with the repo
|
513
|
-
if info["port"].startswith("pyb"):
|
514
|
-
info["port"] = "stm32"
|
515
|
-
elif info["port"] == "win32":
|
516
|
-
info["port"] = "windows"
|
517
|
-
elif info["port"] == "linux":
|
518
|
-
info["port"] = "unix"
|
519
|
-
try:
|
520
|
-
info["version"] = version_str(sys.implementation.version) # type: ignore
|
521
|
-
except AttributeError:
|
522
|
-
pass
|
523
|
-
try:
|
524
|
-
_machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
|
525
|
-
# info["board"] = "with".join(_machine.split("with")[:-1]).strip()
|
526
|
-
info["board"] = _machine
|
527
|
-
info["cpu"] = _machine.split("with")[-1].strip()
|
528
|
-
info["mpy"] = (
|
529
|
-
sys.implementation._mpy
|
530
|
-
if "_mpy" in dir(sys.implementation)
|
531
|
-
else sys.implementation.mpy
|
532
|
-
if "mpy" in dir(sys.implementation)
|
533
|
-
else ""
|
534
|
-
)
|
535
|
-
except (AttributeError, IndexError):
|
536
|
-
pass
|
537
|
-
info["board"] = get_boardname()
|
538
|
-
|
539
|
-
try:
|
540
|
-
if "uname" in dir(os): # old
|
541
|
-
# extract build from uname().version if available
|
542
|
-
info["build"] = _build(os.uname()[3]) # type: ignore
|
543
|
-
if not info["build"]:
|
544
|
-
# extract build from uname().release if available
|
545
|
-
info["build"] = _build(os.uname()[2]) # type: ignore
|
546
|
-
elif "version" in dir(sys): # new
|
547
|
-
# extract build from sys.version if available
|
548
|
-
info["build"] = _build(sys.version)
|
549
|
-
except (AttributeError, IndexError, TypeError):
|
550
|
-
pass
|
551
|
-
# avoid build hashes
|
552
|
-
# if info["build"] and len(info["build"]) > 5:
|
553
|
-
# info["build"] = ""
|
554
|
-
|
555
|
-
if info["version"] == "" and sys.platform not in ("unix", "win32"):
|
556
|
-
try:
|
557
|
-
u = os.uname() # type: ignore
|
558
|
-
info["version"] = u.release
|
559
|
-
except (IndexError, AttributeError, TypeError):
|
560
|
-
pass
|
561
|
-
# detect families
|
562
|
-
for fam_name, mod_name, mod_thing in [
|
563
|
-
("pycopy", "pycopy", "const"),
|
564
|
-
("pycom", "pycom", "FAT"),
|
565
|
-
("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
|
566
|
-
]:
|
567
|
-
try:
|
568
|
-
_t = __import__(mod_name, None, None, (mod_thing))
|
569
|
-
info["family"] = fam_name
|
570
|
-
del _t
|
571
|
-
break
|
572
|
-
except (ImportError, KeyError):
|
573
|
-
pass
|
574
|
-
|
575
|
-
if info["family"] == "ev3-pybricks":
|
576
|
-
info["release"] = "2.0.0"
|
577
|
-
|
578
|
-
if info["family"] == "micropython":
|
579
|
-
info["version"]
|
580
|
-
if (
|
581
|
-
info["version"]
|
582
|
-
and info["version"].endswith(".0")
|
583
|
-
and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
|
584
|
-
and info["version"] <= "1.19.9"
|
585
|
-
):
|
586
|
-
# versions from 1.10.0 to 1.20.0 do not have a micro .0
|
587
|
-
info["version"] = info["version"][:-2]
|
588
|
-
|
589
|
-
# spell-checker: disable
|
590
|
-
if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
|
591
|
-
sys_mpy = int(info["mpy"])
|
592
|
-
# .mpy architecture
|
593
|
-
arch = [
|
594
|
-
None,
|
595
|
-
"x86",
|
596
|
-
"x64",
|
597
|
-
"armv6",
|
598
|
-
"armv6m",
|
599
|
-
"armv7m",
|
600
|
-
"armv7em",
|
601
|
-
"armv7emsp",
|
602
|
-
"armv7emdp",
|
603
|
-
"xtensa",
|
604
|
-
"xtensawin",
|
605
|
-
][sys_mpy >> 10]
|
606
|
-
if arch:
|
607
|
-
info["arch"] = arch
|
608
|
-
# .mpy version.minor
|
609
|
-
info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
|
610
|
-
if info["build"] and not info["version"].endswith("-preview"):
|
611
|
-
info["version"] = info["version"] + "-preview"
|
612
|
-
# simple to use version[-build] string
|
613
|
-
info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}"
|
614
|
-
|
615
|
-
return info
|
616
|
-
|
617
|
-
|
618
|
-
def version_str(version: tuple): # -> str:
|
619
|
-
v_str = ".".join([str(n) for n in version[:3]])
|
620
|
-
if len(version) > 3 and version[3]:
|
621
|
-
v_str += "-" + version[3]
|
622
|
-
return v_str
|
623
|
-
|
624
|
-
|
625
|
-
def get_boardname() -> str:
|
626
|
-
"Read the board name from the boardname.py file that may have been created upfront"
|
627
|
-
try:
|
628
|
-
from boardname import BOARDNAME # type: ignore
|
629
|
-
|
630
|
-
log.info("Found BOARDNAME: {}".format(BOARDNAME))
|
631
|
-
except ImportError:
|
632
|
-
log.warning("BOARDNAME not found")
|
633
|
-
BOARDNAME = ""
|
634
|
-
return BOARDNAME
|
635
|
-
|
636
|
-
|
637
|
-
def get_root() -> str: # sourcery skip: use-assigned-variable
|
638
|
-
"Determine the root folder of the device"
|
639
|
-
try:
|
640
|
-
c = os.getcwd()
|
641
|
-
except (OSError, AttributeError):
|
642
|
-
# unix port
|
643
|
-
c = "."
|
644
|
-
r = c
|
645
|
-
for r in [c, "/sd", "/flash", "/", "."]:
|
646
|
-
try:
|
647
|
-
_ = os.stat(r)
|
648
|
-
break
|
649
|
-
except OSError:
|
650
|
-
continue
|
651
|
-
return r
|
652
|
-
|
653
|
-
|
654
|
-
def file_exists(filename: str):
|
655
|
-
try:
|
656
|
-
if os.stat(filename)[0] >> 14:
|
657
|
-
return True
|
658
|
-
return False
|
659
|
-
except OSError:
|
660
|
-
return False
|
661
|
-
|
662
|
-
|
663
|
-
def show_help():
|
664
|
-
# print("-p, --path path to store the stubs in, defaults to '.'")
|
665
|
-
sys.exit(1)
|
666
|
-
|
667
|
-
|
668
|
-
def read_path() -> str:
|
669
|
-
"get --path from cmdline. [unix/win]"
|
670
|
-
path = ""
|
671
|
-
if len(sys.argv) == 3:
|
672
|
-
cmd = (sys.argv[1]).lower()
|
673
|
-
if cmd in ("--path", "-p"):
|
674
|
-
path = sys.argv[2]
|
675
|
-
else:
|
676
|
-
show_help()
|
677
|
-
elif len(sys.argv) == 2:
|
678
|
-
show_help()
|
679
|
-
return path
|
680
|
-
|
681
|
-
|
682
|
-
def is_micropython() -> bool:
|
683
|
-
"runtime test to determine full or micropython"
|
684
|
-
# pylint: disable=unused-variable,eval-used
|
685
|
-
try:
|
686
|
-
# either test should fail on micropython
|
687
|
-
# a) https://docs.micropython.org/en/latest/genrst/syntax.html#spaces
|
688
|
-
# Micropython : SyntaxError
|
689
|
-
# a = eval("1and 0") # lgtm [py/unused-local-variable]
|
690
|
-
# Eval blocks some minification aspects
|
691
|
-
|
692
|
-
# b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
|
693
|
-
# Micropython: NotImplementedError
|
694
|
-
b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable]
|
695
|
-
|
696
|
-
# c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute
|
697
|
-
# Micropython: AttributeError
|
698
|
-
c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable]
|
699
|
-
return False
|
700
|
-
except (NotImplementedError, AttributeError):
|
701
|
-
return True
|
702
|
-
|
703
|
-
|
704
|
-
def main():
|
705
|
-
try:
|
706
|
-
import lvgl # type: ignore
|
707
|
-
except Exception:
|
708
|
-
# print("\n\nNOTE: The `lvgl` module could not be found on this firmware\n\n")
|
709
|
-
return
|
710
|
-
# Specify firmware name & version
|
711
|
-
fw_id = "lvgl"
|
712
|
-
try:
|
713
|
-
fw_id = "lvgl-{0}_{1}_{2}-{3}-{4}".format(
|
714
|
-
lvgl.version_major(),
|
715
|
-
lvgl.version_minor(),
|
716
|
-
lvgl.version_patch(),
|
717
|
-
lvgl.version_info(),
|
718
|
-
sys.platform,
|
719
|
-
)
|
720
|
-
except Exception:
|
721
|
-
fw_id = "lvgl-{0}_{1}_{2}_{3}-{4}".format(8, 1, 0, "dev", sys.platform)
|
722
|
-
finally:
|
723
|
-
stubber = Stubber(firmware_id=fw_id)
|
724
|
-
stubber.clean()
|
725
|
-
# modules to stub : only lvgl specifics
|
726
|
-
stubber.modules = ["io", "lodepng", "rtch", "lvgl"] # spell-checker: enable
|
727
|
-
|
728
|
-
gc.collect()
|
729
|
-
|
730
|
-
stubber.create_all_stubs()
|
731
|
-
stubber.report()
|
732
|
-
|
733
|
-
|
734
|
-
if __name__ == "__main__" or is_micropython():
|
735
|
-
if not file_exists("no_auto_stubber.txt"):
|
736
|
-
try:
|
737
|
-
gc.threshold(4 * 1024) # type: ignore
|
738
|
-
gc.enable()
|
739
|
-
except BaseException:
|
740
|
-
pass
|
741
|
-
main()
|
1
|
+
"""
|
2
|
+
Create stubs for the lvgl modules on a MicroPython board.
|
3
|
+
|
4
|
+
Note that the stubs can be very large, and it may be best to directly store them on an SD card if your device supports this.
|
5
|
+
|
6
|
+
This variant was generated from createstubs.py by micropython-stubber v1.16.3
|
7
|
+
"""
|
8
|
+
# Copyright (c) 2019-2023 Jos Verlinde
|
9
|
+
|
10
|
+
import gc
|
11
|
+
import os
|
12
|
+
import sys
|
13
|
+
from time import sleep
|
14
|
+
|
15
|
+
try:
|
16
|
+
from ujson import dumps
|
17
|
+
except:
|
18
|
+
from json import dumps
|
19
|
+
|
20
|
+
try:
|
21
|
+
from machine import reset # type: ignore
|
22
|
+
except ImportError:
|
23
|
+
pass
|
24
|
+
|
25
|
+
try:
|
26
|
+
from collections import OrderedDict
|
27
|
+
except ImportError:
|
28
|
+
from ucollections import OrderedDict # type: ignore
|
29
|
+
|
30
|
+
__version__ = "v1.16.3"
|
31
|
+
ENOENT = 2
|
32
|
+
_MAX_CLASS_LEVEL = 2 # Max class nesting
|
33
|
+
LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
|
34
|
+
|
35
|
+
|
36
|
+
# our own logging module to avoid dependency on and interfering with logging module
|
37
|
+
class logging:
|
38
|
+
DEBUG = 10
|
39
|
+
TRACE = 15
|
40
|
+
INFO = 20
|
41
|
+
WARNING = 30
|
42
|
+
ERROR = 40
|
43
|
+
CRITICAL = 50
|
44
|
+
level = INFO
|
45
|
+
prnt = print
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def getLogger(name):
|
49
|
+
return logging()
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def basicConfig(cls, level):
|
53
|
+
cls.level = level
|
54
|
+
pass
|
55
|
+
|
56
|
+
def trace(self, msg):
|
57
|
+
if self.level <= logging.TRACE:
|
58
|
+
self.prnt("TRACE :", msg)
|
59
|
+
|
60
|
+
def debug(self, msg):
|
61
|
+
if self.level <= logging.DEBUG:
|
62
|
+
self.prnt("DEBUG :", msg)
|
63
|
+
|
64
|
+
def info(self, msg):
|
65
|
+
if self.level <= logging.INFO:
|
66
|
+
self.prnt("INFO :", msg)
|
67
|
+
|
68
|
+
def warning(self, msg):
|
69
|
+
if self.level <= logging.WARNING:
|
70
|
+
self.prnt("WARN :", msg)
|
71
|
+
|
72
|
+
def error(self, msg):
|
73
|
+
if self.level <= logging.ERROR:
|
74
|
+
self.prnt("ERROR :", msg)
|
75
|
+
|
76
|
+
def critical(self, msg):
|
77
|
+
if self.level <= logging.CRITICAL:
|
78
|
+
self.prnt("CRIT :", msg)
|
79
|
+
|
80
|
+
|
81
|
+
log = logging.getLogger("stubber")
|
82
|
+
logging.basicConfig(level=logging.INFO)
|
83
|
+
# logging.basicConfig(level=logging.TRACE)
|
84
|
+
# logging.basicConfig(level=logging.DEBUG)
|
85
|
+
|
86
|
+
|
87
|
+
class Stubber:
|
88
|
+
"Generate stubs for modules in firmware"
|
89
|
+
|
90
|
+
def __init__(self, path: str = None, firmware_id: str = None): # type: ignore
|
91
|
+
try:
|
92
|
+
if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore
|
93
|
+
raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
|
94
|
+
except AttributeError:
|
95
|
+
pass
|
96
|
+
self._report = [] # type: list[str]
|
97
|
+
self.info = _info()
|
98
|
+
log.info("Port: {}".format(self.info["port"]))
|
99
|
+
log.info("Board: {}".format(self.info["board"]))
|
100
|
+
gc.collect()
|
101
|
+
if firmware_id:
|
102
|
+
self._fwid = firmware_id.lower()
|
103
|
+
else:
|
104
|
+
if self.info["family"] == "micropython":
|
105
|
+
self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-")
|
106
|
+
else:
|
107
|
+
self._fwid = "{family}-v{version}-{port}".format(**self.info)
|
108
|
+
self._start_free = gc.mem_free() # type: ignore
|
109
|
+
|
110
|
+
if path:
|
111
|
+
if path.endswith("/"):
|
112
|
+
path = path[:-1]
|
113
|
+
else:
|
114
|
+
path = get_root()
|
115
|
+
|
116
|
+
self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
|
117
|
+
log.debug(self.path)
|
118
|
+
try:
|
119
|
+
ensure_folder(path + "/")
|
120
|
+
except OSError:
|
121
|
+
print("error creating stub folder {}".format(path))
|
122
|
+
self.problematic = [
|
123
|
+
"upip",
|
124
|
+
"upysh",
|
125
|
+
"webrepl_setup",
|
126
|
+
"http_client",
|
127
|
+
"http_client_ssl",
|
128
|
+
"http_server",
|
129
|
+
"http_server_ssl",
|
130
|
+
]
|
131
|
+
self.excluded = [
|
132
|
+
"webrepl",
|
133
|
+
"_webrepl",
|
134
|
+
"port_diag",
|
135
|
+
"example_sub_led.py",
|
136
|
+
"example_pub_button.py",
|
137
|
+
]
|
138
|
+
# there is no option to discover modules from micropython, list is read from an external file.
|
139
|
+
self.modules = [] # type: list[str]
|
140
|
+
|
141
|
+
def get_obj_attributes(self, item_instance: object):
|
142
|
+
"extract information of the objects members and attributes"
|
143
|
+
# name_, repr_(value), type as text, item_instance
|
144
|
+
_result = []
|
145
|
+
_errors = []
|
146
|
+
log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
|
147
|
+
for name in dir(item_instance):
|
148
|
+
if name.startswith("_") and not name in self.modules:
|
149
|
+
continue
|
150
|
+
log.debug("get attribute {}".format(name))
|
151
|
+
try:
|
152
|
+
val = getattr(item_instance, name)
|
153
|
+
# name , item_repr(value) , type as text, item_instance, order
|
154
|
+
log.debug("attribute {}:{}".format(name, val))
|
155
|
+
try:
|
156
|
+
type_text = repr(type(val)).split("'")[1]
|
157
|
+
except IndexError:
|
158
|
+
type_text = ""
|
159
|
+
if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}:
|
160
|
+
order = 1
|
161
|
+
elif type_text in {"function", "method"}:
|
162
|
+
order = 2
|
163
|
+
elif type_text in ("class"):
|
164
|
+
order = 3
|
165
|
+
else:
|
166
|
+
order = 4
|
167
|
+
_result.append((name, repr(val), repr(type(val)), val, order))
|
168
|
+
except AttributeError as e:
|
169
|
+
_errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e))
|
170
|
+
except MemoryError as e:
|
171
|
+
# print("MemoryError: {}".format(e))
|
172
|
+
sleep(1)
|
173
|
+
reset()
|
174
|
+
|
175
|
+
# remove internal __
|
176
|
+
# _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4])
|
177
|
+
_result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4])
|
178
|
+
gc.collect()
|
179
|
+
return _result, _errors
|
180
|
+
|
181
|
+
def add_modules(self, modules):
|
182
|
+
"Add additional modules to be exported"
|
183
|
+
self.modules = sorted(set(self.modules) | set(modules))
|
184
|
+
|
185
|
+
def create_all_stubs(self):
|
186
|
+
"Create stubs for all configured modules"
|
187
|
+
log.info("Start micropython-stubber v{} on {}".format(__version__, self._fwid))
|
188
|
+
gc.collect()
|
189
|
+
for module_name in self.modules:
|
190
|
+
self.create_one_stub(module_name)
|
191
|
+
log.info("Finally done")
|
192
|
+
|
193
|
+
def create_one_stub(self, module_name: str):
|
194
|
+
if module_name in self.problematic:
|
195
|
+
log.warning("Skip module: {:<25} : Known problematic".format(module_name))
|
196
|
+
return False
|
197
|
+
if module_name in self.excluded:
|
198
|
+
log.warning("Skip module: {:<25} : Excluded".format(module_name))
|
199
|
+
return False
|
200
|
+
|
201
|
+
file_name = "{}/{}.py".format(self.path, module_name.replace(".", "/"))
|
202
|
+
gc.collect()
|
203
|
+
result = False
|
204
|
+
try:
|
205
|
+
result = self.create_module_stub(module_name, file_name)
|
206
|
+
except OSError:
|
207
|
+
return False
|
208
|
+
gc.collect()
|
209
|
+
return result
|
210
|
+
|
211
|
+
def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore
|
212
|
+
"""Create a Stub of a single python module
|
213
|
+
|
214
|
+
Args:
|
215
|
+
- module_name (str): name of the module to document. This module will be imported.
|
216
|
+
- file_name (Optional[str]): the 'path/filename.py' to write to. If omitted will be created based on the module name.
|
217
|
+
"""
|
218
|
+
if file_name is None:
|
219
|
+
fname = module_name.replace(".", "_") + ".py"
|
220
|
+
file_name = self.path + "/" + fname
|
221
|
+
else:
|
222
|
+
fname = file_name.split("/")[-1]
|
223
|
+
|
224
|
+
if "/" in module_name:
|
225
|
+
# for nested modules
|
226
|
+
module_name = module_name.replace("/", ".")
|
227
|
+
|
228
|
+
# import the module (as new_module) to examine it
|
229
|
+
new_module = None
|
230
|
+
try:
|
231
|
+
new_module = __import__(module_name, None, None, ("*"))
|
232
|
+
m1 = gc.mem_free() # type: ignore
|
233
|
+
log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
|
234
|
+
|
235
|
+
except ImportError:
|
236
|
+
log.trace("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
|
237
|
+
return False
|
238
|
+
|
239
|
+
# Start a new file
|
240
|
+
ensure_folder(file_name)
|
241
|
+
with open(file_name, "w") as fp:
|
242
|
+
# todo: improve header
|
243
|
+
info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}")
|
244
|
+
s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(module_name, self._fwid, info_, __version__)
|
245
|
+
fp.write(s)
|
246
|
+
fp.write("from __future__ import annotations\nfrom typing import Any\nfrom _typeshed import Incomplete\n\n")
|
247
|
+
self.write_object_stub(fp, new_module, module_name, "")
|
248
|
+
|
249
|
+
self._report.append('{{"module": "{}", "file": "{}"}}'.format(module_name, file_name.replace("\\", "/")))
|
250
|
+
|
251
|
+
if module_name not in {"os", "sys", "logging", "gc"}:
|
252
|
+
# try to unload the module unless we use it
|
253
|
+
try:
|
254
|
+
del new_module
|
255
|
+
except (OSError, KeyError): # lgtm [py/unreachable-statement]
|
256
|
+
log.warning("could not del new_module")
|
257
|
+
# lets not try - most times it does not work anyway
|
258
|
+
# try:
|
259
|
+
# del sys.modules[module_name]
|
260
|
+
# except KeyError:
|
261
|
+
# log.warning("could not del sys.modules[{}]".format(module_name))
|
262
|
+
gc.collect()
|
263
|
+
return True
|
264
|
+
|
265
|
+
def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0):
|
266
|
+
"Write a module/object stub to an open file. Can be called recursive."
|
267
|
+
gc.collect()
|
268
|
+
if object_expr in self.problematic:
|
269
|
+
log.warning("SKIPPING problematic module:{}".format(object_expr))
|
270
|
+
return
|
271
|
+
|
272
|
+
# log.debug("DUMP : {}".format(object_expr))
|
273
|
+
items, errors = self.get_obj_attributes(object_expr)
|
274
|
+
|
275
|
+
if errors:
|
276
|
+
print(errors)
|
277
|
+
|
278
|
+
for item_name, item_repr, item_type_txt, item_instance, _ in items:
|
279
|
+
# name_, repr_(value), type as text, item_instance, order
|
280
|
+
if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]:
|
281
|
+
# do not create stubs for these primitives
|
282
|
+
continue
|
283
|
+
if item_name[0].isdigit():
|
284
|
+
log.warning("NameError: invalid name {}".format(item_name))
|
285
|
+
continue
|
286
|
+
# Class expansion only on first 3 levels (bit of a hack)
|
287
|
+
if (
|
288
|
+
item_type_txt == "<class 'type'>"
|
289
|
+
and len(indent) <= _MAX_CLASS_LEVEL * 4
|
290
|
+
# and not obj_name.endswith(".Pin")
|
291
|
+
# avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
|
292
|
+
):
|
293
|
+
log.trace("{0}class {1}:".format(indent, item_name))
|
294
|
+
superclass = ""
|
295
|
+
is_exception = (
|
296
|
+
item_name.endswith("Exception")
|
297
|
+
or item_name.endswith("Error")
|
298
|
+
or item_name
|
299
|
+
in [
|
300
|
+
"KeyboardInterrupt",
|
301
|
+
"StopIteration",
|
302
|
+
"SystemExit",
|
303
|
+
]
|
304
|
+
)
|
305
|
+
if is_exception:
|
306
|
+
superclass = "Exception"
|
307
|
+
s = "\n{}class {}({}):\n".format(indent, item_name, superclass)
|
308
|
+
# s += indent + " ''\n"
|
309
|
+
if is_exception:
|
310
|
+
s += indent + " ...\n"
|
311
|
+
fp.write(s)
|
312
|
+
continue
|
313
|
+
# write classdef
|
314
|
+
fp.write(s)
|
315
|
+
# first write the class literals and methods
|
316
|
+
log.debug("# recursion over class {0}".format(item_name))
|
317
|
+
self.write_object_stub(
|
318
|
+
fp,
|
319
|
+
item_instance,
|
320
|
+
"{0}.{1}".format(obj_name, item_name),
|
321
|
+
indent + " ",
|
322
|
+
in_class + 1,
|
323
|
+
)
|
324
|
+
# end with the __init__ method to make sure that the literals are defined
|
325
|
+
# Add __init__
|
326
|
+
s = indent + " def __init__(self, *argv, **kwargs) -> None:\n"
|
327
|
+
s += indent + " ...\n\n"
|
328
|
+
fp.write(s)
|
329
|
+
elif any(word in item_type_txt for word in ["method", "function", "closure"]):
|
330
|
+
log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
|
331
|
+
# module Function or class method
|
332
|
+
# will accept any number of params
|
333
|
+
# return type Any/Incomplete
|
334
|
+
ret = "Incomplete"
|
335
|
+
first = ""
|
336
|
+
# Self parameter only on class methods/functions
|
337
|
+
if in_class > 0:
|
338
|
+
first = "self, "
|
339
|
+
# class method - add function decoration
|
340
|
+
if "bound_method" in item_type_txt or "bound_method" in item_repr:
|
341
|
+
s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(indent, item_name, ret)
|
342
|
+
else:
|
343
|
+
s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
|
344
|
+
s += indent + " ...\n\n"
|
345
|
+
fp.write(s)
|
346
|
+
log.debug("\n" + s)
|
347
|
+
elif item_type_txt == "<class 'module'>":
|
348
|
+
# Skip imported modules
|
349
|
+
# fp.write("# import {}\n".format(item_name))
|
350
|
+
pass
|
351
|
+
|
352
|
+
elif item_type_txt.startswith("<class '"):
|
353
|
+
t = item_type_txt[8:-2]
|
354
|
+
s = ""
|
355
|
+
|
356
|
+
if t in ["str", "int", "float", "bool", "bytearray", "bytes"]:
|
357
|
+
# known type: use actual value
|
358
|
+
s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
|
359
|
+
elif t in ["dict", "list", "tuple"]:
|
360
|
+
# dict, list , tuple: use empty value
|
361
|
+
ev = {"dict": "{}", "list": "[]", "tuple": "()"}
|
362
|
+
s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
|
363
|
+
else:
|
364
|
+
# something else
|
365
|
+
if t in ["object", "set", "frozenset", "Pin", "FileIO"]:
|
366
|
+
# https://docs.python.org/3/tutorial/classes.html#item_instance-objects
|
367
|
+
# use these types for the attribute
|
368
|
+
s = "{0}{1} : {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
|
369
|
+
else:
|
370
|
+
# Requires Python 3.6 syntax, which is OK for the stubs/pyi
|
371
|
+
t = "Incomplete"
|
372
|
+
s = "{0}{1} : {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
|
373
|
+
fp.write(s)
|
374
|
+
log.debug("\n" + s)
|
375
|
+
else:
|
376
|
+
# keep only the name
|
377
|
+
log.debug("# all other, type = '{0}'".format(item_type_txt))
|
378
|
+
fp.write("# all other, type = '{0}'\n".format(item_type_txt))
|
379
|
+
|
380
|
+
fp.write(indent + item_name + " # type: Incomplete\n")
|
381
|
+
|
382
|
+
# del items
|
383
|
+
# del errors
|
384
|
+
# try:
|
385
|
+
# del item_name, item_repr, item_type_txt, item_instance # type: ignore
|
386
|
+
# except (OSError, KeyError, NameError):
|
387
|
+
# pass
|
388
|
+
|
389
|
+
@property
|
390
|
+
def flat_fwid(self):
|
391
|
+
"Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename"
|
392
|
+
s = self._fwid
|
393
|
+
# path name restrictions
|
394
|
+
chars = " .()/\\:$"
|
395
|
+
for c in chars:
|
396
|
+
s = s.replace(c, "_")
|
397
|
+
return s
|
398
|
+
|
399
|
+
def clean(self, path: str = None): # type: ignore
|
400
|
+
"Remove all files from the stub folder"
|
401
|
+
if path is None:
|
402
|
+
path = self.path
|
403
|
+
log.info("Clean/remove files in folder: {}".format(path))
|
404
|
+
try:
|
405
|
+
os.stat(path) # TEMP workaround mpremote listdir bug -
|
406
|
+
items = os.listdir(path)
|
407
|
+
except (OSError, AttributeError):
|
408
|
+
# os.listdir fails on unix
|
409
|
+
return
|
410
|
+
for fn in items:
|
411
|
+
item = "{}/{}".format(path, fn)
|
412
|
+
try:
|
413
|
+
os.remove(item)
|
414
|
+
except OSError:
|
415
|
+
try: # folder
|
416
|
+
self.clean(item)
|
417
|
+
os.rmdir(item)
|
418
|
+
except OSError:
|
419
|
+
pass
|
420
|
+
|
421
|
+
def report(self, filename: str = "modules.json"):
|
422
|
+
"create json with list of exported modules"
|
423
|
+
log.info("Created stubs for {} modules on board {}\nPath: {}".format(len(self._report), self._fwid, self.path))
|
424
|
+
f_name = "{}/{}".format(self.path, filename)
|
425
|
+
log.info("Report file: {}".format(f_name))
|
426
|
+
gc.collect()
|
427
|
+
try:
|
428
|
+
# write json by node to reduce memory requirements
|
429
|
+
with open(f_name, "w") as f:
|
430
|
+
self.write_json_header(f)
|
431
|
+
first = True
|
432
|
+
for n in self._report:
|
433
|
+
self.write_json_node(f, n, first)
|
434
|
+
first = False
|
435
|
+
self.write_json_end(f)
|
436
|
+
used = self._start_free - gc.mem_free() # type: ignore
|
437
|
+
log.trace("Memory used: {0} Kb".format(used // 1024))
|
438
|
+
except OSError:
|
439
|
+
print("Failed to create the report.")
|
440
|
+
|
441
|
+
def write_json_header(self, f):
|
442
|
+
f.write("{")
|
443
|
+
f.write(dumps({"firmware": self.info})[1:-1])
|
444
|
+
f.write(",\n")
|
445
|
+
f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
|
446
|
+
f.write(",\n")
|
447
|
+
f.write('"modules" :[\n')
|
448
|
+
|
449
|
+
def write_json_node(self, f, n, first):
|
450
|
+
if not first:
|
451
|
+
f.write(",\n")
|
452
|
+
f.write(n)
|
453
|
+
|
454
|
+
def write_json_end(self, f):
|
455
|
+
f.write("\n]}")
|
456
|
+
|
457
|
+
|
458
|
+
def ensure_folder(path: str):
|
459
|
+
"Create nested folders if needed"
|
460
|
+
i = start = 0
|
461
|
+
while i != -1:
|
462
|
+
i = path.find("/", start)
|
463
|
+
if i != -1:
|
464
|
+
p = path[0] if i == 0 else path[:i]
|
465
|
+
# p = partial folder
|
466
|
+
try:
|
467
|
+
_ = os.stat(p)
|
468
|
+
except OSError as e:
|
469
|
+
# folder does not exist
|
470
|
+
if e.args[0] == ENOENT:
|
471
|
+
try:
|
472
|
+
os.mkdir(p)
|
473
|
+
except OSError as e2:
|
474
|
+
print("failed to create folder {}".format(p))
|
475
|
+
raise e2
|
476
|
+
# next level deep
|
477
|
+
start = i + 1
|
478
|
+
|
479
|
+
|
480
|
+
def _build(s):
|
481
|
+
# extract build from sys.version or os.uname().version if available
|
482
|
+
# sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
|
483
|
+
# sys.implementation.version: 'v1.13-103-gb137d064e'
|
484
|
+
if not s:
|
485
|
+
return ""
|
486
|
+
s = s.split(" on ", 1)[0] if " on " in s else s
|
487
|
+
if s.startswith("v"):
|
488
|
+
if not "-" in s:
|
489
|
+
return ""
|
490
|
+
b = s.split("-")[1]
|
491
|
+
return b
|
492
|
+
if not "-preview" in s:
|
493
|
+
return ""
|
494
|
+
b = s.split("-preview")[1].split(".")[1]
|
495
|
+
return b
|
496
|
+
|
497
|
+
|
498
|
+
def _info(): # type:() -> dict[str, str]
|
499
|
+
info = OrderedDict(
|
500
|
+
{
|
501
|
+
"family": sys.implementation.name,
|
502
|
+
"version": "",
|
503
|
+
"build": "",
|
504
|
+
"ver": "",
|
505
|
+
"port": sys.platform, # port: esp32 / win32 / linux / stm32
|
506
|
+
"board": "UNKNOWN",
|
507
|
+
"cpu": "",
|
508
|
+
"mpy": "",
|
509
|
+
"arch": "",
|
510
|
+
}
|
511
|
+
)
|
512
|
+
# change port names to be consistent with the repo
|
513
|
+
if info["port"].startswith("pyb"):
|
514
|
+
info["port"] = "stm32"
|
515
|
+
elif info["port"] == "win32":
|
516
|
+
info["port"] = "windows"
|
517
|
+
elif info["port"] == "linux":
|
518
|
+
info["port"] = "unix"
|
519
|
+
try:
|
520
|
+
info["version"] = version_str(sys.implementation.version) # type: ignore
|
521
|
+
except AttributeError:
|
522
|
+
pass
|
523
|
+
try:
|
524
|
+
_machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
|
525
|
+
# info["board"] = "with".join(_machine.split("with")[:-1]).strip()
|
526
|
+
info["board"] = _machine
|
527
|
+
info["cpu"] = _machine.split("with")[-1].strip()
|
528
|
+
info["mpy"] = (
|
529
|
+
sys.implementation._mpy
|
530
|
+
if "_mpy" in dir(sys.implementation)
|
531
|
+
else sys.implementation.mpy
|
532
|
+
if "mpy" in dir(sys.implementation)
|
533
|
+
else ""
|
534
|
+
)
|
535
|
+
except (AttributeError, IndexError):
|
536
|
+
pass
|
537
|
+
info["board"] = get_boardname()
|
538
|
+
|
539
|
+
try:
|
540
|
+
if "uname" in dir(os): # old
|
541
|
+
# extract build from uname().version if available
|
542
|
+
info["build"] = _build(os.uname()[3]) # type: ignore
|
543
|
+
if not info["build"]:
|
544
|
+
# extract build from uname().release if available
|
545
|
+
info["build"] = _build(os.uname()[2]) # type: ignore
|
546
|
+
elif "version" in dir(sys): # new
|
547
|
+
# extract build from sys.version if available
|
548
|
+
info["build"] = _build(sys.version)
|
549
|
+
except (AttributeError, IndexError, TypeError):
|
550
|
+
pass
|
551
|
+
# avoid build hashes
|
552
|
+
# if info["build"] and len(info["build"]) > 5:
|
553
|
+
# info["build"] = ""
|
554
|
+
|
555
|
+
if info["version"] == "" and sys.platform not in ("unix", "win32"):
|
556
|
+
try:
|
557
|
+
u = os.uname() # type: ignore
|
558
|
+
info["version"] = u.release
|
559
|
+
except (IndexError, AttributeError, TypeError):
|
560
|
+
pass
|
561
|
+
# detect families
|
562
|
+
for fam_name, mod_name, mod_thing in [
|
563
|
+
("pycopy", "pycopy", "const"),
|
564
|
+
("pycom", "pycom", "FAT"),
|
565
|
+
("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
|
566
|
+
]:
|
567
|
+
try:
|
568
|
+
_t = __import__(mod_name, None, None, (mod_thing))
|
569
|
+
info["family"] = fam_name
|
570
|
+
del _t
|
571
|
+
break
|
572
|
+
except (ImportError, KeyError):
|
573
|
+
pass
|
574
|
+
|
575
|
+
if info["family"] == "ev3-pybricks":
|
576
|
+
info["release"] = "2.0.0"
|
577
|
+
|
578
|
+
if info["family"] == "micropython":
|
579
|
+
info["version"]
|
580
|
+
if (
|
581
|
+
info["version"]
|
582
|
+
and info["version"].endswith(".0")
|
583
|
+
and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
|
584
|
+
and info["version"] <= "1.19.9"
|
585
|
+
):
|
586
|
+
# versions from 1.10.0 to 1.20.0 do not have a micro .0
|
587
|
+
info["version"] = info["version"][:-2]
|
588
|
+
|
589
|
+
# spell-checker: disable
|
590
|
+
if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
|
591
|
+
sys_mpy = int(info["mpy"])
|
592
|
+
# .mpy architecture
|
593
|
+
arch = [
|
594
|
+
None,
|
595
|
+
"x86",
|
596
|
+
"x64",
|
597
|
+
"armv6",
|
598
|
+
"armv6m",
|
599
|
+
"armv7m",
|
600
|
+
"armv7em",
|
601
|
+
"armv7emsp",
|
602
|
+
"armv7emdp",
|
603
|
+
"xtensa",
|
604
|
+
"xtensawin",
|
605
|
+
][sys_mpy >> 10]
|
606
|
+
if arch:
|
607
|
+
info["arch"] = arch
|
608
|
+
# .mpy version.minor
|
609
|
+
info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
|
610
|
+
if info["build"] and not info["version"].endswith("-preview"):
|
611
|
+
info["version"] = info["version"] + "-preview"
|
612
|
+
# simple to use version[-build] string
|
613
|
+
info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}"
|
614
|
+
|
615
|
+
return info
|
616
|
+
|
617
|
+
|
618
|
+
def version_str(version: tuple): # -> str:
|
619
|
+
v_str = ".".join([str(n) for n in version[:3]])
|
620
|
+
if len(version) > 3 and version[3]:
|
621
|
+
v_str += "-" + version[3]
|
622
|
+
return v_str
|
623
|
+
|
624
|
+
|
625
|
+
def get_boardname() -> str:
|
626
|
+
"Read the board name from the boardname.py file that may have been created upfront"
|
627
|
+
try:
|
628
|
+
from boardname import BOARDNAME # type: ignore
|
629
|
+
|
630
|
+
log.info("Found BOARDNAME: {}".format(BOARDNAME))
|
631
|
+
except ImportError:
|
632
|
+
log.warning("BOARDNAME not found")
|
633
|
+
BOARDNAME = ""
|
634
|
+
return BOARDNAME
|
635
|
+
|
636
|
+
|
637
|
+
def get_root() -> str: # sourcery skip: use-assigned-variable
|
638
|
+
"Determine the root folder of the device"
|
639
|
+
try:
|
640
|
+
c = os.getcwd()
|
641
|
+
except (OSError, AttributeError):
|
642
|
+
# unix port
|
643
|
+
c = "."
|
644
|
+
r = c
|
645
|
+
for r in [c, "/sd", "/flash", "/", "."]:
|
646
|
+
try:
|
647
|
+
_ = os.stat(r)
|
648
|
+
break
|
649
|
+
except OSError:
|
650
|
+
continue
|
651
|
+
return r
|
652
|
+
|
653
|
+
|
654
|
+
def file_exists(filename: str):
|
655
|
+
try:
|
656
|
+
if os.stat(filename)[0] >> 14:
|
657
|
+
return True
|
658
|
+
return False
|
659
|
+
except OSError:
|
660
|
+
return False
|
661
|
+
|
662
|
+
|
663
|
+
def show_help():
|
664
|
+
# print("-p, --path path to store the stubs in, defaults to '.'")
|
665
|
+
sys.exit(1)
|
666
|
+
|
667
|
+
|
668
|
+
def read_path() -> str:
|
669
|
+
"get --path from cmdline. [unix/win]"
|
670
|
+
path = ""
|
671
|
+
if len(sys.argv) == 3:
|
672
|
+
cmd = (sys.argv[1]).lower()
|
673
|
+
if cmd in ("--path", "-p"):
|
674
|
+
path = sys.argv[2]
|
675
|
+
else:
|
676
|
+
show_help()
|
677
|
+
elif len(sys.argv) == 2:
|
678
|
+
show_help()
|
679
|
+
return path
|
680
|
+
|
681
|
+
|
682
|
+
def is_micropython() -> bool:
|
683
|
+
"runtime test to determine full or micropython"
|
684
|
+
# pylint: disable=unused-variable,eval-used
|
685
|
+
try:
|
686
|
+
# either test should fail on micropython
|
687
|
+
# a) https://docs.micropython.org/en/latest/genrst/syntax.html#spaces
|
688
|
+
# Micropython : SyntaxError
|
689
|
+
# a = eval("1and 0") # lgtm [py/unused-local-variable]
|
690
|
+
# Eval blocks some minification aspects
|
691
|
+
|
692
|
+
# b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
|
693
|
+
# Micropython: NotImplementedError
|
694
|
+
b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable]
|
695
|
+
|
696
|
+
# c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute
|
697
|
+
# Micropython: AttributeError
|
698
|
+
c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable]
|
699
|
+
return False
|
700
|
+
except (NotImplementedError, AttributeError):
|
701
|
+
return True
|
702
|
+
|
703
|
+
|
704
|
+
def main():
|
705
|
+
try:
|
706
|
+
import lvgl # type: ignore
|
707
|
+
except Exception:
|
708
|
+
# print("\n\nNOTE: The `lvgl` module could not be found on this firmware\n\n")
|
709
|
+
return
|
710
|
+
# Specify firmware name & version
|
711
|
+
fw_id = "lvgl"
|
712
|
+
try:
|
713
|
+
fw_id = "lvgl-{0}_{1}_{2}-{3}-{4}".format(
|
714
|
+
lvgl.version_major(),
|
715
|
+
lvgl.version_minor(),
|
716
|
+
lvgl.version_patch(),
|
717
|
+
lvgl.version_info(),
|
718
|
+
sys.platform,
|
719
|
+
)
|
720
|
+
except Exception:
|
721
|
+
fw_id = "lvgl-{0}_{1}_{2}_{3}-{4}".format(8, 1, 0, "dev", sys.platform)
|
722
|
+
finally:
|
723
|
+
stubber = Stubber(firmware_id=fw_id)
|
724
|
+
stubber.clean()
|
725
|
+
# modules to stub : only lvgl specifics
|
726
|
+
stubber.modules = ["io", "lodepng", "rtch", "lvgl"] # spell-checker: enable
|
727
|
+
|
728
|
+
gc.collect()
|
729
|
+
|
730
|
+
stubber.create_all_stubs()
|
731
|
+
stubber.report()
|
732
|
+
|
733
|
+
|
734
|
+
if __name__ == "__main__" or is_micropython():
|
735
|
+
if not file_exists("no_auto_stubber.txt"):
|
736
|
+
try:
|
737
|
+
gc.threshold(4 * 1024) # type: ignore
|
738
|
+
gc.enable()
|
739
|
+
except BaseException:
|
740
|
+
pass
|
741
|
+
main()
|