link_dyn_lib.py 12 KB

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