Skip to content

Commit ec0b69e

Browse files
committed
py/mpconfig: Add make show-config to parse and record overall config.
1 parent afceb56 commit ec0b69e

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed

py/mkrules.mk

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ $(HEADER_BUILD)/compressed.collected: $(HEADER_BUILD)/compressed.split
123123
$(ECHO) "GEN $@"
124124
$(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py cat compress _ $(HEADER_BUILD)/compress $@
125125

126+
$(HEADER_BUILD)/mpconfig.cfg: $(SRC_QSTR) $(QSTR_GLOBAL_DEPENDENCIES) | $(QSTR_GLOBAL_REQUIREMENTS)
127+
$(ECHO) "GEN $@"
128+
$(Q)$(PYTHON) $(PY_SRC)/mpconfig.py pp $(CPP) output $(HEADER_BUILD)/mpconfig.cfg cflags $(QSTR_GEN_CFLAGS) sources $^
129+
130+
126131
# $(sort $(var)) removes duplicates
127132
#
128133
# The net effect of this, is it causes the objects to depend on the
@@ -221,4 +226,8 @@ print-def:
221226
@$(CC) -E -Wp,-dM __empty__.c
222227
@$(RM) -f __empty__.c
223228

229+
show-config: $(HEADER_BUILD)/mpconfig.cfg
230+
@echo "Build configuration recorded in: $(HEADER_BUILD)/mpconfig.cfg"
231+
.PHONY: show-config
232+
224233
-include $(OBJ:.o=.P)

py/mpconfig.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env python
2+
3+
# This tool is used to provide visibility into mpconfig
4+
# Currently it parses configured source files and uses the
5+
# pre-processor to parse and present all configured
6+
# MICROPY_* definitions
7+
8+
import ast
9+
import io
10+
import json
11+
import os
12+
import operator as op
13+
import re
14+
import subprocess
15+
import sys
16+
17+
18+
pattern = re.compile(r"(MICROPY_[A-Z_0-9]*)", flags=re.MULTILINE)
19+
20+
21+
class Args:
22+
pp = None
23+
output = None
24+
cflags = None
25+
sources = None
26+
27+
28+
args = Args()
29+
30+
31+
class ifdef:
32+
def __init__(self, val):
33+
self.s = str(val)
34+
35+
def __str__(self):
36+
return self.s
37+
38+
39+
DEFINED = ifdef(1)
40+
41+
UNDEFINED = ifdef(0)
42+
43+
44+
def find_mpconfig_defines(c_file):
45+
"""Find any MICROPY_* definitions in the provided c file.
46+
47+
:param str c_file: path to c file to check
48+
:return: List[(module_name, obj_module, enabled_define)]
49+
"""
50+
global pattern
51+
52+
with io.open(c_file, encoding="utf-8") as c_file_obj:
53+
return set(re.findall(pattern, c_file_obj.read()))
54+
55+
56+
def preprocess(def_c_file):
57+
return subprocess.check_output(args.pp + args.cflags + [def_c_file]).decode()
58+
59+
60+
def eval_expr(expr):
61+
"""
62+
Safely evaluate maths expressions in C define strings.
63+
"""
64+
expr = expr.replace("||", "|").replace("&&", "&")
65+
try:
66+
return eval_(ast.parse(expr, mode="eval").body)
67+
except (TypeError, SyntaxError):
68+
return expr
69+
70+
71+
def eval_(node):
72+
# supported operators
73+
operators = {
74+
ast.Add: op.add,
75+
ast.Sub: op.sub,
76+
ast.Mult: op.mul,
77+
ast.Div: op.truediv,
78+
ast.Pow: op.pow,
79+
ast.BitXor: op.xor,
80+
ast.USub: op.neg,
81+
ast.BitOr: op.or_,
82+
ast.GtE: op.ge,
83+
ast.Gt: op.gt,
84+
ast.LtE: op.le,
85+
ast.Lt: op.lt,
86+
ast.Eq: op.eq,
87+
ast.NotEq: op.ne,
88+
ast.LShift: op.lshift,
89+
ast.RShift: op.rshift,
90+
}
91+
92+
if isinstance(node, ast.Num): # <number>
93+
return node.n
94+
elif isinstance(node, ast.BinOp): # <left> <operator> <right>
95+
return operators[type(node.op)](eval_(node.left), eval_(node.right))
96+
elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
97+
return operators[type(node.op)](eval_(node.operand))
98+
elif isinstance(node, ast.Compare): # <operator> <operand> e.g., -1
99+
ret = True
100+
left = eval_(node.left)
101+
for opr, right in zip(node.ops, [eval_(c) for c in node.comparators]):
102+
ret &= operators[type(opr)](left, right)
103+
return 1 if ret else 0
104+
else:
105+
raise TypeError(node)
106+
107+
108+
def parse_values(mpconfig_in):
109+
mpconfig = {}
110+
111+
for key, value in mpconfig_in.items():
112+
if isinstance(value, str):
113+
if key == value:
114+
# Undefined
115+
mpconfig[key] = UNDEFINED
116+
continue
117+
118+
if value == "":
119+
# defined
120+
mpconfig[key] = DEFINED
121+
continue
122+
123+
def _fill_mpconfig(match):
124+
return str(mpconfig_in.get(match.group(0), 0))
125+
126+
value = re.sub(pattern, _fill_mpconfig, value)
127+
128+
value = eval_expr(value)
129+
130+
mpconfig[key] = value
131+
return mpconfig
132+
133+
134+
def main():
135+
args.command = sys.argv[1]
136+
137+
named_args = {
138+
s: []
139+
for s in [
140+
"pp",
141+
"output",
142+
"cflags",
143+
"sources",
144+
]
145+
}
146+
147+
for arg in sys.argv[1:]:
148+
if arg in named_args:
149+
current_tok = arg
150+
else:
151+
named_args[current_tok].append(arg)
152+
153+
if not named_args["pp"] or len(named_args["output"]) != 1:
154+
print("usage: %s %s ..." % (sys.argv[0], " ... ".join(named_args)))
155+
sys.exit(2)
156+
157+
for k, v in named_args.items():
158+
setattr(args, k, v)
159+
160+
defines = set()
161+
for c_file in args.sources:
162+
defines |= find_mpconfig_defines(c_file)
163+
164+
os.makedirs(os.path.dirname(args.output[0]), exist_ok=True)
165+
def_c_file = args.output[0] + ".c"
166+
167+
with open(def_c_file, "w") as cdef:
168+
cdef.write("#include <py/mpconfig.h>\n\n")
169+
cdef.write("#define STR(a) STR_(a)\n")
170+
cdef.write("#define STR_(a...) #a\n\n")
171+
cdef.write("___MPCONFIG_DEFINES___\n")
172+
cdef.write("{\n")
173+
cdef.write(",\n".join(('"%s": STR(%s)' % (d, d) for d in defines)))
174+
cdef.write("}\n")
175+
176+
preproc_out = preprocess(def_c_file)
177+
pp_json = preproc_out.split("___MPCONFIG_DEFINES___")[-1].strip()
178+
mpconfig_json = json.loads(pp_json)
179+
180+
mpconfig_json_eval = None
181+
182+
while True:
183+
# re-run eval on values to process any defines dependant on other defines
184+
mpconfig_json_eval = parse_values(mpconfig_json)
185+
if mpconfig_json_eval == mpconfig_json:
186+
break
187+
mpconfig_json = mpconfig_json_eval
188+
189+
with open(args.output[0], "w") as out_file:
190+
for key, value in sorted(mpconfig_json.items()):
191+
if value is UNDEFINED:
192+
out_file.write(f"# {key} is not defined\n")
193+
elif value is DEFINED:
194+
out_file.write(f'{key} = "" # is defined\n')
195+
elif isinstance(value, str):
196+
value = value.strip('"')
197+
out_file.write(f'{key} = "{value}"\n')
198+
else:
199+
out_file.write(f"{key} = {value}\n")
200+
201+
202+
if __name__ == "__main__":
203+
main()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy