gobuild.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import base64
  2. import itertools
  3. from hashlib import md5
  4. import os
  5. import six
  6. from _common import rootrel_arc_src, tobuilddir
  7. import ymake
  8. runtime_cgo_path = os.path.join('runtime', 'cgo')
  9. runtime_msan_path = os.path.join('runtime', 'msan')
  10. runtime_race_path = os.path.join('runtime', 'race')
  11. arc_project_prefix = 'a.yandex-team.ru/'
  12. import_runtime_cgo_false = {
  13. 'norace': (runtime_cgo_path, runtime_msan_path, runtime_race_path),
  14. 'race': (runtime_cgo_path, runtime_msan_path),
  15. }
  16. import_syscall_false = {
  17. 'norace': (runtime_cgo_path),
  18. 'race': (runtime_cgo_path, runtime_race_path),
  19. }
  20. def get_import_path(unit):
  21. # std_lib_prefix = unit.get('GO_STD_LIB_PREFIX')
  22. # unit.get() doesn't evalutate the value of variable, so the line above doesn't really work
  23. std_lib_prefix = unit.get('GOSTD') + '/'
  24. arc_project_prefix = unit.get('GO_ARCADIA_PROJECT_PREFIX')
  25. vendor_prefix = unit.get('GO_CONTRIB_PROJECT_PREFIX')
  26. module_path = rootrel_arc_src(unit.path(), unit)
  27. assert len(module_path) > 0
  28. if go_package_name(unit) == "main":
  29. return "main"
  30. import_path = module_path.replace('\\', '/')
  31. if import_path.startswith(std_lib_prefix):
  32. import_path = import_path[len(std_lib_prefix) :]
  33. elif import_path.startswith(vendor_prefix):
  34. import_path = import_path[len(vendor_prefix) :]
  35. else:
  36. import_path = arc_project_prefix + import_path
  37. assert len(import_path) > 0
  38. if import_path.endswith("/gotest"):
  39. return import_path[:-7]
  40. return import_path
  41. def get_appended_values(unit, key):
  42. values = []
  43. raw_value = unit.get(key)
  44. if raw_value:
  45. values = [x for x in raw_value.split(' ') if x]
  46. if len(values) > 0 and values[0] == '$' + key:
  47. values.pop(0)
  48. return values
  49. def compare_versions(version1, version2):
  50. def last_index(version):
  51. index = version.find('beta')
  52. return len(version) if index < 0 else index
  53. v1 = tuple(x.zfill(8) for x in version1[: last_index(version1)].split('.'))
  54. v2 = tuple(x.zfill(8) for x in version2[: last_index(version2)].split('.'))
  55. if v1 == v2:
  56. return 0
  57. return 1 if v1 < v2 else -1
  58. def go_package_name(unit):
  59. name = unit.get('_GO_PACKAGE_VALUE')
  60. if not name and unit.enabled('GO_TEST_MODULE'):
  61. name = unit.get('GO_PACKAGE_VALUE')
  62. if not name:
  63. name = unit.get('GO_TEST_IMPORT_PATH')
  64. if name:
  65. name = os.path.basename(os.path.normpath(name))
  66. elif unit.get('MODULE_TYPE') == 'PROGRAM':
  67. name = 'main'
  68. else:
  69. name = unit.get('REALPRJNAME')
  70. return name
  71. def need_lint(path):
  72. return not path.startswith('$S/vendor/') and not path.startswith('$S/contrib/')
  73. def on_go_process_srcs(unit):
  74. """
  75. _GO_PROCESS_SRCS() macro processes only 'CGO' files. All remaining *.go files
  76. and other input files are currently processed by a link command of the
  77. GO module (GO_LIBRARY, GO_PROGRAM)
  78. """
  79. srcs_files = get_appended_values(unit, '_GO_SRCS_VALUE')
  80. asm_files = []
  81. c_files = []
  82. cxx_files = []
  83. ev_files = []
  84. fbs_files = []
  85. go_files = []
  86. in_files = []
  87. proto_files = []
  88. s_files = []
  89. syso_files = []
  90. classified_files = {
  91. '.c': c_files,
  92. '.cc': cxx_files,
  93. '.cpp': cxx_files,
  94. '.cxx': cxx_files,
  95. '.ev': ev_files,
  96. '.fbs': fbs_files,
  97. '.go': go_files,
  98. '.in': in_files,
  99. '.proto': proto_files,
  100. '.s': asm_files,
  101. '.syso': syso_files,
  102. '.C': cxx_files,
  103. '.S': s_files,
  104. }
  105. # Classify files specifed in _GO_SRCS() macro by extension and process CGO_EXPORT keyword
  106. # which can preceed C/C++ files only
  107. is_cgo_export = False
  108. for f in srcs_files:
  109. _, ext = os.path.splitext(f)
  110. ext_files = classified_files.get(ext)
  111. if ext_files is not None:
  112. if is_cgo_export:
  113. is_cgo_export = False
  114. if ext in ('.c', '.cc', '.cpp', '.cxx', '.C'):
  115. unit.oncopy_file_with_context([f, f, 'OUTPUT_INCLUDES', '${BINDIR}/_cgo_export.h'])
  116. f = '${BINDIR}/' + f
  117. else:
  118. ymake.report_configure_error('Unmatched CGO_EXPORT keyword in SRCS() macro')
  119. ext_files.append(f)
  120. elif f == 'CGO_EXPORT':
  121. is_cgo_export = True
  122. else:
  123. # FIXME(snermolaev): We can report an unsupported files for _GO_SRCS here
  124. pass
  125. if is_cgo_export:
  126. ymake.report_configure_error('Unmatched CGO_EXPORT keyword in SRCS() macro')
  127. for f in go_files:
  128. if f.endswith('_test.go'):
  129. ymake.report_configure_error('file {} must be listed in GO_TEST_SRCS() or GO_XTEST_SRCS() macros'.format(f))
  130. go_test_files = get_appended_values(unit, '_GO_TEST_SRCS_VALUE')
  131. go_xtest_files = get_appended_values(unit, '_GO_XTEST_SRCS_VALUE')
  132. for f in go_test_files + go_xtest_files:
  133. if not f.endswith('_test.go'):
  134. ymake.report_configure_error(
  135. 'file {} should not be listed in GO_TEST_SRCS() or GO_XTEST_SRCS() macros'.format(f)
  136. )
  137. is_test_module = unit.enabled('GO_TEST_MODULE')
  138. unit_path = unit.path()
  139. # Add gofmt style checks
  140. add_fmt = True
  141. if not unit.enabled('_GO_FMT_ADD_CHECK'):
  142. allow_skip_fmt = unit.get('_GO_FMT_ALLOW_SKIP')
  143. allow_skip_fmt_list = None
  144. if allow_skip_fmt:
  145. allow_skip_fmt_list = unit.get('_GO_FMT_ALLOW_SKIP').split(' ')
  146. if allow_skip_fmt_list:
  147. unit_rel_path = rootrel_arc_src(unit_path, unit)
  148. if unit_rel_path in allow_skip_fmt_list:
  149. add_fmt = False
  150. else:
  151. for item in allow_skip_fmt_list:
  152. if item:
  153. prefix = item if item[-1] == '/' else item + '/'
  154. if unit_rel_path.startswith(prefix):
  155. add_fmt = False
  156. break
  157. if add_fmt:
  158. ymake.report_configure_error('Disabling gofmt is prohibited, please contact devtools')
  159. if add_fmt:
  160. resolved_go_files = []
  161. go_source_files = [] if is_test_module and unit.get(['GO_TEST_FOR_DIR']) else go_files
  162. for path in itertools.chain(go_source_files, go_test_files, go_xtest_files):
  163. if path.endswith('.go'):
  164. resolved = unit.resolve_arc_path([path])
  165. if resolved != path and need_lint(resolved):
  166. resolved_go_files.append(resolved)
  167. if resolved_go_files:
  168. basedirs = {}
  169. for f in resolved_go_files:
  170. basedir = os.path.dirname(f)
  171. if basedir not in basedirs:
  172. basedirs[basedir] = []
  173. basedirs[basedir].append(f)
  174. for basedir in basedirs:
  175. unit.onadd_check(['gofmt'] + basedirs[basedir])
  176. # Go coverage instrumentation (NOTE! go_files list is modified here)
  177. if is_test_module and unit.enabled('GO_TEST_COVER'):
  178. cover_info = []
  179. for f in go_files:
  180. if f.endswith('_test.go'):
  181. continue
  182. cover_var = 'GoCover' + six.ensure_str(base64.b32encode(six.ensure_binary(f))).rstrip('=')
  183. cover_file = unit.resolve_arc_path(f)
  184. cover_file_output = '{}/{}'.format(unit_path, os.path.basename(f))
  185. unit.on_go_gen_cover_go([cover_file, cover_file_output, cover_var])
  186. if cover_file.startswith('$S/'):
  187. cover_file = arc_project_prefix + cover_file[3:]
  188. cover_info.append('{}:{}'.format(cover_var, cover_file))
  189. # go_files should be empty now since the initial list shouldn't contain
  190. # any non-go or go test file. The value of go_files list will be used later
  191. # to update the value of _GO_SRCS_VALUE
  192. go_files = []
  193. unit.set(['GO_COVER_INFO_VALUE', ' '.join(cover_info)])
  194. # We have cleaned up the list of files from _GO_SRCS_VALUE var and we have to update
  195. # the value since it is used in module command line
  196. unit.set(['_GO_SRCS_VALUE', ' '.join(itertools.chain(go_files, asm_files, syso_files))])
  197. # Add go vet check
  198. if unit.enabled('_GO_VET_ADD_CHECK') and need_lint(unit_path):
  199. vet_report_file_name = os.path.join(unit_path, '{}{}'.format(unit.filename(), unit.get('GO_VET_REPORT_EXT')))
  200. unit.onadd_check(["govet", '$(BUILD_ROOT)/' + tobuilddir(vet_report_file_name)[3:]])
  201. for f in ev_files:
  202. ev_proto_file = '{}.proto'.format(f)
  203. unit.oncopy_file_with_context([f, ev_proto_file])
  204. proto_files.append(ev_proto_file)
  205. # Process .proto files
  206. for f in proto_files:
  207. unit.on_go_proto_cmd(f)
  208. # Process .fbs files
  209. for f in fbs_files:
  210. unit.on_go_flatc_cmd([f, go_package_name(unit)])
  211. # Process .in files
  212. for f in in_files:
  213. unit.onsrc(f)
  214. # Generate .symabis for .s files (starting from 1.12 version)
  215. if len(asm_files) > 0:
  216. symabis_flags = []
  217. gostd_version = unit.get('GOSTD_VERSION')
  218. if compare_versions('1.16', gostd_version) >= 0:
  219. import_path = get_import_path(unit)
  220. symabis_flags.extend(['FLAGS', '-p', import_path])
  221. unit.on_go_compile_symabis(asm_files + symabis_flags)
  222. # Process cgo files
  223. cgo_files = get_appended_values(unit, '_CGO_SRCS_VALUE')
  224. cgo_cflags = []
  225. if len(c_files) + len(cxx_files) + len(s_files) + len(cgo_files) > 0:
  226. if is_test_module:
  227. go_test_for_dir = unit.get('GO_TEST_FOR_DIR')
  228. if go_test_for_dir and go_test_for_dir.startswith('$S/'):
  229. unit.onaddincl(['FOR', 'c', go_test_for_dir[3:]])
  230. unit.onaddincl(['FOR', 'c', unit.get('MODDIR')])
  231. cgo_cflags = get_appended_values(unit, 'CGO_CFLAGS_VALUE')
  232. for f in itertools.chain(c_files, cxx_files, s_files):
  233. unit.onsrc([f] + cgo_cflags)
  234. if len(cgo_files) > 0:
  235. if not unit.enabled('CGO_ENABLED'):
  236. ymake.report_configure_error('trying to build with CGO (CGO_SRCS is non-empty) when CGO is disabled')
  237. import_path = get_import_path(unit)
  238. if import_path != runtime_cgo_path:
  239. go_std_root = unit.get('GOSTD')
  240. unit.onpeerdir(os.path.join(go_std_root, runtime_cgo_path))
  241. race_mode = 'race' if unit.enabled('RACE') else 'norace'
  242. import_runtime_cgo = 'false' if import_path in import_runtime_cgo_false[race_mode] else 'true'
  243. import_syscall = 'false' if import_path in import_syscall_false[race_mode] else 'true'
  244. args = (
  245. [import_path]
  246. + cgo_files
  247. + ['FLAGS', '-import_runtime_cgo=' + import_runtime_cgo, '-import_syscall=' + import_syscall]
  248. )
  249. unit.on_go_compile_cgo1(args)
  250. cgo2_cflags = get_appended_values(unit, 'CGO2_CFLAGS_VALUE')
  251. for f in cgo_files:
  252. if f.endswith('.go'):
  253. unit.onsrc([f[:-2] + 'cgo2.c'] + cgo_cflags + cgo2_cflags)
  254. else:
  255. ymake.report_configure_error('file {} should not be listed in CGO_SRCS() macros'.format(f))
  256. args = [go_package_name(unit)] + cgo_files
  257. if len(c_files) > 0:
  258. args += ['C_FILES'] + c_files
  259. if len(s_files) > 0:
  260. args += ['S_FILES'] + s_files
  261. if len(syso_files) > 0:
  262. args += ['OBJ_FILES'] + syso_files
  263. unit.on_go_compile_cgo2(args)
  264. def on_go_resource(unit, *args):
  265. args = list(args)
  266. files = args[::2]
  267. keys = args[1::2]
  268. suffix_md5 = md5(six.ensure_binary('@'.join(args))).hexdigest()
  269. resource_go = os.path.join("resource.{}.res.go".format(suffix_md5))
  270. unit.onpeerdir(["library/go/core/resource"])
  271. if len(files) != len(keys):
  272. ymake.report_configure_error("last file {} is missing resource key".format(files[-1]))
  273. for i, (key, filename) in enumerate(zip(keys, files)):
  274. if not key:
  275. ymake.report_configure_error("file key must be non empty")
  276. return
  277. if filename == "-" and "=" not in key:
  278. ymake.report_configure_error("key \"{}\" must contain = sign".format(key))
  279. return
  280. # quote key, to avoid automatic substitution of filename by absolute
  281. # path in RUN_PROGRAM
  282. args[2 * i + 1] = "notafile" + args[2 * i + 1]
  283. files = [file for file in files if file != "-"]
  284. unit.onrun_program(
  285. ["library/go/core/resource/cc", "-package", go_package_name(unit), "-o", resource_go]
  286. + list(args)
  287. + ["IN"]
  288. + files
  289. + ["OUT", resource_go]
  290. )