| #!/usr/bin/env python3 |
| # 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 sys |
| import argparse |
| import os |
| import filecmp |
| |
| root_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) |
| sys.path.insert(0, root_dir) |
| symbols_base_dir = os.path.join(root_dir, 'system', 'lib', 'symbols') |
| asmjs_symbols_dir = os.path.join(symbols_base_dir, 'asmjs') |
| wasm_symbols_dir = os.path.join(symbols_base_dir, 'wasm') |
| |
| from tools import shared, cache |
| from tools.system_libs import Library |
| |
| # Libraries that need .symbols file |
| target_libs = ['libal', 'libc', 'libc-extras', 'libcompiler_rt', 'libc-wasm', |
| 'libc++', 'libc++abi', 'libgl', 'libhtml5', 'libpthreads'] |
| |
| |
| def get_symbols_dir(): |
| if shared.Settings.WASM_BACKEND: |
| return wasm_symbols_dir |
| else: |
| return asmjs_symbols_dir |
| |
| |
| def is_symbol_file_supported(symbol_file): |
| if shared.Settings.WASM_BACKEND: |
| return os.path.abspath(symbol_file).startswith(wasm_symbols_dir) |
| else: |
| return os.path.abspath(symbol_file).startswith(asmjs_symbols_dir) |
| |
| |
| # Given a symbol file name, returns a matching library file name. |
| def get_lib_file(symbol_file): |
| basename = os.path.splitext(os.path.basename(symbol_file))[0] |
| cache_dir = cache.Cache().dirname |
| lib_extension = 'a' if shared.Settings.WASM_BACKEND else 'bc' |
| return os.path.join(cache_dir, basename + '.' + lib_extension) |
| |
| |
| # Given a library file name, returns a matching symbols file name. |
| def get_symbol_file(lib_file): |
| basename = os.path.splitext(os.path.basename(lib_file))[0] |
| return os.path.join(get_symbols_dir(), basename + '.symbols') |
| |
| |
| 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 generate_symbol_file(symbol_file, lib_file): |
| """Regenerate the contents of a given symbol file.""" |
| output = shared.run_process([shared.LLVM_NM, '-g', lib_file], |
| 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="%(prog)s [options] [files ...]") |
| parser.add_argument('files', metavar='files', type=str, nargs='*', |
| help='symbol files to regenerate (default: all)') |
| args = parser.parse_args() |
| |
| if not shared.Settings.WASM: |
| sys.stderr.write('This script only runs in WASM mode\n') |
| sys.exit(1) |
| |
| shared.safe_ensure_dirs(get_symbols_dir()) |
| if args.files: |
| for symbol_file in args.files: |
| if not is_symbol_file_supported(symbol_file): |
| print('skipping %s because it is not supported' % symbol_file) |
| continue |
| lib_file = get_lib_file(symbol_file) |
| if not os.path.exists(lib_file): |
| print('skipping %s because %s does not exist' % (symbol_file, lib_file)) |
| continue |
| generate_symbol_file(symbol_file, lib_file) |
| else: |
| # Build all combinations of libraries and generate symbols files |
| system_libs = Library.get_all_variations() |
| for lib in system_libs.values(): |
| if lib.name not in target_libs: |
| continue |
| lib_file = lib.get_path() |
| symbol_file = get_symbol_file(lib_file) |
| generate_symbol_file(symbol_file, lib_file) |
| |
| # Not to generate too many symbols files with the same contents, if there |
| # exists a default symbols file (that has a library name without any |
| # suffices, such as -mt) and its contents are the same as another symbols |
| # file with suffices, remove it. |
| for lib in system_libs.values(): |
| if lib.name not in target_libs: |
| continue |
| lib_file = lib.get_path() |
| symbol_file = get_symbol_file(lib_file) |
| default_symbol_file = os.path.join(get_symbols_dir(), |
| lib.name + '.symbols') |
| if symbol_file != default_symbol_file and \ |
| os.path.isfile(default_symbol_file) and \ |
| filecmp.cmp(default_symbol_file, symbol_file): |
| os.unlink(symbol_file) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |