makeqstrdefs.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. """
  2. This script processes the output from the C preprocessor and extracts all
  3. qstr. Each qstr is transformed into a qstr definition of the form 'Q(...)'.
  4. This script works with Python 2.6, 2.7, 3.3 and 3.4.
  5. """
  6. from __future__ import print_function
  7. import re
  8. import subprocess
  9. import sys
  10. import io
  11. import os
  12. # Extract MP_QSTR_FOO macros.
  13. _MODE_QSTR = "qstr"
  14. # Extract MP_COMPRESSED_ROM_TEXT("") macros. (Which come from MP_ERROR_TEXT)
  15. _MODE_COMPRESS = "compress"
  16. def preprocess():
  17. if any(src in args.dependencies for src in args.changed_sources):
  18. sources = args.sources
  19. elif any(args.changed_sources):
  20. sources = args.changed_sources
  21. else:
  22. sources = args.sources
  23. csources = []
  24. cxxsources = []
  25. for source in sources:
  26. if source.endswith(".cpp"):
  27. cxxsources.append(source)
  28. else:
  29. csources.append(source)
  30. try:
  31. os.makedirs(os.path.dirname(args.output[0]))
  32. except OSError:
  33. pass
  34. with open(args.output[0], "w") as out_file:
  35. if csources:
  36. subprocess.check_call(args.pp + args.cflags + csources, stdout=out_file)
  37. if cxxsources:
  38. subprocess.check_call(args.pp + args.cxxflags + cxxsources, stdout=out_file)
  39. def write_out(fname, output):
  40. if output:
  41. for m, r in [("/", "__"), ("\\", "__"), (":", "@"), ("..", "@@")]:
  42. fname = fname.replace(m, r)
  43. with open(args.output_dir + "/" + fname + "." + args.mode, "w") as f:
  44. f.write("\n".join(output) + "\n")
  45. def process_file(f):
  46. re_line = re.compile(r"#[line]*\s\d+\s\"([^\"]+)\"")
  47. if args.mode == _MODE_QSTR:
  48. re_match = re.compile(r"MP_QSTR_[_a-zA-Z0-9]+")
  49. elif args.mode == _MODE_COMPRESS:
  50. re_match = re.compile(r'MP_COMPRESSED_ROM_TEXT\("([^"]*)"\)')
  51. output = []
  52. last_fname = None
  53. for line in f:
  54. if line.isspace():
  55. continue
  56. # match gcc-like output (# n "file") and msvc-like output (#line n "file")
  57. if line.startswith(("# ", "#line")):
  58. m = re_line.match(line)
  59. assert m is not None
  60. fname = m.group(1)
  61. if os.path.splitext(fname)[1] not in [".c", ".cpp"]:
  62. continue
  63. if fname != last_fname:
  64. write_out(last_fname, output)
  65. output = []
  66. last_fname = fname
  67. continue
  68. for match in re_match.findall(line):
  69. if args.mode == _MODE_QSTR:
  70. name = match.replace("MP_QSTR_", "")
  71. output.append("Q(" + name + ")")
  72. elif args.mode == _MODE_COMPRESS:
  73. output.append(match)
  74. if last_fname:
  75. write_out(last_fname, output)
  76. return ""
  77. def cat_together():
  78. import glob
  79. import hashlib
  80. hasher = hashlib.md5()
  81. all_lines = []
  82. outf = open(args.output_dir + "/out", "wb")
  83. for fname in glob.glob(args.output_dir + "/*." + args.mode):
  84. with open(fname, "rb") as f:
  85. lines = f.readlines()
  86. all_lines += lines
  87. all_lines.sort()
  88. all_lines = b"\n".join(all_lines)
  89. outf.write(all_lines)
  90. outf.close()
  91. hasher.update(all_lines)
  92. new_hash = hasher.hexdigest()
  93. # print(new_hash)
  94. old_hash = None
  95. try:
  96. with open(args.output_file + ".hash") as f:
  97. old_hash = f.read()
  98. except IOError:
  99. pass
  100. mode_full = "QSTR"
  101. if args.mode == _MODE_COMPRESS:
  102. mode_full = "Compressed data"
  103. if old_hash != new_hash:
  104. print(mode_full, "updated")
  105. try:
  106. # rename below might fail if file exists
  107. os.remove(args.output_file)
  108. except:
  109. pass
  110. os.rename(args.output_dir + "/out", args.output_file)
  111. with open(args.output_file + ".hash", "w") as f:
  112. f.write(new_hash)
  113. else:
  114. print(mode_full, "not updated")
  115. if __name__ == "__main__":
  116. if len(sys.argv) < 6:
  117. print("usage: %s command mode input_filename output_dir output_file" % sys.argv[0])
  118. sys.exit(2)
  119. class Args:
  120. pass
  121. args = Args()
  122. args.command = sys.argv[1]
  123. if args.command == "pp":
  124. named_args = {
  125. s: []
  126. for s in [
  127. "pp",
  128. "output",
  129. "cflags",
  130. "cxxflags",
  131. "sources",
  132. "changed_sources",
  133. "dependencies",
  134. ]
  135. }
  136. for arg in sys.argv[1:]:
  137. if arg in named_args:
  138. current_tok = arg
  139. else:
  140. named_args[current_tok].append(arg)
  141. if not named_args["pp"] or len(named_args["output"]) != 1:
  142. print("usage: %s %s ..." % (sys.argv[0], " ... ".join(named_args)))
  143. sys.exit(2)
  144. for k, v in named_args.items():
  145. setattr(args, k, v)
  146. preprocess()
  147. sys.exit(0)
  148. args.mode = sys.argv[2]
  149. args.input_filename = sys.argv[3] # Unused for command=cat
  150. args.output_dir = sys.argv[4]
  151. args.output_file = None if len(sys.argv) == 5 else sys.argv[5] # Unused for command=split
  152. if args.mode not in (_MODE_QSTR, _MODE_COMPRESS):
  153. print("error: mode %s unrecognised" % sys.argv[2])
  154. sys.exit(2)
  155. try:
  156. os.makedirs(args.output_dir)
  157. except OSError:
  158. pass
  159. if args.command == "split":
  160. with io.open(args.input_filename, encoding="utf-8") as infile:
  161. process_file(infile)
  162. if args.command == "cat":
  163. cat_together()