link_dyn_lib.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. def fix_cmd(arch, c):
  107. if arch == 'WINDOWS':
  108. prefix = '/DEF:'
  109. f = fix_windows_param
  110. else:
  111. prefix = '-Wl,--version-script='
  112. if arch in ('DARWIN', 'IOS', 'IOSSIM'):
  113. f = fix_darwin_param
  114. else:
  115. f = lambda x: fix_gnu_param(arch, x)
  116. def do_fix(p):
  117. if p.startswith(prefix) and p.endswith('.exports'):
  118. fname = p[len(prefix) :]
  119. return list(f(list(parse_export_file(fname))))
  120. if p.endswith('.pkg.fake'):
  121. return []
  122. return [p]
  123. return sum((do_fix(x) for x in c), [])
  124. def parse_args(args):
  125. parser = optparse.OptionParser()
  126. parser.disable_interspersed_args()
  127. parser.add_option('--arch')
  128. parser.add_option('--target')
  129. parser.add_option('--soname')
  130. parser.add_option('--source-root')
  131. parser.add_option('--build-root')
  132. parser.add_option('--fix-elf')
  133. parser.add_option('--linker-output')
  134. parser.add_option('--dynamic-cuda', action='store_true')
  135. parser.add_option('--cuda-architectures',
  136. help='List of supported CUDA architectures, separated by ":" (e.g. "sm_52:compute_70:lto_90a"')
  137. parser.add_option('--nvprune-exe')
  138. parser.add_option('--objcopy-exe')
  139. parser.add_option('--whole-archive-peers', action='append')
  140. parser.add_option('--whole-archive-libs', action='append')
  141. parser.add_option('--custom-step')
  142. parser.add_option('--python')
  143. thinlto_cache.add_options(parser)
  144. return parser.parse_args(args)
  145. if __name__ == '__main__':
  146. args = sys.argv[1:]
  147. plugins = []
  148. if '--start-plugins' in args:
  149. ib = args.index('--start-plugins')
  150. ie = args.index('--end-plugins')
  151. plugins = args[ib + 1:ie]
  152. args = args[:ib] + args[ie + 1:]
  153. for p in plugins:
  154. res = subprocess.check_output([sys.executable, p] + args).decode().strip()
  155. if res:
  156. args = json.loads(res)
  157. opts, args = parse_args(args)
  158. assert opts.arch
  159. assert opts.target
  160. cmd = args
  161. cmd = fix_cmd(opts.arch, cmd)
  162. cmd = ProcessWholeArchiveOption(opts.arch, opts.whole_archive_peers, opts.whole_archive_libs).construct_cmd(cmd)
  163. thinlto_cache.preprocess(opts, cmd)
  164. if opts.custom_step:
  165. assert opts.python
  166. subprocess.check_call([opts.python] + [opts.custom_step] + cmd)
  167. if opts.linker_output:
  168. stdout = open(opts.linker_output, 'w')
  169. else:
  170. stdout = sys.stdout
  171. proc = subprocess.Popen(cmd, shell=False, stderr=sys.stderr, stdout=stdout)
  172. proc.communicate()
  173. thinlto_cache.postprocess(opts)
  174. if proc.returncode:
  175. print('linker has failed with retcode:', proc.returncode, file=sys.stderr)
  176. print('linker command:', shlex_join(cmd), file=sys.stderr)
  177. sys.exit(proc.returncode)
  178. if opts.fix_elf:
  179. cmd = [opts.fix_elf, opts.target]
  180. proc = subprocess.Popen(cmd, shell=False, stderr=sys.stderr, stdout=sys.stdout)
  181. proc.communicate()
  182. if proc.returncode:
  183. print('fix_elf has failed with retcode:', proc.returncode, file=sys.stderr)
  184. print('fix_elf command:', shlex_join(cmd), file=sys.stderr)
  185. sys.exit(proc.returncode)
  186. if opts.soname and opts.soname != opts.target:
  187. if os.path.exists(opts.soname):
  188. os.unlink(opts.soname)
  189. os.link(opts.target, opts.soname)
  190. # -----------------Test---------------- #
  191. def write_temp_file(content):
  192. import yatest.common as yc
  193. filename = yc.output_path('test.exports')
  194. with open(filename, 'w') as f:
  195. f.write(content)
  196. return filename
  197. def test_fix_cmd_darwin():
  198. export_file_content = """
  199. C++ geobase5::details::lookup_impl::*
  200. C++ geobase5::hardcoded_service
  201. """
  202. filename = write_temp_file(export_file_content)
  203. args = ['-Wl,--version-script={}'.format(filename)]
  204. assert fix_cmd('DARWIN', args) == [
  205. '-Wl,-exported_symbol,__ZN8geobase57details11lookup_impl*',
  206. '-Wl,-exported_symbol,__ZTIN8geobase57details11lookup_impl*',
  207. '-Wl,-exported_symbol,__ZTSN8geobase57details11lookup_impl*',
  208. '-Wl,-exported_symbol,__ZTTN8geobase57details11lookup_impl*',
  209. '-Wl,-exported_symbol,__ZTVN8geobase57details11lookup_impl*',
  210. '-Wl,-exported_symbol,__ZNK8geobase57details11lookup_impl*',
  211. '-Wl,-exported_symbol,__ZN8geobase517hardcoded_serviceE*',
  212. '-Wl,-exported_symbol,__ZTIN8geobase517hardcoded_serviceE*',
  213. '-Wl,-exported_symbol,__ZTSN8geobase517hardcoded_serviceE*',
  214. '-Wl,-exported_symbol,__ZTTN8geobase517hardcoded_serviceE*',
  215. '-Wl,-exported_symbol,__ZTVN8geobase517hardcoded_serviceE*',
  216. '-Wl,-exported_symbol,__ZNK8geobase517hardcoded_serviceE*',
  217. ]
  218. def run_fix_gnu_param(export_file_content):
  219. filename = write_temp_file(export_file_content)
  220. result = fix_gnu_param('LINUX', list(parse_export_file(filename)))[0]
  221. version_script_path = result[len('-Wl,--version-script=') :]
  222. with open(version_script_path) as f:
  223. content = f.read()
  224. return content
  225. def test_fix_gnu_param():
  226. export_file_content = """
  227. C++ geobase5::details::lookup_impl::*
  228. C getFactoryMap
  229. """
  230. assert (
  231. run_fix_gnu_param(export_file_content)
  232. == """{
  233. global:
  234. extern "C" {
  235. _ZN8geobase57details11lookup_impl*;
  236. _ZTIN8geobase57details11lookup_impl*;
  237. _ZTSN8geobase57details11lookup_impl*;
  238. _ZTTN8geobase57details11lookup_impl*;
  239. _ZTVN8geobase57details11lookup_impl*;
  240. _ZNK8geobase57details11lookup_impl*;
  241. getFactoryMap;
  242. };
  243. local: *;
  244. };
  245. """
  246. )
  247. def test_fix_gnu_param_with_linux_version():
  248. export_file_content = """
  249. C++ geobase5::details::lookup_impl::*
  250. linux_version ver1.0
  251. C getFactoryMap
  252. """
  253. assert (
  254. run_fix_gnu_param(export_file_content)
  255. == """ver1.0 {
  256. global:
  257. extern "C" {
  258. _ZN8geobase57details11lookup_impl*;
  259. _ZTIN8geobase57details11lookup_impl*;
  260. _ZTSN8geobase57details11lookup_impl*;
  261. _ZTTN8geobase57details11lookup_impl*;
  262. _ZTVN8geobase57details11lookup_impl*;
  263. _ZNK8geobase57details11lookup_impl*;
  264. getFactoryMap;
  265. };
  266. local: *;
  267. };
  268. """
  269. )