|
24 | 24 | import json
|
25 | 25 | import os
|
26 | 26 | import re
|
| 27 | +import subprocess |
| 28 | +import sys |
27 | 29 |
|
28 | 30 |
|
29 |
| -SUPPORTED_PORTS = ["atmel-samd", "nrf", "stm", "mimxrt10xx"] |
30 |
| - |
31 |
| - |
32 |
| -def parse_port_config(contents, chip_keyword=None): |
33 |
| - """ Compile a dictionary of port-wide module configs, which may |
34 |
| - be categorized by chipset. |
35 |
| - """ |
36 |
| - chip_fam = "all" |
37 |
| - ifeq_found = False |
38 |
| - port_config_results = {"all": []} |
39 |
| - |
40 |
| - chip_pattern = "" |
41 |
| - if chip_keyword: |
42 |
| - chip_pattern = ( |
43 |
| - re.compile("(?<=ifeq\s\(\$\({}\)\,)(\w+)".format(chip_keyword)) |
44 |
| - ) |
45 |
| - |
46 |
| - for line in contents: |
47 |
| - if chip_keyword: |
48 |
| - if not ifeq_found: |
49 |
| - check_ifeq = chip_pattern.search(line) |
50 |
| - if check_ifeq: |
51 |
| - ifeq_found = True |
52 |
| - chip_fam = check_ifeq.group(1) |
53 |
| - #print("found chip:", chip_fam) |
54 |
| - else: |
55 |
| - ifeq_found = False |
56 |
| - chip_fam = "all" |
57 |
| - else: |
58 |
| - if "endif" in line: |
59 |
| - ifeq_found = False |
60 |
| - chip_fam = "all" |
61 |
| - |
62 |
| - if "CIRCUITPY_" in line: |
63 |
| - if chip_fam in port_config_results: |
64 |
| - port_config_results[chip_fam].append(line.rstrip("\n")) |
65 |
| - else: |
66 |
| - port_config_results[chip_fam] = [line.rstrip("\n")] |
67 |
| - |
68 |
| - #print(port_config_results) |
69 |
| - return port_config_results |
| 31 | +SUPPORTED_PORTS = ['atmel-samd', 'esp32s2', 'litex', 'mimxrt10xx', 'nrf', 'stm'] |
70 | 32 |
|
71 | 33 | def get_shared_bindings():
|
72 | 34 | """ Get a list of modules in shared-bindings based on folder names
|
@@ -117,168 +79,80 @@ def build_module_map():
|
117 | 79 | "excluded": {}
|
118 | 80 | }
|
119 | 81 |
|
120 |
| - #print(base) |
121 | 82 | return base
|
122 | 83 |
|
| 84 | +def get_settings_from_makefile(port_dir, board_name): |
| 85 | + """ Invoke make in a mode which prints the database, then parse it for |
| 86 | + settings. |
123 | 87 |
|
124 |
| -def get_excluded_boards(base): |
125 |
| - """ Cycles through each board's `mpconfigboard.mk` file to determine |
126 |
| - if each module is included or not. Boards are selected by existence |
127 |
| - in a port listed in `SUPPORTED_PORTS` (e.g. `/port/nrf/feather_52840`) |
128 |
| -
|
129 |
| - Boards are further categorized by their respective chipset (SAMD21, |
130 |
| - SAMD51, nRF52840, etc.) |
| 88 | + This means that the effect of all Makefile directives is taken |
| 89 | + into account, without having to re-encode the logic that sets them |
| 90 | + in this script, something that has proved error-prone |
131 | 91 | """
|
132 |
| - modules = list(base.keys()) |
133 |
| - |
134 |
| - re_board_chip = None |
135 |
| - chip_keyword = None |
136 |
| - for port in SUPPORTED_PORTS: |
137 |
| - # each port appears to use its own define for the chipset |
138 |
| - if port in ["atmel-samd"]: |
139 |
| - re_board_chip = re.compile("CHIP_FAMILY\s=\s(\w+)") |
140 |
| - chip_keyword = "CHIP_FAMILY" |
141 |
| - elif port in ["nrf"]: |
142 |
| - re_board_chip = re.compile(r"MCU_VARIANT\s=\s(\w+)") |
143 |
| - elif port in ["stm"]: |
144 |
| - re_board_chip = re.compile(r"MCU_SERIES\s*=\s*(\w+)") |
145 |
| - chip_keyword = "MCU_SERIES" |
146 |
| - |
147 |
| - port_dir = "ports/{}".format(port) |
148 |
| - |
149 |
| - port_config_contents = "" |
150 |
| - with open(os.path.join(port_dir, "mpconfigport.mk")) as port_config: |
151 |
| - port_config_contents = port_config.readlines() |
152 |
| - port_config = parse_port_config(port_config_contents, chip_keyword) |
153 |
| - |
154 |
| - for entry in os.scandir(os.path.join(port_dir, "boards")): |
155 |
| - if not entry.is_dir(): |
156 |
| - continue |
157 |
| - |
158 |
| - contents = "" |
159 |
| - board_dir = os.path.join(entry.path, "mpconfigboard.mk") |
160 |
| - with open(board_dir) as board: |
161 |
| - contents = board.read() |
162 |
| - |
163 |
| - board_chip = re_board_chip.search(contents) |
164 |
| - if not board_chip: |
165 |
| - board_chip = "Unknown Chip" |
166 |
| - else: |
167 |
| - #print(entry.name, board_chip.group(1)) |
168 |
| - board_chip = board_chip.group(1) |
169 |
| - |
170 |
| - # add port_config results to contents |
171 |
| - contents += "\n" + "\n".join(port_config["all"]) |
172 |
| - if board_chip in port_config: |
173 |
| - contents += "\n" + "\n".join(port_config[board_chip]) |
174 |
| - |
175 |
| - check_dependent_modules = dict() |
176 |
| - for module in modules: |
177 |
| - board_is_excluded = False |
178 |
| - # check if board turns off `FULL_BUILD`. if yes, and current |
179 |
| - # module is marked as `FULL_BUILD`, board is excluded |
180 |
| - small_build = re.search("CIRCUITPY_FULL_BUILD = 0", contents) |
181 |
| - if small_build and base[module]["full_build"] == "1": |
182 |
| - board_is_excluded = True |
183 |
| - |
184 |
| - # check if module is specifically disabled for this board |
185 |
| - re_pattern = r"CIRCUITPY_{}\s=\s(\w)".format(module.upper()) |
186 |
| - find_module = re.search(re_pattern, contents) |
187 |
| - if not find_module: |
188 |
| - if base[module]["default_value"].isdigit(): |
189 |
| - # check if default inclusion is off ('0'). if the board doesn't |
190 |
| - # have it explicitly enabled, its excluded. |
191 |
| - if base[module]["default_value"] == "0": |
192 |
| - board_is_excluded = True |
193 |
| - else: |
194 |
| - # this module is dependent on another module. add it |
195 |
| - # to the list to check after processing all other modules. |
196 |
| - # only need to check exclusion if it isn't already excluded. |
197 |
| - if (not board_is_excluded and |
198 |
| - base[module]["default_value"] not in [ |
199 |
| - "None", |
200 |
| - "CIRCUITPY_DEFAULT_BUILD" |
201 |
| - ]): |
202 |
| - check_dependent_modules[module] = base[module]["default_value"] |
203 |
| - else: |
204 |
| - board_is_excluded = find_module.group(1) == "0" |
205 |
| - |
206 |
| - if board_is_excluded: |
207 |
| - if board_chip in base[module]["excluded"]: |
208 |
| - base[module]["excluded"][board_chip].append(entry.name) |
209 |
| - else: |
210 |
| - base[module]["excluded"][board_chip] = [entry.name] |
211 |
| - |
212 |
| - for module in check_dependent_modules: |
213 |
| - depend_results = set() |
214 |
| - |
215 |
| - parents = check_dependent_modules[module].split("CIRCUITPY_") |
216 |
| - parents = [item.strip(", ").lower() for item in parents if item] |
217 |
| - |
218 |
| - for parent in parents: |
219 |
| - if parent in base: |
220 |
| - if (board_chip in base[parent]["excluded"] and |
221 |
| - entry.name in base[parent]["excluded"][board_chip]): |
222 |
| - depend_results.add(False) |
223 |
| - else: |
224 |
| - depend_results.add(True) |
225 |
| - |
226 |
| - # only exclude the module if there were zero parents enabled |
227 |
| - # as determined by the 'depend_results' set. |
228 |
| - if not any(depend_results): |
229 |
| - if board_chip in base[module]["excluded"]: |
230 |
| - base[module]["excluded"][board_chip].append(entry.name) |
231 |
| - else: |
232 |
| - base[module]["excluded"][board_chip] = [entry.name] |
233 |
| - |
234 |
| - #print(json.dumps(base, indent=2)) |
235 |
| - return base |
| 92 | + contents = subprocess.run( |
| 93 | + ["make", "-C", port_dir, f"BOARD={board_name}", "-qp", "print-CC"], |
| 94 | + encoding="utf-8", |
| 95 | + errors="replace", |
| 96 | + stdout=subprocess.PIPE, |
| 97 | + stderr=subprocess.PIPE |
| 98 | + ) |
| 99 | + # Make signals errors with exit status 2; 0 and 1 are "non-error" statuses |
| 100 | + if contents.returncode not in (0, 1): |
| 101 | + error_msg = ( |
| 102 | + f"Invoking '{' '.join(contents.args)}' exited with " |
| 103 | + f"{contents.returncode}: {contents.stderr}" |
| 104 | + ) |
| 105 | + raise RuntimeError(error_msg) |
236 | 106 |
|
| 107 | + settings = {} |
| 108 | + for line in contents.stdout.split('\n'): |
| 109 | + m = re.match(r'^([A-Z][A-Z0-9_]*) = (.*)$', line) |
| 110 | + if m: |
| 111 | + settings[m.group(1)] = m.group(2) |
237 | 112 |
|
238 |
| -def support_matrix_excluded_boards(): |
239 |
| - """ Compiles a list of available modules, and which board definitions |
240 |
| - do not include them. |
241 |
| - """ |
242 |
| - base = build_module_map() |
| 113 | + return settings |
243 | 114 |
|
244 |
| - return get_excluded_boards(base) |
| 115 | +def lookup_setting(settings, key, default=''): |
| 116 | + while True: |
| 117 | + value = settings.get(key, default) |
| 118 | + if not value.startswith('$'): |
| 119 | + break |
| 120 | + key = value[2:-1] |
| 121 | + return value |
245 | 122 |
|
246 | 123 | def support_matrix_by_board():
|
247 | 124 | """ Compiles a list of the available core modules available for each
|
248 | 125 | board.
|
249 | 126 | """
|
250 | 127 | base = build_module_map()
|
251 |
| - base_with_exclusions = get_excluded_boards(base) |
252 | 128 |
|
253 | 129 | boards = dict()
|
254 | 130 | for port in SUPPORTED_PORTS:
|
| 131 | + |
255 | 132 | port_dir = "ports/{}/boards".format(port)
|
256 | 133 | for entry in os.scandir(port_dir):
|
257 | 134 | if not entry.is_dir():
|
258 | 135 | continue
|
259 | 136 | board_modules = []
|
260 | 137 |
|
261 |
| - board_name = entry.name |
262 |
| - board_contents = "" |
| 138 | + settings = get_settings_from_makefile(f'ports/{port}', entry.name) |
| 139 | + |
263 | 140 | with open(os.path.join(entry.path, "mpconfigboard.h")) as get_name:
|
264 | 141 | board_contents = get_name.read()
|
265 | 142 | board_name_re = re.search("(?<=MICROPY_HW_BOARD_NAME)\s+(.+)",
|
266 | 143 | board_contents)
|
267 | 144 | if board_name_re:
|
268 | 145 | board_name = board_name_re.group(1).strip('"')
|
269 | 146 |
|
270 |
| - for module in base_with_exclusions.keys(): |
271 |
| - #print(module) |
272 |
| - board_has_module = True |
273 |
| - if base_with_exclusions[module]["excluded"]: |
274 |
| - for port in base_with_exclusions[module]["excluded"].values(): |
275 |
| - #print(port) |
276 |
| - if entry.name in port: |
277 |
| - board_has_module = False |
278 |
| - |
279 |
| - if board_has_module: |
280 |
| - board_modules.append(base_with_exclusions[module]["name"]) |
| 147 | + board_modules = [] |
| 148 | + for module in base: |
| 149 | + key = f'CIRCUITPY_{module.upper()}' |
| 150 | + if int(lookup_setting(settings, key, '0')): |
| 151 | + board_modules.append(base[module]['name']) |
281 | 152 | boards[board_name] = sorted(board_modules)
|
282 | 153 |
|
283 | 154 | #print(json.dumps(boards, indent=2))
|
284 | 155 | return boards
|
| 156 | + |
| 157 | +if __name__ == '__main__': |
| 158 | + print(json.dumps(support_matrix_by_board(), indent=2)) |
0 commit comments