#!/usr/bin/env python2
# Copyright 2019 The Emscripten Authors.  All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License.  Both these licenses can be
# found in the LICENSE file.

"""Update 'symbols' files based on the contents of libraries in the cache.

The symbols files looks like the output of `nm` but only contain external
symbols and the symbols from all object in that archive are sorted and
de-duplicated.
"""

import argparse
import glob
import os
import sys

root_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
sys.path.insert(0, root_dir)

from tools import shared, cache


def filter_and_sort(symbols):
  lines = symbols.splitlines()
  lines = [l.rstrip() for l in lines]
  lines = [l for l in lines if l and l[-1] != ':']

  # Extract symbol type and name
  symbols = [l.split()[-2:] for l in lines]

  # Remove local symbols (lowercase type name)
  symbols = [(typ, name) for typ, name in symbols if typ.isupper()]

  symbol_map = {}
  for sym_type, sym_name in symbols:
    assert sym_type in ('W', 'T', 'D', 'C', 'U')
    existing_type = symbol_map.get(sym_name)
    if not existing_type:
      symbol_map[sym_name] = sym_type
      continue
    if existing_type == 'U' and sym_type != 'U':
      symbol_map[sym_name] = sym_type
    elif existing_type == 'W' and sym_type not in ('W', 'U'):
      symbol_map[sym_name] = sym_type
    elif sym_type not in ('W', 'U'):
      # We don't expect to see two defined version of a given symbol
      if existing_type != sym_type:
        print('Unexpected symbol types found: %s: %s vs %s' % (sym_name, existing_type, sym_type))
  symbols = [(typ, name) for name, typ in symbol_map.items()]

  # sort by name
  symbols.sort(key=lambda s: s[1])

  lines = ['# Auto-generated by tools/update_symbols.py.  DO NOT EDIT.']
  for typ, name in symbols:
    if typ == 'U':
      lines.append("         %s %s" % (typ, name))
    else:
      lines.append("-------- %s %s" % (typ, name))

  return '\n'.join(lines) + '\n'


def handle_symbol_file(args, symbol_file):
  """Regenerate the contents of a given symbol file."""

  if args.filter_in_place:
    output = open(symbol_file).read()
  else:
    basename = os.path.splitext(os.path.basename(symbol_file))[0]
    if not basename.startswith('lib'):
      basename = 'lib' + basename
    if basename.endswith('_asmjs'):
      basename = basename.rsplit('_asmjs', 1)[0]
    basename = basename.replace('cxx', 'c++')
    cache_dir = cache.Cache().dirname
    pattern = os.path.join(cache_dir, basename + '.*')
    libs = glob.glob(pattern)
    if basename == 'libgl':
      # For libgl we generate the symbol list based on a superset of all
      # library variants.
      pattern = os.path.join(cache_dir, basename + '-*.*')
      libs += glob.glob(pattern)
    if not libs:
      print(cache_dir)
      print("%s: Unable to find library to generate symbols from" % symbol_file)
      return
    print('Generating %s based on syms from: %s' % (basename, libs))
    output = ''
    for lib in libs:
      output += shared.run_process([shared.LLVM_NM, '-g', lib], stdout=shared.PIPE).stdout
  new_symbols = filter_and_sort(output)

  with open(symbol_file, 'w') as f:
    f.write(new_symbols)


def main():
  parser = argparse.ArgumentParser(description=__doc__, usage="usage: %prog [options] [<file>...]")
  parser.add_argument('--asmjs', dest='asmjs', action='store_true',
                      help='Use asmjs library files')
  parser.add_argument('-i', dest='filter_in_place', action='store_true',
                      help='filter symbols in place rather than re-generating from library file')
  parser.add_argument('files', metavar='N', type=int, nargs='*',
                      help='symbol files to regenerate (default: all)')
  args = parser.parse_args()
  if args.asmjs:
    shared.Settings.WASM_BACKEND = False
    shared.Settings.ASM_JS = True

  if args.files:
    for f in args.files:
      handle_symbol_file(args, f)
  else:
    symbols_dir = os.path.join(root_dir, 'system', 'lib')
    for symbol_file in glob.glob(os.path.join(symbols_dir, '*.symbols')):
      if shared.Settings.WASM_BACKEND:
        if symbol_file.endswith('_asmjs.symbols'):
          print('skipping asmjs-only file: %s' % symbol_file)
          continue
      else:
        asmjs_only_file = os.path.splitext(symbol_file)[0] + '_asmjs.symbols'
        if os.path.exists(asmjs_only_file):
          print('skipping wasm-only file: %s' % symbol_file)
          continue
      handle_symbol_file(args, symbol_file)

  return 0


if __name__ == '__main__':
  sys.exit(main())
