go_tool.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. import argparse
  2. import copy
  3. import json
  4. import os
  5. import re
  6. import shutil
  7. import subprocess
  8. import sys
  9. import tarfile
  10. import tempfile
  11. import threading
  12. import traceback
  13. from contextlib import contextmanager
  14. from functools import reduce
  15. # Explicitly enable local imports
  16. # Don't forget to add imported scripts to inputs of the calling command!
  17. sys.path.append(os.path.dirname(os.path.abspath(__file__)))
  18. import process_command_files as pcf
  19. import process_whole_archive_option as pwa
  20. arc_project_prefix = 'a.yandex-team.ru/'
  21. # FIXME: make version-independent
  22. std_lib_prefix = 'contrib/go/_std_1.22/src/'
  23. vendor_prefix = 'vendor/'
  24. vet_info_ext = '.vet.out'
  25. vet_report_ext = '.vet.txt'
  26. FIXED_CGO1_SUFFIX = '.fixed.cgo1.go'
  27. COMPILE_OPTIMIZATION_FLAGS = ('-N',)
  28. IGNORED_FLAGS = ['-fprofile-instr-generate', '-fcoverage-mapping']
  29. def get_trimpath_args(args):
  30. return ['-trimpath', args.trimpath] if args.trimpath else []
  31. def preprocess_cgo1(src_path, dst_path, source_root):
  32. with open(src_path, 'r') as f:
  33. content = f.read()
  34. content = content.replace('__ARCADIA_SOURCE_ROOT_PREFIX__', source_root)
  35. with open(dst_path, 'w') as f:
  36. f.write(content)
  37. def preprocess_args(args):
  38. # Temporary work around for noauto
  39. if args.cgo_srcs and len(args.cgo_srcs) > 0:
  40. cgo_srcs_set = set(args.cgo_srcs)
  41. args.srcs = [x for x in args.srcs if x not in cgo_srcs_set]
  42. args.pkg_root = os.path.join(args.toolchain_root, 'pkg')
  43. toolchain_tool_root = os.path.join(args.pkg_root, 'tool', '{}_{}'.format(args.host_os, args.host_arch))
  44. args.go_compile = os.path.join(toolchain_tool_root, 'compile')
  45. args.go_cgo = os.path.join(toolchain_tool_root, 'cgo')
  46. args.go_link = os.path.join(toolchain_tool_root, 'link')
  47. args.go_asm = os.path.join(toolchain_tool_root, 'asm')
  48. args.go_pack = os.path.join(toolchain_tool_root, 'pack')
  49. args.go_vet = os.path.join(toolchain_tool_root, 'vet') if args.vet is True else args.vet
  50. args.output = os.path.normpath(args.output)
  51. args.vet_report_output = vet_report_output_name(args.output, args.vet_report_ext)
  52. args.trimpath = None
  53. if args.debug_root_map:
  54. roots = {'build': args.build_root, 'source': args.source_root, 'tools': args.tools_root}
  55. replaces = []
  56. for root in args.debug_root_map.split(';'):
  57. src, dst = root.split('=', 1)
  58. assert src in roots
  59. replaces.append('{}=>{}'.format(roots[src], dst))
  60. del roots[src]
  61. assert len(replaces) > 0
  62. args.trimpath = ';'.join(replaces)
  63. args.build_root = os.path.normpath(args.build_root)
  64. args.build_root_dir = args.build_root + os.path.sep
  65. args.source_root = os.path.normpath(args.source_root)
  66. args.source_root_dir = args.source_root + os.path.sep
  67. args.output_root = os.path.normpath(args.output_root)
  68. args.import_map = {}
  69. args.module_map = {}
  70. def is_valid_cgo_peer(peer):
  71. if peer.endswith('.fake.pkg') or peer.endswith('.fake'):
  72. return False
  73. return True
  74. if args.cgo_peers:
  75. args.cgo_peers = list(filter(is_valid_cgo_peer, args.cgo_peers))
  76. srcs = []
  77. for f in args.srcs:
  78. if f.endswith('.gosrc'):
  79. with tarfile.open(f, 'r') as tar:
  80. srcs.extend(os.path.join(args.output_root, src) for src in tar.getnames())
  81. if sys.version_info >= (3, 12):
  82. tar.extractall(path=args.output_root, filter='data')
  83. else:
  84. tar.extractall(path=args.output_root)
  85. else:
  86. srcs.append(f)
  87. args.srcs = srcs
  88. assert args.mode == 'test' or args.test_srcs is None and args.xtest_srcs is None
  89. # add lexical oreder by basename for go sources
  90. args.srcs.sort(key=lambda x: os.path.basename(x))
  91. if args.test_srcs:
  92. args.srcs += sorted(args.test_srcs, key=lambda x: os.path.basename(x))
  93. del args.test_srcs
  94. if args.xtest_srcs:
  95. args.xtest_srcs.sort(key=lambda x: os.path.basename(x))
  96. # compute root relative module dir path
  97. assert args.output is None or args.output_root == os.path.dirname(args.output)
  98. assert args.output_root.startswith(args.build_root_dir)
  99. args.module_path = args.output_root[len(args.build_root_dir) :]
  100. args.source_module_dir = os.path.join(args.source_root, args.test_import_path or args.module_path) + os.path.sep
  101. assert len(args.module_path) > 0
  102. args.import_path, args.is_std = get_import_path(args.module_path)
  103. assert args.asmhdr is None or args.word == 'go'
  104. srcs = []
  105. for f in args.srcs:
  106. if f.endswith(FIXED_CGO1_SUFFIX) and f.startswith(args.build_root_dir):
  107. path = os.path.join(args.output_root, '{}.cgo1.go'.format(os.path.basename(f[: -len(FIXED_CGO1_SUFFIX)])))
  108. srcs.append(path)
  109. preprocess_cgo1(f, path, args.source_root)
  110. else:
  111. srcs.append(f)
  112. args.srcs = srcs
  113. if args.extldflags:
  114. tmp = [flag for flag in args.extldflags if flag not in IGNORED_FLAGS]
  115. args.extldflags = pwa.ProcessWholeArchiveOption(args.targ_os).construct_cmd(tmp)
  116. classify_srcs(args.srcs, args)
  117. def compare_versions(version1, version2):
  118. def last_index(version):
  119. index = version.find('beta')
  120. return len(version) if index < 0 else index
  121. v1 = tuple(x.zfill(8) for x in version1[: last_index(version1)].split('.'))
  122. v2 = tuple(x.zfill(8) for x in version2[: last_index(version2)].split('.'))
  123. if v1 == v2:
  124. return 0
  125. return 1 if v1 < v2 else -1
  126. def get_symlink_or_copyfile():
  127. os_symlink = getattr(os, 'symlink', None)
  128. if os_symlink is None or os.name == 'nt':
  129. os_symlink = shutil.copyfile
  130. return os_symlink
  131. def copy_args(args):
  132. return copy.copy(args)
  133. def get_vendor_index(import_path):
  134. index = import_path.rfind('/' + vendor_prefix)
  135. if index < 0:
  136. index = 0 if import_path.startswith(vendor_prefix) else index
  137. else:
  138. index = index + 1
  139. return index
  140. def get_import_path(module_path):
  141. assert len(module_path) > 0
  142. import_path = module_path.replace('\\', '/')
  143. is_std_module = import_path.startswith(std_lib_prefix)
  144. if is_std_module:
  145. import_path = import_path[len(std_lib_prefix) :]
  146. elif import_path.startswith(vendor_prefix):
  147. import_path = import_path[len(vendor_prefix) :]
  148. else:
  149. import_path = arc_project_prefix + import_path
  150. assert len(import_path) > 0
  151. return import_path, is_std_module
  152. def call(cmd, cwd, env=None):
  153. # sys.stderr.write('{}\n'.format(' '.join(cmd)))
  154. return subprocess.check_output(cmd, stdin=None, stderr=subprocess.STDOUT, cwd=cwd, env=env, text=True)
  155. def classify_srcs(srcs, args):
  156. args.go_srcs = [x for x in srcs if x.endswith('.go')]
  157. args.asm_srcs = [x for x in srcs if x.endswith('.s')]
  158. args.objects = [x for x in srcs if x.endswith('.o') or x.endswith('.obj')]
  159. args.symabis = [x for x in srcs if x.endswith('.symabis')]
  160. args.sysos = [x for x in srcs if x.endswith('.syso')]
  161. def get_import_config_info(peers, gen_importmap, import_map={}, module_map={}):
  162. info = {'importmap': [], 'packagefile': [], 'standard': {}}
  163. if gen_importmap:
  164. for key, value in import_map.items():
  165. info['importmap'].append((key, value))
  166. for peer in peers:
  167. peer_import_path, is_std = get_import_path(os.path.dirname(peer))
  168. if gen_importmap:
  169. index = get_vendor_index(peer_import_path)
  170. if index >= 0:
  171. index += len(vendor_prefix)
  172. info['importmap'].append((peer_import_path[index:], peer_import_path))
  173. info['packagefile'].append((peer_import_path, os.path.join(args.build_root, peer)))
  174. if is_std:
  175. info['standard'][peer_import_path] = True
  176. for key, value in module_map.items():
  177. info['packagefile'].append((key, value))
  178. return info
  179. def create_import_config(peers, gen_importmap, import_map={}, module_map={}):
  180. lines = []
  181. info = get_import_config_info(peers, gen_importmap, import_map, module_map)
  182. for key in ('importmap', 'packagefile'):
  183. for item in info[key]:
  184. lines.append('{} {}={}'.format(key, *item))
  185. if len(lines) > 0:
  186. lines.append('')
  187. content = '\n'.join(lines)
  188. # sys.stderr.writelines('{}\n'.format(l) for l in lines)
  189. with tempfile.NamedTemporaryFile(delete=False) as f:
  190. f.write(content.encode('UTF-8'))
  191. return f.name
  192. return None
  193. def create_embed_config(args):
  194. data = {
  195. 'Patterns': {},
  196. 'Files': {},
  197. }
  198. for info in args.embed:
  199. embed_dir = os.path.normpath(info[0])
  200. assert embed_dir == args.source_module_dir[:-1] or embed_dir.startswith(
  201. (args.source_module_dir, args.build_root)
  202. )
  203. pattern = info[1]
  204. if pattern.endswith('/**/*'):
  205. pattern = pattern[:-3]
  206. files = {os.path.relpath(f, embed_dir).replace('\\', '/'): f for f in info[2:]}
  207. data['Patterns'][pattern] = list(files.keys())
  208. data['Files'].update(files)
  209. # sys.stderr.write('{}\n'.format(json.dumps(data, indent=4)))
  210. with tempfile.NamedTemporaryFile(delete=False, suffix='.embedcfg') as f:
  211. f.write(json.dumps(data).encode('UTF-8'))
  212. return f.name
  213. def vet_info_output_name(path, ext=None):
  214. return '{}{}'.format(path, ext or vet_info_ext)
  215. def vet_report_output_name(path, ext=None):
  216. return '{}{}'.format(path, ext or vet_report_ext)
  217. def get_source_path(args):
  218. return args.test_import_path or args.module_path
  219. def gen_vet_info(args):
  220. import_path = args.real_import_path if hasattr(args, 'real_import_path') else args.import_path
  221. info = get_import_config_info(args.peers, True, args.import_map, args.module_map)
  222. import_map = dict(info['importmap'])
  223. # FIXME(snermolaev): it seems that adding import map for 'fake' package
  224. # does't make any harm (it needs to be revised later)
  225. import_map['unsafe'] = 'unsafe'
  226. for key, _ in info['packagefile']:
  227. if key not in import_map:
  228. import_map[key] = key
  229. data = {
  230. 'ID': import_path,
  231. 'Compiler': 'gc',
  232. 'Dir': os.path.join(args.source_root, get_source_path(args)),
  233. 'ImportPath': import_path,
  234. 'GoVersion': ('go%s' % args.goversion),
  235. 'GoFiles': [x for x in args.go_srcs if x.endswith('.go')],
  236. 'NonGoFiles': [x for x in args.go_srcs if not x.endswith('.go')],
  237. 'ImportMap': import_map,
  238. 'PackageFile': dict(info['packagefile']),
  239. 'Standard': dict(info['standard']),
  240. 'PackageVetx': dict((key, vet_info_output_name(value)) for key, value in info['packagefile']),
  241. 'VetxOnly': False,
  242. 'VetxOutput': vet_info_output_name(args.output),
  243. 'SucceedOnTypecheckFailure': False,
  244. }
  245. # sys.stderr.write('{}\n'.format(json.dumps(data, indent=4)))
  246. return data
  247. def create_vet_config(args, info):
  248. with tempfile.NamedTemporaryFile(delete=False, suffix='.cfg') as f:
  249. f.write(json.dumps(info).encode('UTF-8'))
  250. return f.name
  251. def decode_vet_report(json_report):
  252. report = ''
  253. if json_report:
  254. json_report = json_report.decode('UTF-8')
  255. try:
  256. full_diags = json.JSONDecoder().decode(json_report)
  257. except ValueError:
  258. report = json_report
  259. else:
  260. messages = []
  261. for _, module_diags in full_diags.items():
  262. for _, type_diags in module_diags.items():
  263. for diag in type_diags:
  264. messages.append('{}: {}'.format(diag['posn'], json.dumps(diag['message'])))
  265. report = '\n'.join(messages)
  266. return report
  267. def dump_vet_report(args, report):
  268. if report:
  269. report = report.replace(args.build_root, '$B')
  270. report = report.replace(args.source_root, '$S')
  271. with open(args.vet_report_output, 'w') as f:
  272. f.write(report)
  273. def read_vet_report(args):
  274. assert args
  275. report = ''
  276. if os.path.exists(args.vet_report_output):
  277. with open(args.vet_report_output, 'r') as f:
  278. report += f.read()
  279. return report
  280. def dump_vet_report_for_tests(args, *test_args_list):
  281. dump_vet_report(args, reduce(lambda x, y: x + read_vet_report(y), [_f for _f in test_args_list if _f], ''))
  282. def do_vet(args):
  283. assert args.vet
  284. info = gen_vet_info(args)
  285. vet_config = create_vet_config(args, info)
  286. cmd = [args.go_vet, '-json']
  287. if args.vet_flags:
  288. cmd.extend(args.vet_flags)
  289. cmd.append(vet_config)
  290. # sys.stderr.write('>>>> [{}]\n'.format(' '.join(cmd)))
  291. p_vet = subprocess.Popen(cmd, stdin=None, stderr=subprocess.PIPE, stdout=subprocess.PIPE, cwd=args.source_root)
  292. vet_out, vet_err = p_vet.communicate()
  293. report = decode_vet_report(vet_out) if vet_out else ''
  294. dump_vet_report(args, report)
  295. if p_vet.returncode:
  296. raise subprocess.CalledProcessError(returncode=p_vet.returncode, cmd=cmd, output=vet_err)
  297. def _do_compile_go(args):
  298. import_path, is_std_module = args.import_path, args.is_std
  299. cmd = [
  300. args.go_compile,
  301. '-o',
  302. args.output,
  303. '-p',
  304. import_path if import_path != "unsafe" else "",
  305. '-D',
  306. '""',
  307. ]
  308. if args.lang:
  309. cmd.append('-lang=go{}'.format(args.lang))
  310. cmd.extend(get_trimpath_args(args))
  311. compiling_runtime = False
  312. if is_std_module:
  313. cmd.append('-std')
  314. if import_path in ('runtime', 'internal/abi', 'internal/bytealg', 'internal/cpu') or import_path.startswith(
  315. 'runtime/internal/'
  316. ):
  317. cmd.append('-+')
  318. compiling_runtime = True
  319. import_config_name = create_import_config(args.peers, True, args.import_map, args.module_map)
  320. if import_config_name:
  321. cmd += ['-importcfg', import_config_name]
  322. else:
  323. if import_path == 'unsafe' or len(args.objects) > 0 or args.asmhdr:
  324. pass
  325. else:
  326. cmd.append('-complete')
  327. # if compare_versions('1.16', args.goversion) >= 0:
  328. if args.embed:
  329. embed_config_name = create_embed_config(args)
  330. cmd.extend(['-embedcfg', embed_config_name])
  331. if args.asmhdr:
  332. cmd += ['-asmhdr', args.asmhdr]
  333. # Use .symabis (starting from 1.12 version)
  334. if args.symabis:
  335. cmd += ['-symabis'] + args.symabis
  336. # If 1.12 <= version < 1.13 we have to pass -allabis for 'runtime' and 'runtime/internal/atomic'
  337. # if compare_versions('1.13', args.goversion) >= 0:
  338. # pass
  339. # elif import_path in ('runtime', 'runtime/internal/atomic'):
  340. # cmd.append('-allabis')
  341. compile_workers = '4'
  342. if args.compile_flags:
  343. if compiling_runtime:
  344. cmd.extend(x for x in args.compile_flags if x not in COMPILE_OPTIMIZATION_FLAGS)
  345. else:
  346. cmd.extend(args.compile_flags)
  347. if any([x in ('-race', '-shared') for x in args.compile_flags]):
  348. compile_workers = '1'
  349. cmd += ['-pack', '-c={}'.format(compile_workers)]
  350. cmd += args.go_srcs
  351. call(cmd, args.build_root)
  352. class VetThread(threading.Thread):
  353. def __init__(self, target, args):
  354. super(VetThread, self).__init__(target=target, args=args)
  355. self.exc_info = None
  356. def run(self):
  357. try:
  358. super(VetThread, self).run()
  359. except:
  360. self.exc_info = sys.exc_info()
  361. def join_with_exception(self, reraise_exception):
  362. self.join()
  363. if reraise_exception and self.exc_info:
  364. raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2])
  365. def do_compile_go(args):
  366. raise_exception_from_vet = False
  367. if args.vet:
  368. run_vet = VetThread(target=do_vet, args=(args,))
  369. run_vet.start()
  370. try:
  371. _do_compile_go(args)
  372. raise_exception_from_vet = True
  373. finally:
  374. if args.vet:
  375. run_vet.join_with_exception(raise_exception_from_vet)
  376. def do_compile_asm(args):
  377. assert len(args.srcs) == 1 and len(args.asm_srcs) == 1
  378. cmd = [args.go_asm]
  379. cmd += get_trimpath_args(args)
  380. cmd += ['-I', args.output_root, '-I', os.path.join(args.pkg_root, 'include')]
  381. cmd += ['-D', 'GOOS_' + args.targ_os, '-D', 'GOARCH_' + args.targ_arch, '-o', args.output]
  382. # if compare_versions('1.16', args.goversion) >= 0:
  383. cmd += ['-p', args.import_path]
  384. if args.asm_flags:
  385. cmd += args.asm_flags
  386. cmd += args.asm_srcs
  387. call(cmd, args.build_root)
  388. def do_link_lib(args):
  389. if len(args.asm_srcs) > 0:
  390. asmargs = copy_args(args)
  391. asmargs.asmhdr = os.path.join(asmargs.output_root, 'go_asm.h')
  392. do_compile_go(asmargs)
  393. for src in asmargs.asm_srcs:
  394. asmargs.srcs = [src]
  395. asmargs.asm_srcs = [src]
  396. asmargs.output = os.path.join(asmargs.output_root, os.path.basename(src) + '.o')
  397. do_compile_asm(asmargs)
  398. args.objects.append(asmargs.output)
  399. else:
  400. do_compile_go(args)
  401. if args.objects or args.sysos:
  402. cmd = [args.go_pack, 'r', args.output] + args.objects + args.sysos
  403. call(cmd, args.build_root)
  404. def do_link_exe(args):
  405. assert args.extld is not None
  406. assert args.non_local_peers is not None
  407. compile_args = copy_args(args)
  408. compile_args.output = os.path.join(args.output_root, 'main.a')
  409. compile_args.real_import_path = compile_args.import_path
  410. compile_args.import_path = 'main'
  411. if args.vcs and os.path.isfile(compile_args.vcs):
  412. build_info = os.path.join('library', 'go', 'core', 'buildinfo')
  413. if any([x.startswith(build_info) for x in compile_args.peers]):
  414. compile_args.go_srcs.append(compile_args.vcs)
  415. do_link_lib(compile_args)
  416. cmd = [args.go_link, '-o', args.output]
  417. import_config_name = create_import_config(
  418. args.peers + args.non_local_peers, False, args.import_map, args.module_map
  419. )
  420. if import_config_name:
  421. cmd += ['-importcfg', import_config_name]
  422. if args.link_flags:
  423. cmd += args.link_flags
  424. extldflags = []
  425. if args.buildmode:
  426. cmd.append('-buildmode={}'.format(args.buildmode))
  427. elif args.mode in ('exe', 'test'):
  428. mode = '-buildmode=exe'
  429. if 'ld.lld' in str(args):
  430. if '-fPIE' in str(args) or '-fPIC' in str(args):
  431. # support explicit PIE
  432. mode = '-buildmode=pie'
  433. else:
  434. extldflags.append('-Wl,-no-pie')
  435. cmd.append(mode)
  436. elif args.mode == 'dll':
  437. cmd.append('-buildmode=c-shared')
  438. else:
  439. assert False, 'Unexpected mode: {}'.format(args.mode)
  440. cmd.append('-extld={}'.format(args.extld))
  441. if args.extldflags is not None:
  442. filter_musl = bool
  443. if args.musl:
  444. cmd.append('-linkmode=external')
  445. extldflags.append('-static')
  446. filter_musl = lambda x: x not in ('-lc', '-ldl', '-lm', '-lpthread', '-lrt')
  447. extldflags += [x for x in args.extldflags if filter_musl(x)]
  448. cgo_peers = []
  449. if args.cgo_peers is not None and len(args.cgo_peers) > 0:
  450. is_group = args.targ_os == 'linux'
  451. if is_group:
  452. cgo_peers.append('-Wl,--start-group')
  453. cgo_peers.extend(args.cgo_peers)
  454. if is_group:
  455. cgo_peers.append('-Wl,--end-group')
  456. try:
  457. index = extldflags.index('--cgo-peers')
  458. extldflags = extldflags[:index] + cgo_peers + extldflags[index + 1 :]
  459. except ValueError:
  460. extldflags.extend(cgo_peers)
  461. if len(extldflags) > 0:
  462. cmd.append('-extldflags={}'.format(' '.join(extldflags)))
  463. cmd.append(compile_args.output)
  464. call(cmd, args.build_root)
  465. def gen_cover_info(args):
  466. lines = []
  467. lines.extend(
  468. [
  469. """
  470. var (
  471. coverCounters = make(map[string][]uint32)
  472. coverBlocks = make(map[string][]testing.CoverBlock)
  473. )
  474. """,
  475. 'func init() {',
  476. ]
  477. )
  478. for var, file in (x.split(':') for x in args.cover_info):
  479. lines.append(
  480. ' coverRegisterFile("{file}", _cover0.{var}.Count[:], _cover0.{var}.Pos[:], _cover0.{var}.NumStmt[:])'.format(
  481. file=file, var=var
  482. )
  483. )
  484. lines.extend(
  485. [
  486. '}',
  487. """
  488. func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) {
  489. if 3*len(counter) != len(pos) || len(counter) != len(numStmts) {
  490. panic("coverage: mismatched sizes")
  491. }
  492. if coverCounters[fileName] != nil {
  493. // Already registered.
  494. return
  495. }
  496. coverCounters[fileName] = counter
  497. block := make([]testing.CoverBlock, len(counter))
  498. for i := range counter {
  499. block[i] = testing.CoverBlock{
  500. Line0: pos[3*i+0],
  501. Col0: uint16(pos[3*i+2]),
  502. Line1: pos[3*i+1],
  503. Col1: uint16(pos[3*i+2]>>16),
  504. Stmts: numStmts[i],
  505. }
  506. }
  507. coverBlocks[fileName] = block
  508. }
  509. """,
  510. ]
  511. )
  512. return lines
  513. def filter_out_skip_tests(tests, skip_tests):
  514. skip_set = set()
  515. star_skip_set = set()
  516. for t in skip_tests:
  517. work_set = star_skip_set if '*' in t else skip_set
  518. work_set.add(t)
  519. re_star_tests = None
  520. if len(star_skip_set) > 0:
  521. re_star_tests = re.compile(re.sub(r'(\*)+', r'.\1', '^({})$'.format('|'.join(star_skip_set))))
  522. return [x for x in tests if not (x in skip_tests or re_star_tests and re_star_tests.match(x))]
  523. @contextmanager
  524. def create_strip_symlink():
  525. # This function creates symlink of llvm-strip as strip for golink needs.
  526. # We believe that cc-binaries path is a first element in PATH enviroment variable.
  527. tmpdir = None
  528. if os.getenv("CC") == "clang":
  529. tmpdir = tempfile.mkdtemp()
  530. cc_path = os.getenv("PATH").split(os.pathsep)[0]
  531. os.environ["PATH"] += os.pathsep + tmpdir
  532. src_strip_path = os.path.join(cc_path, 'llvm-strip')
  533. dst_strip_path = os.path.join(tmpdir, 'strip')
  534. os.symlink(src_strip_path, dst_strip_path)
  535. try:
  536. yield
  537. finally:
  538. if tmpdir:
  539. shutil.rmtree(tmpdir)
  540. def gen_test_main(args, test_lib_args, xtest_lib_args):
  541. assert args and (test_lib_args or xtest_lib_args)
  542. test_miner = args.test_miner
  543. test_module_path = test_lib_args.import_path if test_lib_args else xtest_lib_args.import_path
  544. is_cover = args.cover_info and len(args.cover_info) > 0
  545. # Prepare GOPATH
  546. # $BINDIR
  547. # |- __go__
  548. # |- src
  549. # |- pkg
  550. # |- ${TARGET_OS}_${TARGET_ARCH}
  551. go_path_root = os.path.join(args.output_root, '__go__')
  552. test_src_dir = os.path.join(go_path_root, 'src')
  553. target_os_arch = '_'.join([args.targ_os, args.targ_arch])
  554. test_pkg_dir = os.path.join(go_path_root, 'pkg', target_os_arch, os.path.dirname(test_module_path))
  555. os.makedirs(test_pkg_dir)
  556. my_env = os.environ.copy()
  557. my_env['GOROOT'] = ''
  558. my_env['GOPATH'] = go_path_root
  559. my_env['GOARCH'] = args.targ_arch
  560. my_env['GOOS'] = args.targ_os
  561. tests = []
  562. xtests = []
  563. os_symlink = get_symlink_or_copyfile()
  564. # Get the list of "internal" tests
  565. if test_lib_args:
  566. os.makedirs(os.path.join(test_src_dir, test_module_path))
  567. os_symlink(test_lib_args.output, os.path.join(test_pkg_dir, os.path.basename(test_module_path) + '.a'))
  568. cmd = [test_miner, '-benchmarks', '-tests', test_module_path]
  569. tests = [x for x in (call(cmd, test_lib_args.output_root, my_env) or '').strip().split('\n') if len(x) > 0]
  570. if args.skip_tests:
  571. tests = filter_out_skip_tests(tests, args.skip_tests)
  572. test_main_found = '#TestMain' in tests
  573. # Get the list of "external" tests
  574. if xtest_lib_args:
  575. xtest_module_path = xtest_lib_args.import_path
  576. os.makedirs(os.path.join(test_src_dir, xtest_module_path))
  577. os_symlink(xtest_lib_args.output, os.path.join(test_pkg_dir, os.path.basename(xtest_module_path) + '.a'))
  578. cmd = [test_miner, '-benchmarks', '-tests', xtest_module_path]
  579. xtests = [x for x in (call(cmd, xtest_lib_args.output_root, my_env) or '').strip().split('\n') if len(x) > 0]
  580. if args.skip_tests:
  581. xtests = filter_out_skip_tests(xtests, args.skip_tests)
  582. xtest_main_found = '#TestMain' in xtests
  583. test_main_package = None
  584. if test_main_found and xtest_main_found:
  585. assert False, 'multiple definition of TestMain'
  586. elif test_main_found:
  587. test_main_package = '_test'
  588. elif xtest_main_found:
  589. test_main_package = '_xtest'
  590. shutil.rmtree(go_path_root)
  591. lines = ['package main', '', 'import (']
  592. if test_main_package is None:
  593. lines.append(' "os"')
  594. lines.extend([' "testing"', ' "testing/internal/testdeps"'])
  595. lines.extend([' _ "{}library/go/test/yatest"'.format(args.arc_project_prefix)])
  596. if len(tests) > 0:
  597. lines.append(' _test "{}"'.format(test_module_path))
  598. elif test_lib_args:
  599. lines.append(' _ "{}"'.format(test_module_path))
  600. if len(xtests) > 0:
  601. lines.append(' _xtest "{}"'.format(xtest_module_path))
  602. elif xtest_lib_args:
  603. lines.append(' _ "{}"'.format(xtest_module_path))
  604. if is_cover:
  605. lines.append(' _cover0 "{}"'.format(test_module_path))
  606. lines.extend([')', ''])
  607. if compare_versions('1.18', args.goversion) < 0:
  608. kinds = ['Test', 'Benchmark', 'Example']
  609. else:
  610. kinds = ['Test', 'Benchmark', 'FuzzTarget', 'Example']
  611. var_names = []
  612. for kind in kinds:
  613. var_name = '{}s'.format(kind.lower())
  614. var_names.append(var_name)
  615. lines.append('var {} = []testing.Internal{}{{'.format(var_name, kind))
  616. for test in [x for x in tests if x.startswith(kind)]:
  617. lines.append(' {{"{test}", _test.{test}}},'.format(test=test))
  618. for test in [x for x in xtests if x.startswith(kind)]:
  619. lines.append(' {{"{test}", _xtest.{test}}},'.format(test=test))
  620. lines.extend(['}', ''])
  621. if is_cover:
  622. lines.extend(gen_cover_info(args))
  623. lines.append('func main() {')
  624. if is_cover:
  625. lines.extend(
  626. [
  627. ' testing.RegisterCover(testing.Cover{',
  628. ' Mode: "set",',
  629. ' Counters: coverCounters,',
  630. ' Blocks: coverBlocks,',
  631. ' CoveredPackages: "",',
  632. ' })',
  633. ]
  634. )
  635. lines.extend(
  636. [
  637. ' m := testing.MainStart(testdeps.TestDeps{{}}, {})'.format(', '.join(var_names)),
  638. '',
  639. ]
  640. )
  641. if test_main_package:
  642. lines.append(' {}.TestMain(m)'.format(test_main_package))
  643. else:
  644. lines.append(' os.Exit(m.Run())')
  645. lines.extend(['}', ''])
  646. content = '\n'.join(lines)
  647. # sys.stderr.write('{}\n'.format(content))
  648. return content
  649. def do_link_test(args):
  650. assert args.srcs or args.xtest_srcs
  651. assert args.test_miner is not None
  652. test_module_path = get_source_path(args)
  653. test_import_path, _ = get_import_path(test_module_path)
  654. test_lib_args = copy_args(args) if args.srcs else None
  655. xtest_lib_args = copy_args(args) if args.xtest_srcs else None
  656. if xtest_lib_args is not None:
  657. xtest_lib_args.embed = args.embed_xtest if args.embed_xtest else None
  658. ydx_file_name = None
  659. xtest_ydx_file_name = None
  660. need_append_ydx = test_lib_args and xtest_lib_args and args.ydx_file and args.vet_flags
  661. if need_append_ydx:
  662. def find_ydx_file_name(name, flags):
  663. for i, elem in enumerate(flags):
  664. if elem.endswith(name):
  665. return (i, elem)
  666. assert False, 'Unreachable code'
  667. idx, ydx_file_name = find_ydx_file_name(xtest_lib_args.ydx_file, xtest_lib_args.vet_flags)
  668. xtest_ydx_file_name = '{}_xtest'.format(ydx_file_name)
  669. xtest_lib_args.vet_flags = copy.copy(xtest_lib_args.vet_flags)
  670. xtest_lib_args.vet_flags[idx] = xtest_ydx_file_name
  671. if test_lib_args:
  672. test_lib_args.output = os.path.join(args.output_root, 'test.a')
  673. test_lib_args.vet_report_output = vet_report_output_name(test_lib_args.output)
  674. test_lib_args.module_path = test_module_path
  675. test_lib_args.import_path = test_import_path
  676. do_link_lib(test_lib_args)
  677. if xtest_lib_args:
  678. xtest_lib_args.srcs = xtest_lib_args.xtest_srcs
  679. classify_srcs(xtest_lib_args.srcs, xtest_lib_args)
  680. xtest_lib_args.output = os.path.join(args.output_root, 'xtest.a')
  681. xtest_lib_args.vet_report_output = vet_report_output_name(xtest_lib_args.output)
  682. xtest_lib_args.module_path = test_module_path + '_test'
  683. xtest_lib_args.import_path = test_import_path + '_test'
  684. if test_lib_args:
  685. xtest_lib_args.module_map[test_import_path] = test_lib_args.output
  686. need_append_ydx = args.ydx_file and args.srcs and args.vet_flags
  687. do_link_lib(xtest_lib_args)
  688. if need_append_ydx:
  689. with open(os.path.join(args.build_root, ydx_file_name), 'ab') as dst_file:
  690. with open(os.path.join(args.build_root, xtest_ydx_file_name), 'rb') as src_file:
  691. dst_file.write(src_file.read())
  692. test_main_content = gen_test_main(args, test_lib_args, xtest_lib_args)
  693. test_main_name = os.path.join(args.output_root, '_test_main.go')
  694. with open(test_main_name, "w") as f:
  695. f.write(test_main_content)
  696. test_args = copy_args(args)
  697. test_args.embed = None
  698. test_args.srcs = [test_main_name]
  699. if test_args.test_import_path is None:
  700. # it seems that we can do it unconditionally, but this kind
  701. # of mangling doesn't really looks good to me and we leave it
  702. # for pure GO_TEST module
  703. test_args.module_path = test_args.module_path + '___test_main__'
  704. test_args.import_path = test_args.import_path + '___test_main__'
  705. classify_srcs(test_args.srcs, test_args)
  706. if test_lib_args:
  707. test_args.module_map[test_lib_args.import_path] = test_lib_args.output
  708. if xtest_lib_args:
  709. test_args.module_map[xtest_lib_args.import_path] = xtest_lib_args.output
  710. if args.vet:
  711. dump_vet_report_for_tests(test_args, test_lib_args, xtest_lib_args)
  712. test_args.vet = False
  713. do_link_exe(test_args)
  714. if __name__ == '__main__':
  715. args = pcf.get_args(sys.argv[1:])
  716. parser = argparse.ArgumentParser(prefix_chars='+')
  717. parser.add_argument('++mode', choices=['dll', 'exe', 'lib', 'test'], required=True)
  718. parser.add_argument('++buildmode', choices=['c-shared', 'exe', 'pie'])
  719. parser.add_argument('++srcs', nargs='*', required=True)
  720. parser.add_argument('++cgo-srcs', nargs='*')
  721. parser.add_argument('++test_srcs', nargs='*')
  722. parser.add_argument('++xtest_srcs', nargs='*')
  723. parser.add_argument('++cover_info', nargs='*')
  724. parser.add_argument('++output', nargs='?', default=None)
  725. parser.add_argument('++source-root', default=None)
  726. parser.add_argument('++build-root', required=True)
  727. parser.add_argument('++tools-root', default=None)
  728. parser.add_argument('++output-root', required=True)
  729. parser.add_argument('++toolchain-root', required=True)
  730. parser.add_argument('++host-os', choices=['linux', 'darwin', 'windows'], required=True)
  731. parser.add_argument('++host-arch', choices=['amd64', 'arm64'], required=True)
  732. parser.add_argument('++targ-os', choices=['linux', 'darwin', 'windows'], required=True)
  733. parser.add_argument('++targ-arch', choices=['amd64', 'x86', 'arm64'], required=True)
  734. parser.add_argument('++peers', nargs='*')
  735. parser.add_argument('++non-local-peers', nargs='*')
  736. parser.add_argument('++cgo-peers', nargs='*')
  737. parser.add_argument('++asmhdr', nargs='?', default=None)
  738. parser.add_argument('++test-import-path', nargs='?')
  739. parser.add_argument('++test-miner', nargs='?')
  740. parser.add_argument('++arc-project-prefix', nargs='?', default=arc_project_prefix)
  741. parser.add_argument('++std-lib-prefix', nargs='?', default=std_lib_prefix)
  742. parser.add_argument('++vendor-prefix', nargs='?', default=vendor_prefix)
  743. parser.add_argument('++extld', nargs='?', default=None)
  744. parser.add_argument('++extldflags', nargs='+', default=None)
  745. parser.add_argument('++goversion', required=True)
  746. parser.add_argument('++lang', nargs='?', default=None)
  747. parser.add_argument('++asm-flags', nargs='*')
  748. parser.add_argument('++compile-flags', nargs='*')
  749. parser.add_argument('++link-flags', nargs='*')
  750. parser.add_argument('++vcs', nargs='?', default=None)
  751. parser.add_argument('++vet', nargs='?', const=True, default=False)
  752. parser.add_argument('++vet-flags', nargs='*', default=None)
  753. parser.add_argument('++vet-info-ext', default=vet_info_ext)
  754. parser.add_argument('++vet-report-ext', default=vet_report_ext)
  755. parser.add_argument('++musl', action='store_true')
  756. parser.add_argument('++skip-tests', nargs='*', default=None)
  757. parser.add_argument('++ydx-file', default='')
  758. parser.add_argument('++debug-root-map', default=None)
  759. parser.add_argument('++embed', action='append', nargs='*')
  760. parser.add_argument('++embed_xtest', action='append', nargs='*')
  761. args = parser.parse_args(args)
  762. arc_project_prefix = args.arc_project_prefix
  763. std_lib_prefix = args.std_lib_prefix
  764. vendor_prefix = args.vendor_prefix
  765. vet_info_ext = args.vet_info_ext
  766. vet_report_ext = args.vet_report_ext
  767. preprocess_args(args)
  768. try:
  769. os.unlink(args.output)
  770. except OSError:
  771. pass
  772. # We are going to support only 'lib', 'exe' and 'cgo' build modes currently
  773. # and as a result we are going to generate only one build node per module
  774. # (or program)
  775. dispatch = {'exe': do_link_exe, 'dll': do_link_exe, 'lib': do_link_lib, 'test': do_link_test}
  776. exit_code = 1
  777. try:
  778. with create_strip_symlink():
  779. dispatch[args.mode](args)
  780. exit_code = 0
  781. except subprocess.CalledProcessError as e:
  782. sys.stderr.write('{} returned non-zero exit code {}.\n{}\n'.format(' '.join(e.cmd), e.returncode, e.output))
  783. exit_code = e.returncode
  784. sys.exit(exit_code)