link_dyn_lib.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. from __future__ import print_function
  2. import sys
  3. import os
  4. import json
  5. import subprocess
  6. import tempfile
  7. import collections
  8. import optparse
  9. import pipes
  10. # Explicitly enable local imports
  11. # Don't forget to add imported scripts to inputs of the calling command!
  12. sys.path.append(os.path.dirname(os.path.abspath(__file__)))
  13. import thinlto_cache
  14. import link_exe
  15. from process_whole_archive_option import ProcessWholeArchiveOption
  16. def shlex_join(cmd):
  17. # equivalent to shlex.join() in python 3
  18. return ' '.join(pipes.quote(part) for part in cmd)
  19. def parse_export_file(p):
  20. with open(p, 'r') as f:
  21. for l in f:
  22. l = l.strip()
  23. if l and '#' not in l:
  24. words = l.split()
  25. if len(words) == 2 and words[0] == 'linux_version':
  26. yield {'linux_version': words[1]}
  27. elif len(words) == 2:
  28. yield {'lang': words[0], 'sym': words[1]}
  29. elif len(words) == 1:
  30. yield {'lang': 'C', 'sym': words[0]}
  31. else:
  32. raise Exception('unsupported exports line: ' + l)
  33. def to_c(sym):
  34. symbols = collections.deque(sym.split('::'))
  35. c_prefixes = [ # demangle prefixes for c++ symbols
  36. '_ZN', # namespace
  37. '_ZTIN', # typeinfo for
  38. '_ZTSN', # typeinfo name for
  39. '_ZTTN', # VTT for
  40. '_ZTVN', # vtable for
  41. '_ZNK', # const methods
  42. ]
  43. c_sym = ''
  44. while symbols:
  45. s = symbols.popleft()
  46. if s == '*':
  47. c_sym += '*'
  48. break
  49. if '*' in s and len(s) > 1:
  50. raise Exception('Unsupported format, cannot guess length of symbol: ' + s)
  51. c_sym += str(len(s)) + s
  52. if symbols:
  53. raise Exception('Unsupported format: ' + sym)
  54. if c_sym[-1] != '*':
  55. c_sym += 'E*'
  56. return ['{prefix}{sym}'.format(prefix=prefix, sym=c_sym) for prefix in c_prefixes]
  57. def fix_darwin_param(ex):
  58. for item in ex:
  59. if item.get('linux_version'):
  60. continue
  61. if item['lang'] == 'C':
  62. yield '-Wl,-exported_symbol,_' + item['sym']
  63. elif item['lang'] == 'C++':
  64. for sym in to_c(item['sym']):
  65. yield '-Wl,-exported_symbol,_' + sym
  66. else:
  67. raise Exception('unsupported lang: ' + item['lang'])
  68. def fix_gnu_param(arch, ex):
  69. d = collections.defaultdict(list)
  70. version = None
  71. for item in ex:
  72. if item.get('linux_version'):
  73. if not version:
  74. version = item.get('linux_version')
  75. else:
  76. raise Exception('More than one linux_version defined')
  77. elif item['lang'] == 'C++':
  78. d['C'].extend(to_c(item['sym']))
  79. else:
  80. d[item['lang']].append(item['sym'])
  81. with tempfile.NamedTemporaryFile(mode='wt', delete=False) as f:
  82. if version:
  83. f.write('{} {{\nglobal:\n'.format(version))
  84. else:
  85. f.write('{\nglobal:\n')
  86. for k, v in d.items():
  87. f.write(' extern "' + k + '" {\n')
  88. for x in v:
  89. f.write(' ' + x + ';\n')
  90. f.write(' };\n')
  91. f.write('local: *;\n};\n')
  92. ret = ['-Wl,--version-script=' + f.name]
  93. if arch == 'ANDROID':
  94. ret += ['-Wl,--export-dynamic']
  95. return ret
  96. def fix_windows_param(ex):
  97. with tempfile.NamedTemporaryFile(delete=False) as def_file:
  98. exports = []
  99. for item in ex:
  100. if item.get('lang') == 'C':
  101. exports.append(item.get('sym'))
  102. def_file.write('EXPORTS\n')
  103. for export in exports:
  104. def_file.write(' {}\n'.format(export))
  105. return ['/DEF:{}'.format(def_file.name)]
  106. CUDA_LIBRARIES = {
  107. '-lcublas_static': '-lcublas',
  108. '-lcublasLt_static': '-lcublasLt',
  109. '-lcudart_static': '-lcudart',
  110. '-lcudnn_static': '-lcudnn',
  111. '-lcufft_static_nocallback': '-lcufft',
  112. '-lcurand_static': '-lcurand',
  113. '-lcusolver_static': '-lcusolver',
  114. '-lcusparse_static': '-lcusparse',
  115. '-lmyelin_compiler_static': '-lmyelin',
  116. '-lmyelin_executor_static': '-lnvcaffe_parser',
  117. '-lmyelin_pattern_library_static': '',
  118. '-lmyelin_pattern_runtime_static': '',
  119. '-lnvinfer_static': '-lnvinfer',
  120. '-lnvinfer_plugin_static': '-lnvinfer_plugin',
  121. '-lnvonnxparser_static': '-lnvonnxparser',
  122. '-lnvparsers_static': '-lnvparsers',
  123. }
  124. def fix_cmd(arch, c):
  125. if arch == 'WINDOWS':
  126. prefix = '/DEF:'
  127. f = fix_windows_param
  128. else:
  129. prefix = '-Wl,--version-script='
  130. if arch in ('DARWIN', 'IOS', 'IOSSIM'):
  131. f = fix_darwin_param
  132. else:
  133. f = lambda x: fix_gnu_param(arch, x)
  134. def do_fix(p):
  135. if p.startswith(prefix) and p.endswith('.exports'):
  136. fname = p[len(prefix) :]
  137. return list(f(list(parse_export_file(fname))))
  138. if p.endswith('.supp'):
  139. return []
  140. if p.endswith('.pkg.fake'):
  141. return []
  142. return [p]
  143. return sum((do_fix(x) for x in c), [])
  144. def fix_cmd_for_dynamic_cuda(cmd):
  145. flags = []
  146. for flag in cmd:
  147. if flag in CUDA_LIBRARIES:
  148. flags.append(CUDA_LIBRARIES[flag])
  149. else:
  150. flags.append(flag)
  151. return flags
  152. def parse_args(args):
  153. parser = optparse.OptionParser()
  154. parser.disable_interspersed_args()
  155. parser.add_option('--arch')
  156. parser.add_option('--target')
  157. parser.add_option('--soname')
  158. parser.add_option('--source-root')
  159. parser.add_option('--build-root')
  160. parser.add_option('--fix-elf')
  161. parser.add_option('--linker-output')
  162. parser.add_option('--dynamic-cuda', action='store_true')
  163. parser.add_option('--cuda-architectures',
  164. help='List of supported CUDA architectures, separated by ":" (e.g. "sm_52:compute_70:lto_90a"')
  165. parser.add_option('--nvprune-exe')
  166. parser.add_option('--objcopy-exe')
  167. parser.add_option('--whole-archive-peers', action='append')
  168. parser.add_option('--whole-archive-libs', action='append')
  169. parser.add_option('--custom-step')
  170. parser.add_option('--python')
  171. thinlto_cache.add_options(parser)
  172. return parser.parse_args(args)
  173. if __name__ == '__main__':
  174. args = sys.argv[1:]
  175. plugins = []
  176. if '--start-plugins' in args:
  177. ib = args.index('--start-plugins')
  178. ie = args.index('--end-plugins')
  179. plugins = args[ib + 1:ie]
  180. args = args[:ib] + args[ie + 1:]
  181. for p in plugins:
  182. res = subprocess.check_output([sys.executable, p] + args).decode().strip()
  183. if res:
  184. args = json.loads(res)
  185. opts, args = parse_args(args)
  186. assert opts.arch
  187. assert opts.target
  188. cmd = args
  189. cmd = fix_cmd(opts.arch, cmd)
  190. if opts.dynamic_cuda:
  191. cmd = fix_cmd_for_dynamic_cuda(cmd)
  192. else:
  193. cuda_manager = link_exe.CUDAManager(opts.cuda_architectures, opts.nvprune_exe)
  194. cmd = link_exe.process_cuda_libraries_by_nvprune(cmd, cuda_manager, opts.build_root)
  195. cmd = link_exe.process_cuda_libraries_by_objcopy(cmd, opts.build_root, opts.objcopy_exe)
  196. cmd = ProcessWholeArchiveOption(opts.arch, opts.whole_archive_peers, opts.whole_archive_libs).construct_cmd(cmd)
  197. thinlto_cache.preprocess(opts, cmd)
  198. if opts.custom_step:
  199. assert opts.python
  200. subprocess.check_call([opts.python] + [opts.custom_step] + cmd)
  201. if opts.linker_output:
  202. stdout = open(opts.linker_output, 'w')
  203. else:
  204. stdout = sys.stdout
  205. proc = subprocess.Popen(cmd, shell=False, stderr=sys.stderr, stdout=stdout)
  206. proc.communicate()
  207. thinlto_cache.postprocess(opts)
  208. if proc.returncode:
  209. print('linker has failed with retcode:', proc.returncode, file=sys.stderr)
  210. print('linker command:', shlex_join(cmd), file=sys.stderr)
  211. sys.exit(proc.returncode)
  212. if opts.fix_elf:
  213. cmd = [opts.fix_elf, opts.target]
  214. proc = subprocess.Popen(cmd, shell=False, stderr=sys.stderr, stdout=sys.stdout)
  215. proc.communicate()
  216. if proc.returncode:
  217. print('fix_elf has failed with retcode:', proc.returncode, file=sys.stderr)
  218. print('fix_elf command:', shlex_join(cmd), file=sys.stderr)
  219. sys.exit(proc.returncode)
  220. if opts.soname and opts.soname != opts.target:
  221. if os.path.exists(opts.soname):
  222. os.unlink(opts.soname)
  223. os.link(opts.target, opts.soname)
  224. # -----------------Test---------------- #
  225. def write_temp_file(content):
  226. import yatest.common as yc
  227. filename = yc.output_path('test.exports')
  228. with open(filename, 'w') as f:
  229. f.write(content)
  230. return filename
  231. def test_fix_cmd_darwin():
  232. export_file_content = """
  233. C++ geobase5::details::lookup_impl::*
  234. C++ geobase5::hardcoded_service
  235. """
  236. filename = write_temp_file(export_file_content)
  237. args = ['-Wl,--version-script={}'.format(filename)]
  238. assert fix_cmd('DARWIN', args) == [
  239. '-Wl,-exported_symbol,__ZN8geobase57details11lookup_impl*',
  240. '-Wl,-exported_symbol,__ZTIN8geobase57details11lookup_impl*',
  241. '-Wl,-exported_symbol,__ZTSN8geobase57details11lookup_impl*',
  242. '-Wl,-exported_symbol,__ZTTN8geobase57details11lookup_impl*',
  243. '-Wl,-exported_symbol,__ZTVN8geobase57details11lookup_impl*',
  244. '-Wl,-exported_symbol,__ZNK8geobase57details11lookup_impl*',
  245. '-Wl,-exported_symbol,__ZN8geobase517hardcoded_serviceE*',
  246. '-Wl,-exported_symbol,__ZTIN8geobase517hardcoded_serviceE*',
  247. '-Wl,-exported_symbol,__ZTSN8geobase517hardcoded_serviceE*',
  248. '-Wl,-exported_symbol,__ZTTN8geobase517hardcoded_serviceE*',
  249. '-Wl,-exported_symbol,__ZTVN8geobase517hardcoded_serviceE*',
  250. '-Wl,-exported_symbol,__ZNK8geobase517hardcoded_serviceE*',
  251. ]
  252. def run_fix_gnu_param(export_file_content):
  253. filename = write_temp_file(export_file_content)
  254. result = fix_gnu_param('LINUX', list(parse_export_file(filename)))[0]
  255. version_script_path = result[len('-Wl,--version-script=') :]
  256. with open(version_script_path) as f:
  257. content = f.read()
  258. return content
  259. def test_fix_gnu_param():
  260. export_file_content = """
  261. C++ geobase5::details::lookup_impl::*
  262. C getFactoryMap
  263. """
  264. assert (
  265. run_fix_gnu_param(export_file_content)
  266. == """{
  267. global:
  268. extern "C" {
  269. _ZN8geobase57details11lookup_impl*;
  270. _ZTIN8geobase57details11lookup_impl*;
  271. _ZTSN8geobase57details11lookup_impl*;
  272. _ZTTN8geobase57details11lookup_impl*;
  273. _ZTVN8geobase57details11lookup_impl*;
  274. _ZNK8geobase57details11lookup_impl*;
  275. getFactoryMap;
  276. };
  277. local: *;
  278. };
  279. """
  280. )
  281. def test_fix_gnu_param_with_linux_version():
  282. export_file_content = """
  283. C++ geobase5::details::lookup_impl::*
  284. linux_version ver1.0
  285. C getFactoryMap
  286. """
  287. assert (
  288. run_fix_gnu_param(export_file_content)
  289. == """ver1.0 {
  290. global:
  291. extern "C" {
  292. _ZN8geobase57details11lookup_impl*;
  293. _ZTIN8geobase57details11lookup_impl*;
  294. _ZTSN8geobase57details11lookup_impl*;
  295. _ZTTN8geobase57details11lookup_impl*;
  296. _ZTVN8geobase57details11lookup_impl*;
  297. _ZNK8geobase57details11lookup_impl*;
  298. getFactoryMap;
  299. };
  300. local: *;
  301. };
  302. """
  303. )