vcs_info.py 9.3 KB

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