vcs_info.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import base64
  2. import json
  3. import os
  4. import re
  5. import sys
  6. import shutil
  7. import tempfile
  8. import textwrap
  9. import zipfile
  10. class _Formatting(object):
  11. @staticmethod
  12. def is_str(strval):
  13. return isinstance(strval, (bytes, str))
  14. @staticmethod
  15. def encoding_needed(strval):
  16. return isinstance(strval, str)
  17. @staticmethod
  18. def escape_special_symbols(strval):
  19. encoding_needed = _Formatting.encoding_needed(strval)
  20. c_str = strval.encode('utf-8') if encoding_needed else strval
  21. retval = b""
  22. for c in c_str:
  23. c = bytes([c])
  24. if c in (b"\\", b"\""):
  25. retval += b"\\" + c
  26. elif ord(c) < ord(' '):
  27. retval += c.decode('latin-1').encode('unicode_escape')
  28. else:
  29. retval += c
  30. return retval.decode('utf-8') if encoding_needed else retval
  31. @staticmethod
  32. def escape_line_feed(strval, indent=' '):
  33. return strval.replace(r'\n', '\\n"\\\n' + indent + '"')
  34. @staticmethod
  35. def escape_trigraphs(strval):
  36. return strval.replace(r'?', '\\?')
  37. @staticmethod
  38. def escaped_define(strkey, val):
  39. name = "#define " + strkey + " "
  40. if _Formatting.is_str(val):
  41. define = (
  42. "\""
  43. + _Formatting.escape_line_feed(_Formatting.escape_trigraphs(_Formatting.escape_special_symbols(val)))
  44. + "\""
  45. )
  46. else:
  47. define = str(val)
  48. return name + define
  49. @staticmethod
  50. def escaped_go_map_key(strkey, strval):
  51. if _Formatting.is_str(strval):
  52. return ' ' + '"' + strkey + '": "' + _Formatting.escape_special_symbols(strval) + '",'
  53. else:
  54. return ' ' + '"' + strkey + '": "' + str(strval) + '",'
  55. def get_default_json():
  56. return json.loads(
  57. '''{
  58. "ARCADIA_SOURCE_HG_HASH": "0000000000000000000000000000000000000000",
  59. "ARCADIA_SOURCE_LAST_AUTHOR": "<UNKNOWN>",
  60. "ARCADIA_SOURCE_LAST_CHANGE": -1,
  61. "ARCADIA_SOURCE_PATH": "/",
  62. "ARCADIA_SOURCE_REVISION": -1,
  63. "ARCADIA_SOURCE_URL": "",
  64. "BRANCH": "unknown-vcs-branch",
  65. "BUILD_DATE": "",
  66. "BUILD_TIMESTAMP": 0,
  67. "BUILD_HOST": "localhost",
  68. "BUILD_USER": "nobody",
  69. "CUSTOM_VERSION": "",
  70. "PROGRAM_VERSION": "Arc info:\\n Branch: unknown-vcs-branch\\n Commit: 0000000000000000000000000000000000000000\\n Author: <UNKNOWN>\\n Summary: No VCS\\n\\n",
  71. "SCM_DATA": "Arc info:\\n Branch: unknown-vcs-branch\\n Commit: 0000000000000000000000000000000000000000\\n Author: <UNKNOWN>\\n Summary: No VCS\\n",
  72. "VCS": "arc",
  73. "ARCADIA_PATCH_NUMBER": 0,
  74. "ARCADIA_TAG": ""
  75. }'''
  76. )
  77. def get_json(file_name):
  78. try:
  79. with open(file_name, 'rt', encoding="utf-8") as f:
  80. out = json.load(f)
  81. # TODO: check 'tar+svn' parsing
  82. for num_var in ['ARCADIA_SOURCE_REVISION', 'ARCADIA_SOURCE_LAST_CHANGE', 'SVN_REVISION']:
  83. if num_var in out and _Formatting.is_str(out[num_var]):
  84. try:
  85. out[num_var] = int(out[num_var])
  86. except:
  87. out[num_var] = -1
  88. return out
  89. except:
  90. return get_default_json()
  91. def print_c(json_file, output_file, argv):
  92. """params:
  93. json file
  94. output file
  95. $(SOURCE_ROOT)/build/scripts/c_templates/svn_interface.c"""
  96. interface = argv[0]
  97. with open(interface, 'rt', encoding="utf-8") as c:
  98. c_file = c.read()
  99. with open(output_file, 'wt', encoding="utf-8") as f:
  100. header = '\n'.join(_Formatting.escaped_define(k, v) for k, v in json_file.items())
  101. f.write(header + '\n' + c_file)
  102. def merge_java_content(old_content, json_file):
  103. new_content, names = print_java_mf(json_file)
  104. def split_to_sections(content):
  105. sections = []
  106. cur_section = []
  107. for l in content:
  108. if l.rstrip():
  109. cur_section.append(l)
  110. else:
  111. sections.append(cur_section)
  112. cur_section = []
  113. if cur_section: # should not be needed according to format specification
  114. sections.append(cur_section)
  115. return sections
  116. def drop_duplicate_entries(main_section, names):
  117. header = re.compile('^([A-Za-z0-9][A-Za-z0-9_-]*): .*$')
  118. new_main_section = []
  119. for l in main_section:
  120. match = header.match(l)
  121. # duplicate entry
  122. if match:
  123. skip = match.group(1) in names
  124. if not skip:
  125. new_main_section.append(l)
  126. return new_main_section
  127. if old_content:
  128. sections = split_to_sections(old_content)
  129. sections[0] = drop_duplicate_entries(sections[0], names)
  130. else:
  131. sections = [['Manifest-Version: 1.0\n']]
  132. sections[0].extend(map(lambda x: x + '\n', new_content))
  133. return ''.join(map(lambda x: ''.join(x), sections)) + '\n'
  134. def merge_java_mf_jar(json_file, out_manifest, jar_file):
  135. try:
  136. temp_dir = tempfile.mkdtemp()
  137. try:
  138. with zipfile.ZipFile(jar_file, 'r') as jar:
  139. jar.extract(os.path.join('META-INF', 'MANIFEST.MF'), path=temp_dir)
  140. except KeyError:
  141. pass
  142. merge_java_mf_dir(json_file, out_manifest, temp_dir)
  143. finally:
  144. shutil.rmtree(temp_dir)
  145. def merge_java_mf_dir(json_file, out_manifest, input_dir):
  146. manifest = os.path.join(input_dir, 'META-INF', 'MANIFEST.MF')
  147. old_lines = []
  148. if os.path.isfile(manifest):
  149. with open(manifest, 'rt', encoding="utf-8") as f:
  150. old_lines = f.readlines()
  151. with open(out_manifest, 'wt', encoding="utf-8") as f:
  152. f.write(merge_java_content(old_lines, json_file))
  153. def merge_java_mf(json_file, out_manifest, input):
  154. if zipfile.is_zipfile(input):
  155. merge_java_mf_jar(json_file, out_manifest, input)
  156. elif os.path.isdir(input):
  157. merge_java_mf_dir(json_file, out_manifest, input)
  158. def print_java_mf(info):
  159. wrapper = textwrap.TextWrapper(
  160. subsequent_indent=' ', break_long_words=True, replace_whitespace=False, drop_whitespace=False
  161. )
  162. names = set()
  163. def wrap(key, val):
  164. names.add(key[:-2])
  165. if not val:
  166. return []
  167. return wrapper.wrap(key + val)
  168. lines = wrap('Program-Version-String: ', base64.b64encode(info['PROGRAM_VERSION'].encode('utf-8')).decode('utf-8'))
  169. lines += wrap('SCM-String: ', base64.b64encode(info['SCM_DATA'].encode('utf-8')).decode('utf-8'))
  170. lines += wrap('Arcadia-Source-Path: ', info['ARCADIA_SOURCE_PATH'])
  171. lines += wrap('Arcadia-Source-URL: ', info['ARCADIA_SOURCE_URL'])
  172. lines += wrap('Arcadia-Source-Revision: ', str(info['ARCADIA_SOURCE_REVISION']))
  173. lines += wrap('Arcadia-Source-Hash: ', info['ARCADIA_SOURCE_HG_HASH'].rstrip())
  174. lines += wrap('Arcadia-Source-Last-Change: ', str(info['ARCADIA_SOURCE_LAST_CHANGE']))
  175. lines += wrap('Arcadia-Source-Last-Author: ', info['ARCADIA_SOURCE_LAST_AUTHOR'])
  176. lines += wrap('Build-User: ', info['BUILD_USER'])
  177. lines += wrap('Build-Host: ', info['BUILD_HOST'])
  178. lines += wrap('Version-Control-System: ', info['VCS'])
  179. lines += wrap('Branch: ', info['BRANCH'])
  180. lines += wrap('Arcadia-Tag: ', info.get('ARCADIA_TAG', ''))
  181. lines += wrap('Arcadia-Patch-Number: ', str(info.get('ARCADIA_PATCH_NUMBER', 42)))
  182. if 'SVN_REVISION' in info:
  183. lines += wrap('SVN-Revision: ', str(info['SVN_REVISION']))
  184. lines += wrap('SVN-Arcroot: ', info['SVN_ARCROOT'])
  185. lines += wrap('SVN-Time: ', info['SVN_TIME'])
  186. lines += wrap('Build-Date: ', info['BUILD_DATE'])
  187. if 'BUILD_TIMESTAMP' in info:
  188. lines += wrap('Build-Timestamp: ', str(info['BUILD_TIMESTAMP']))
  189. if 'CUSTOM_VERSION' in info:
  190. lines += wrap(
  191. 'Custom-Version-String: ', base64.b64encode(info['CUSTOM_VERSION'].encode('utf-8')).decode('utf-8')
  192. )
  193. return lines, names
  194. def print_java(json_file, output_file, argv):
  195. """params:
  196. json file
  197. output file
  198. file"""
  199. input = argv[0] if argv else os.curdir
  200. merge_java_mf(json_file, output_file, input)
  201. def print_go(json_file, output_file, arc_project_prefix):
  202. def gen_map(info):
  203. lines = []
  204. for k, v in info.items():
  205. lines.append(_Formatting.escaped_go_map_key(k, v))
  206. return lines
  207. with open(output_file, 'wt', encoding="utf-8") as f:
  208. f.write(
  209. '\n'.join(
  210. [
  211. '// Code generated by vcs_info.py; DO NOT EDIT.',
  212. '',
  213. 'package main',
  214. 'import "{}library/go/core/buildinfo"'.format(arc_project_prefix),
  215. 'func init() {',
  216. ' buildinfo.InitBuildInfo(map[string]string {',
  217. ]
  218. + gen_map(json_file)
  219. + ['})', '}']
  220. )
  221. + '\n'
  222. )
  223. def print_json(json_file, output_file):
  224. MANDATOTRY_FIELDS_MAP = {
  225. 'ARCADIA_TAG': 'Arcadia-Tag',
  226. 'ARCADIA_PATCH_NUMBER': 'Arcadia-Patch-Number',
  227. 'ARCADIA_SOURCE_URL': 'Arcadia-Source-URL',
  228. 'ARCADIA_SOURCE_REVISION': 'Arcadia-Source-Revision',
  229. 'ARCADIA_SOURCE_HG_HASH': 'Arcadia-Source-Hash',
  230. 'ARCADIA_SOURCE_LAST_CHANGE': 'Arcadia-Source-Last-Change',
  231. 'ARCADIA_SOURCE_LAST_AUTHOR': 'Arcadia-Source-Last-Author',
  232. 'BRANCH': 'Branch',
  233. 'BUILD_HOST': 'Build-Host',
  234. 'BUILD_USER': 'Build-User',
  235. 'PROGRAM_VERSION': 'Program-Version-String',
  236. 'SCM_DATA': 'SCM-String',
  237. 'VCS': 'Version-Control-System',
  238. }
  239. SVN_REVISION = 'SVN_REVISION'
  240. SVN_FIELDS_MAP = {
  241. SVN_REVISION: 'SVN-Revision',
  242. 'SVN_ARCROOT': 'SVN-Arcroot',
  243. 'SVN_TIME': 'SVN-Time',
  244. }
  245. OPTIONAL_FIELDS_MAP = {
  246. 'BUILD_TIMESTAMP': 'Build-Timestamp',
  247. 'CUSTOM_VERSION': 'Custom-Version-String',
  248. 'DIRTY': 'Working-Copy-State',
  249. }
  250. ext_json = {}
  251. for k in MANDATOTRY_FIELDS_MAP:
  252. ext_json[MANDATOTRY_FIELDS_MAP[k]] = json_file[k]
  253. if SVN_REVISION in json_file:
  254. for k in SVN_FIELDS_MAP:
  255. ext_json[SVN_FIELDS_MAP[k]] = json_file[k]
  256. for k in OPTIONAL_FIELDS_MAP:
  257. if k in json_file and json_file[k]:
  258. ext_json[OPTIONAL_FIELDS_MAP[k]] = json_file[k]
  259. with open(output_file, 'wt', encoding="utf-8") as f:
  260. json.dump(ext_json, f, sort_keys=True, indent=4)
  261. if __name__ == '__main__':
  262. if 'output-go' in sys.argv:
  263. lang = 'Go'
  264. sys.argv.remove('output-go')
  265. elif 'output-java' in sys.argv:
  266. lang = 'Java'
  267. sys.argv.remove('output-java')
  268. elif 'output-json' in sys.argv:
  269. lang = 'JSON'
  270. sys.argv.remove('output-json')
  271. else:
  272. lang = 'C'
  273. if 'no-vcs' in sys.argv:
  274. sys.argv.remove('no-vcs')
  275. json_file = get_default_json()
  276. else:
  277. json_name = sys.argv[1]
  278. json_file = get_json(json_name)
  279. if lang == 'Go':
  280. print_go(json_file, sys.argv[2], sys.argv[3])
  281. elif lang == 'Java':
  282. print_java(json_file, sys.argv[2], sys.argv[3:])
  283. elif lang == 'JSON':
  284. print_json(json_file, sys.argv[2])
  285. else:
  286. print_c(json_file, sys.argv[2], sys.argv[3:])