Skip to content

Commit 9306670

Browse files
committed
device specific stubs
1 parent cd28f1d commit 9306670

File tree

4 files changed

+338
-0
lines changed

4 files changed

+338
-0
lines changed

Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ stubs:
271271
@cp setup.py-stubs circuitpython-stubs/setup.py
272272
@cp README.rst-stubs circuitpython-stubs/README.rst
273273
@cp MANIFEST.in-stubs circuitpython-stubs/MANIFEST.in
274+
@$(PYTHON) tools/board_stubs/build_board_specific_stubs/board_stub_builder.py
275+
@cp -r tools/board_stubs/circuitpython_setboard circuitpython-stubs/circuitpython_setboard
274276
@$(PYTHON) -m build circuitpython-stubs
275277

276278
.PHONY: check-stubs

setup.py-stubs

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def build_package_data() -> Dict[str, List[str]]:
2424
result = {}
2525
for package in packages:
2626
result[f"{package}-stubs"] = ["*.pyi", "*/*.pyi"]
27+
result['circuitpython_setboard'] = ["*.py", "*/*.py"]
28+
result['board_definitions'] = ["*.pyi", "*/*.pyi"]
2729
return result
2830

2931
package_data=build_package_data()
@@ -35,6 +37,11 @@ setup(
3537
maintainer_email="circuitpython@adafruit.com",
3638
author_email="circuitpython@adafruit.com",
3739
license="MIT",
40+
entry_points = {
41+
'console_scripts': [
42+
'circuitpython_setboard = circuitpython_setboard:set_board'
43+
]
44+
},
3845
packages=list(package_data.keys()),
3946
package_data=package_data,
4047
package_dir = package_dir,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
import json
6+
import os
7+
import re
8+
import sys
9+
from collections import defaultdict
10+
11+
12+
def get_board_pins(pin_filename):
13+
records = []
14+
15+
with open(pin_filename, encoding="utf-8") as pin_file:
16+
for line in pin_file:
17+
if line.strip()[0:2] == "//":
18+
continue
19+
20+
search = re.search(r"MP_ROM_QSTR\(MP_QSTR_(.*?)\), MP_ROM_PTR", line)
21+
if search is None:
22+
search = re.search(
23+
r"MP_OBJ_NEW_QSTR\(MP_QSTR_(.*?)\), MP_ROM_PTR", line
24+
)
25+
if search is None:
26+
continue
27+
28+
board_member = search.group(1)
29+
30+
board_type_search = re.search(r"MP_ROM_PTR\(&pin_(.*?)\)", line)
31+
if board_type_search:
32+
board_type = "pin"
33+
else:
34+
if board_type_search is None:
35+
board_type_search = re.search(r"MP_ROM_PTR\(&(.*?)_obj", line)
36+
if board_type_search is None:
37+
board_type_search = re.search(
38+
r"MP_ROM_PTR\(&(.*?)\[0\].[display|epaper_display]", line
39+
)
40+
if board_type_search is None:
41+
records.append(["unmapped", None, line])
42+
continue
43+
44+
board_type = board_type_search.group(1)
45+
46+
extra_search = None
47+
extra = None
48+
49+
if board_type == "pin":
50+
extra_search = re.search(r"&pin_(.*?)\)", line)
51+
52+
elif board_type == "displays":
53+
extra_search = re.search(r"&displays\[0\].(.*?)\)", line)
54+
55+
if extra_search:
56+
extra = extra_search.group(1)
57+
58+
records.append([board_type, board_member, extra])
59+
60+
return records
61+
62+
63+
def create_board_stubs(board_id, records, mappings, board_filename):
64+
pins = []
65+
members = []
66+
unmapped = []
67+
68+
needs_busio = False
69+
needs_displayio = False
70+
needs_microcontroller = False
71+
72+
for board_type, board_member, extra in records:
73+
if board_type == "pin":
74+
needs_microcontroller = True
75+
comment = f" # {extra}"
76+
pins.append(f"{board_member}: microcontroller.Pin{comment}")
77+
78+
elif board_type == "board_i2c":
79+
needs_busio = True
80+
import_name = "I2C"
81+
class_name = f"busio.{import_name}"
82+
member_data = f"def {board_member}() -> {class_name}:\n"
83+
member_data += f' """Returns the `{class_name}` object for the board\'s designated I2C bus(es).\n'
84+
member_data += f" The object created is a singleton, and uses the default parameter values for `{class_name}`.\n"
85+
member_data += ' """\n'
86+
members.append(member_data)
87+
88+
elif board_type == "board_spi":
89+
needs_busio = True
90+
import_name = "SPI"
91+
class_name = f"busio.{import_name}"
92+
member_data = f"def {board_member}() -> {class_name}:\n"
93+
member_data += f' """Returns the `{class_name}` object for the board\'s designated SPI bus(es).\n'
94+
member_data += f" The object created is a singleton, and uses the default parameter values for `{class_name}`.\n"
95+
member_data += ' """\n'
96+
members.append(member_data)
97+
98+
elif board_type == "board_uart":
99+
needs_busio = True
100+
import_name = "UART"
101+
class_name = f"busio.{import_name}"
102+
member_data = f"def {board_member}() -> {class_name}:\n"
103+
member_data += f' """Returns the `{class_name}` object for the board\'s designated UART bus(es).\n'
104+
member_data += f" The object created is a singleton, and uses the default parameter values for `{class_name}`.\n"
105+
member_data += ' """\n'
106+
members.append(member_data)
107+
108+
elif board_type == "displays":
109+
needs_displayio = True
110+
if extra == "display":
111+
import_name = "Display"
112+
elif extra == "epaper_display":
113+
import_name = "EPaperDisplay"
114+
class_name = f"displayio.{import_name}"
115+
member_data = f'"""Returns the `{class_name}` object for the board\'s built in display.\n'
116+
member_data += f"The object created is a singleton, and uses the default parameter values for `{class_name}`.\n"
117+
member_data += '"""\n'
118+
member_data += f"{board_member}: {class_name}\n"
119+
members.append(member_data)
120+
121+
elif board_type == "unmapped":
122+
unmapped.append(extra)
123+
124+
libraries = mappings["libraries"]
125+
included_modules = "Unknown"
126+
frozen_libraries = "Unknown"
127+
if "modules" in libraries:
128+
included_modules = ", ".join(libraries["modules"])
129+
if "frozen_libraries" in libraries:
130+
frozen_libraries = ", ".join(libraries["frozen_libraries"])
131+
132+
with open(board_filename, "w") as boards_file:
133+
boards_file.write(
134+
"# SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries\n"
135+
)
136+
boards_file.write("#\n")
137+
boards_file.write("# SPDX-License-Identifier: MIT\n")
138+
boards_file.write('"""\n')
139+
boards_file.write(f'Board stub for {mappings["board_name"]}\n')
140+
boards_file.write(f' - port: {mappings["port"]}\n')
141+
boards_file.write(f" - board_id: {board_id}\n")
142+
boards_file.write(f' - NVM size: {mappings["nvm_size"]}\n')
143+
boards_file.write(f" - Included modules: {included_modules}\n")
144+
boards_file.write(f" - Frozen libraries: {frozen_libraries}\n")
145+
boards_file.write('"""\n\n')
146+
boards_file.write("# Imports\n")
147+
if needs_busio:
148+
boards_file.write("import busio\n")
149+
if needs_displayio:
150+
boards_file.write("import displayio\n")
151+
if needs_microcontroller:
152+
boards_file.write("import microcontroller\n")
153+
154+
boards_file.write("\n\n")
155+
boards_file.write("# Board Info:\n")
156+
boards_file.write("board_id: str\n")
157+
158+
boards_file.write("\n\n")
159+
boards_file.write("# Pins:\n")
160+
for pin in pins:
161+
boards_file.write(f"{pin}\n")
162+
163+
boards_file.write("\n\n")
164+
boards_file.write("# Members:\n")
165+
for member in members:
166+
boards_file.write(f"{member}\n")
167+
168+
boards_file.write("\n")
169+
boards_file.write("# Unmapped:\n")
170+
if not unmapped:
171+
boards_file.write("# none\n")
172+
for record in unmapped:
173+
boards_file.write(f"# {record}")
174+
175+
176+
def process(board_mappings, export_dir):
177+
total_boards = 0
178+
total_pins = 0
179+
total_members = 0
180+
total_unmapped = 0
181+
skipped_boards = []
182+
unmapped_boards = defaultdict(int)
183+
unmapped_values = defaultdict(list)
184+
185+
for board_id, mappings in board_mappings.items():
186+
total_boards += 1
187+
188+
if "pins_filename" not in mappings:
189+
skipped_boards.append(board_id)
190+
continue
191+
192+
pin_filename = mappings["pins_filename"]
193+
194+
sub_dir = f"{export_dir}/{board_id}"
195+
if not os.path.exists(sub_dir):
196+
os.makedirs(sub_dir)
197+
198+
try:
199+
records = get_board_pins(pin_filename)
200+
create_board_stubs(board_id, records, mappings, f"{sub_dir}/__init__.pyi")
201+
202+
for board_type, board_member, extra in records:
203+
if board_type == "pin":
204+
total_pins += 1
205+
elif board_type == "unmapped":
206+
unmapped_boards[board_id] += 1
207+
unmapped_values[extra].append(board_id)
208+
total_unmapped += 1
209+
else:
210+
total_members += 1
211+
212+
except Exception as e:
213+
print(f" - {e}")
214+
215+
unmapped_percent = total_unmapped / (total_pins + total_members + total_unmapped)
216+
217+
print("\nTotals:")
218+
print(f" boards: {total_boards}")
219+
print(f" pins: {total_pins}")
220+
print(f" members: {total_members}")
221+
print(f" unmapped: {total_unmapped} ({unmapped_percent:.5f}%)")
222+
print("\n\nSkipped Boards")
223+
for board in skipped_boards:
224+
print(f" {board}")
225+
print("\n\nBoards with Unmapped Pins:")
226+
for board, total in unmapped_boards.items():
227+
print(f" {board}: {total}")
228+
print("\n\nUnmapped Pins:")
229+
for unmapped, boards in unmapped_values.items():
230+
print(f" {unmapped.strip()}")
231+
for board in boards:
232+
print(f" - {board}")
233+
234+
235+
def build_stubs(circuitpython_dir, circuitpython_org_dir, export_dir, version="8.2.9"):
236+
if circuitpython_dir[-1] != "/":
237+
circuitpython_dir = circuitpython_dir + "/"
238+
239+
sys.path.append(circuitpython_dir)
240+
from docs import shared_bindings_matrix
241+
242+
if not os.path.exists(export_dir):
243+
os.makedirs(export_dir)
244+
245+
libraries = {}
246+
if circuitpython_org_dir is None:
247+
libraries = shared_bindings_matrix.support_matrix_by_board(use_branded_name=False, withurl=False)
248+
else:
249+
with open(f"{circuitpython_org_dir}/_data/files.json") as libraries_file:
250+
libraries_list = json.load(libraries_file)
251+
for library in libraries_list:
252+
board = library["id"]
253+
for version_data in library["versions"]:
254+
if version_data["version"] == version:
255+
libraries[board] = version_data
256+
257+
aliases = {}
258+
for board, renames in shared_bindings_matrix.ALIASES_BY_BOARD.items():
259+
for rename in renames:
260+
aliases[rename] = board
261+
262+
board_mappings = shared_bindings_matrix.get_board_mapping()
263+
for board, board_data in board_mappings.items():
264+
if board in aliases:
265+
lookup = aliases[board]
266+
else:
267+
lookup = board
268+
269+
port_path = f'{circuitpython_dir}ports/{board_data["port"]}/'
270+
board_path = f"{port_path}boards/{lookup}/"
271+
pins_path = f"{board_path}pins.c"
272+
if not os.path.isfile(pins_path):
273+
print(f"Could not find pin file for {lookup}")
274+
continue
275+
276+
board_mappings[board]["pins_filename"] = pins_path
277+
278+
nvm_size = "Unknown"
279+
with open(f"{port_path}/mpconfigport.h") as get_name:
280+
port_contents = get_name.read()
281+
port_nvm_re = re.search(
282+
r"(?<=#define CIRCUITPY_INTERNAL_NVM_SIZE)\s+(.+)", port_contents
283+
)
284+
if port_nvm_re:
285+
nvm_size = port_nvm_re.group(1).strip()
286+
287+
board_name = board
288+
with open(f"{board_path}/mpconfigboard.h") as get_name:
289+
board_contents = get_name.read()
290+
board_name_re = re.search(
291+
r"(?<=MICROPY_HW_BOARD_NAME)\s+(.+)", board_contents
292+
)
293+
if board_name_re:
294+
board_name = board_name_re.group(1).strip('"')
295+
296+
port_nvm_re = re.search(
297+
r"(?<=#define CIRCUITPY_INTERNAL_NVM_SIZE)\s+(.+)", port_contents
298+
)
299+
if port_nvm_re:
300+
nvm_size = port_nvm_re.group(1).strip()
301+
302+
nvm_size_re = re.search(r"^[0-9\(\) *]*$", nvm_size)
303+
if nvm_size_re:
304+
nvm_size = eval(nvm_size_re.group(0))
305+
306+
board_mappings[board]["board_name"] = board_name
307+
board_mappings[board]["nvm_size"] = nvm_size
308+
board_mappings[board]["libraries"] = libraries.get(board, None)
309+
310+
process(board_mappings, export_dir)
311+
312+
if __name__ == '__main__':
313+
build_stubs("./", None, "circuitpython-stubs/board_definitions/")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import sys
2+
import os
3+
import shutil
4+
5+
def set_board():
6+
chosen_board = sys.argv[1]
7+
#print(f"setting it {sys.argv[0]} - {sys.argv[1]}")
8+
print(f"setting board: {chosen_board}")
9+
board_defs_path = os.path.sep.join(__file__.split("/")[:-2]) + f"{os.path.sep}board_definitions{os.path.sep}"
10+
board_stubs_path = os.path.sep.join(__file__.split("/")[:-2]) + f"{os.path.sep}board-stubs{os.path.sep}__init__.pyi"
11+
12+
if chosen_board not in os.listdir(board_defs_path):
13+
print(f"Board: '{chosen_board}' was not found")
14+
return
15+
16+
shutil.copyfile(board_defs_path + f"{os.path.sep}{chosen_board}{os.path.sep}__init__.pyi", board_stubs_path)

0 commit comments

Comments
 (0)