When SCR_QSTR contains C++ files they should be preprocessed with the same compiler flags (CXXFLAGS) as they will be compiled with, to make sure code scanned for QSTR occurrences is effectively the code used in the rest of the build. The 'split SCR_QSTR in .c and .cpp files and process each with different flags' logic isn't trivial to express in a Makefile and the existing principle for deciding which files to preprocess was already rather complicated, so the actual preprocessing is moved into makeqstrdefs.py completely.
194 lines
5.4 KiB
Python
194 lines
5.4 KiB
Python
"""
|
|
This script processes the output from the C preprocessor and extracts all
|
|
qstr. Each qstr is transformed into a qstr definition of the form 'Q(...)'.
|
|
|
|
This script works with Python 2.6, 2.7, 3.3 and 3.4.
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import io
|
|
import os
|
|
|
|
|
|
# Extract MP_QSTR_FOO macros.
|
|
_MODE_QSTR = "qstr"
|
|
|
|
# Extract MP_COMPRESSED_ROM_TEXT("") macros. (Which come from MP_ERROR_TEXT)
|
|
_MODE_COMPRESS = "compress"
|
|
|
|
|
|
def preprocess():
|
|
if any(src in args.dependencies for src in args.changed_sources):
|
|
sources = args.sources
|
|
elif any(args.changed_sources):
|
|
sources = args.changed_sources
|
|
else:
|
|
sources = args.sources
|
|
csources = []
|
|
cxxsources = []
|
|
for source in sources:
|
|
if source.endswith(".cpp"):
|
|
cxxsources.append(source)
|
|
else:
|
|
csources.append(source)
|
|
try:
|
|
os.makedirs(os.path.dirname(args.output[0]))
|
|
except OSError:
|
|
pass
|
|
with open(args.output[0], "w") as out_file:
|
|
if csources:
|
|
subprocess.check_call(args.pp + args.cflags + csources, stdout=out_file)
|
|
if cxxsources:
|
|
subprocess.check_call(args.pp + args.cxxflags + cxxsources, stdout=out_file)
|
|
|
|
|
|
def write_out(fname, output):
|
|
if output:
|
|
for m, r in [("/", "__"), ("\\", "__"), (":", "@"), ("..", "@@")]:
|
|
fname = fname.replace(m, r)
|
|
with open(args.output_dir + "/" + fname + "." + args.mode, "w") as f:
|
|
f.write("\n".join(output) + "\n")
|
|
|
|
|
|
def process_file(f):
|
|
re_line = re.compile(r"#[line]*\s\d+\s\"([^\"]+)\"")
|
|
if args.mode == _MODE_QSTR:
|
|
re_match = re.compile(r"MP_QSTR_[_a-zA-Z0-9]+")
|
|
elif args.mode == _MODE_COMPRESS:
|
|
re_match = re.compile(r'MP_COMPRESSED_ROM_TEXT\("([^"]*)"\)')
|
|
output = []
|
|
last_fname = None
|
|
for line in f:
|
|
if line.isspace():
|
|
continue
|
|
# match gcc-like output (# n "file") and msvc-like output (#line n "file")
|
|
if line.startswith(("# ", "#line")):
|
|
m = re_line.match(line)
|
|
assert m is not None
|
|
fname = m.group(1)
|
|
if os.path.splitext(fname)[1] not in [".c", ".cpp"]:
|
|
continue
|
|
if fname != last_fname:
|
|
write_out(last_fname, output)
|
|
output = []
|
|
last_fname = fname
|
|
continue
|
|
for match in re_match.findall(line):
|
|
if args.mode == _MODE_QSTR:
|
|
name = match.replace("MP_QSTR_", "")
|
|
output.append("Q(" + name + ")")
|
|
elif args.mode == _MODE_COMPRESS:
|
|
output.append(match)
|
|
|
|
if last_fname:
|
|
write_out(last_fname, output)
|
|
return ""
|
|
|
|
|
|
def cat_together():
|
|
import glob
|
|
import hashlib
|
|
|
|
hasher = hashlib.md5()
|
|
all_lines = []
|
|
outf = open(args.output_dir + "/out", "wb")
|
|
for fname in glob.glob(args.output_dir + "/*." + args.mode):
|
|
with open(fname, "rb") as f:
|
|
lines = f.readlines()
|
|
all_lines += lines
|
|
all_lines.sort()
|
|
all_lines = b"\n".join(all_lines)
|
|
outf.write(all_lines)
|
|
outf.close()
|
|
hasher.update(all_lines)
|
|
new_hash = hasher.hexdigest()
|
|
# print(new_hash)
|
|
old_hash = None
|
|
try:
|
|
with open(args.output_file + ".hash") as f:
|
|
old_hash = f.read()
|
|
except IOError:
|
|
pass
|
|
mode_full = "QSTR"
|
|
if args.mode == _MODE_COMPRESS:
|
|
mode_full = "Compressed data"
|
|
if old_hash != new_hash:
|
|
print(mode_full, "updated")
|
|
try:
|
|
# rename below might fail if file exists
|
|
os.remove(args.output_file)
|
|
except:
|
|
pass
|
|
os.rename(args.output_dir + "/out", args.output_file)
|
|
with open(args.output_file + ".hash", "w") as f:
|
|
f.write(new_hash)
|
|
else:
|
|
print(mode_full, "not updated")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 6:
|
|
print("usage: %s command mode input_filename output_dir output_file" % sys.argv[0])
|
|
sys.exit(2)
|
|
|
|
class Args:
|
|
pass
|
|
|
|
args = Args()
|
|
args.command = sys.argv[1]
|
|
|
|
if args.command == "pp":
|
|
named_args = {
|
|
s: []
|
|
for s in [
|
|
"pp",
|
|
"output",
|
|
"cflags",
|
|
"cxxflags",
|
|
"sources",
|
|
"changed_sources",
|
|
"dependencies",
|
|
]
|
|
}
|
|
|
|
for arg in sys.argv[1:]:
|
|
if arg in named_args:
|
|
current_tok = arg
|
|
else:
|
|
named_args[current_tok].append(arg)
|
|
|
|
if not named_args["pp"] or len(named_args["output"]) != 1:
|
|
print("usage: %s %s ..." % (sys.argv[0], " ... ".join(named_args)))
|
|
sys.exit(2)
|
|
|
|
for k, v in named_args.items():
|
|
setattr(args, k, v)
|
|
|
|
preprocess()
|
|
sys.exit(0)
|
|
|
|
args.mode = sys.argv[2]
|
|
args.input_filename = sys.argv[3] # Unused for command=cat
|
|
args.output_dir = sys.argv[4]
|
|
args.output_file = None if len(sys.argv) == 5 else sys.argv[5] # Unused for command=split
|
|
|
|
if args.mode not in (_MODE_QSTR, _MODE_COMPRESS):
|
|
print("error: mode %s unrecognised" % sys.argv[2])
|
|
sys.exit(2)
|
|
|
|
try:
|
|
os.makedirs(args.output_dir)
|
|
except OSError:
|
|
pass
|
|
|
|
if args.command == "split":
|
|
with io.open(args.input_filename, encoding="utf-8") as infile:
|
|
process_file(infile)
|
|
|
|
if args.command == "cat":
|
|
cat_together()
|