link_dyn_lib.py 9.9 KB

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