blob: c74381388f80fd03ead2d450a04dd9aa3b18f014 [file] [log] [blame]
#!/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())