blob: 25921b55e6a1728a64e67fcb5b5c116bc0a331c8 [file] [log] [blame] [edit]
#!/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, ignores undefined symbols (we just care about what is provided, not
what is required), 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', 'libpthread']
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')
existing_type = symbol_map.get(sym_name)
if not existing_type:
symbol_map[sym_name] = sym_type
continue
elif existing_type == 'W' and sym_type != 'W':
symbol_map[sym_name] = sym_type
elif sym_type != 'W':
# 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:
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, '-defined-only'],
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())