diff --git a/ndk/build_symbol_db.py b/ndk/build_symbol_db.py new file mode 100755 index 00000000000..e01ff184c50 --- /dev/null +++ b/ndk/build_symbol_db.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Builds a database of symbol version introductions.""" +import argparse +import json +import logging +import os + + +THIS_DIR = os.path.realpath(os.path.dirname(__file__)) + + +ALL_ARCHITECTURES = ( + 'arm', + 'arm64', + 'mips', + 'mips64', + 'x86', + 'x86_64', +) + + +def logger(): + """Returns the default logger for this module.""" + return logging.getLogger(__name__) + + +def get_platform_versions(): + """Returns a list of the platform versions we have data for.""" + versions = [] + platforms_dir = os.path.join(THIS_DIR, 'platforms') + logger().debug('Getting platform versions from %s', platforms_dir) + for name in os.listdir(platforms_dir): + if name.startswith('android-'): + versions.append(int(name.split('-')[1])) + return versions + + +def add_symbols(symbols, symbol_file_path, version, arch, is_var): + """Adds symbols from a file to the symbol dict.""" + with open(symbol_file_path) as symbol_file: + names = symbol_file.readlines() + + for name in names: + name = name.strip() + if not name: + continue + introduced_tag = 'introduced-' + arch + if name in symbols: + assert symbols[name]['is_var'] == is_var + if introduced_tag in symbols[name]: + continue + symbols[name][introduced_tag] = version + else: + symbols[name] = {} + symbols[name]['is_var'] = is_var + symbols[name][introduced_tag] = version + + +def build_symbol_db(lib_name): + """Returns a dict of symbols and their version information. + + Args: + lib_name: Name of the library to return file mapping for. + + Returns: dict of symbol information in the following format: + { + "symbol_name": { + "is_var": "true", + "introduced-arm": 9, + "introduced-x86": 14, + "introduced-mips": 16, + "introduced-arm64": 21, + "introduced-mips64": 21, + "introduced-x86_64": 21, + }, + ... + } + """ + symbols = {} + versions = sorted(get_platform_versions()) + for version in versions: + for arch in ALL_ARCHITECTURES: + symbols_dir = os.path.join( + THIS_DIR, 'platforms', 'android-' + str(version), + 'arch-' + arch, 'symbols') + if not os.path.exists(symbols_dir): + logger().debug('Skipping non-existent %s', symbols_dir) + continue + + logger().info('Processing android-%d arch-%s', version, arch) + + funcs_file_name = lib_name + '.so.functions.txt' + funcs_file = os.path.join(symbols_dir, funcs_file_name) + if os.path.exists(funcs_file): + add_symbols(symbols, funcs_file, version, arch, is_var='false') + + vars_file_name = lib_name + '.so.variables.txt' + vars_file = os.path.join(symbols_dir, vars_file_name) + if os.path.exists(funcs_file): + add_symbols(symbols, vars_file, version, arch, is_var='true') + return symbols + + +def parse_args(): + """Returns parsed command line arguments.""" + parser = argparse.ArgumentParser() + + parser.add_argument('-v', '--verbose', action='count', default=0) + + parser.add_argument( + 'library_name', metavar='LIBRARY_NAME', + help='Name of the library to create a database for.') + + return parser.parse_args() + + +def main(): + """Program entry point.""" + args = parse_args() + os.chdir(THIS_DIR) + + verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) + verbosity = args.verbose + if verbosity > 2: + verbosity = 2 + logging.basicConfig(level=verbose_map[verbosity]) + + symbol_db = build_symbol_db(args.library_name) + with open(args.library_name + '.so.json', 'w') as db_file: + json.dump(symbol_db, db_file, indent=4, separators=(',', ': '), + sort_keys=True) + + +if __name__ == '__main__': + main() diff --git a/ndk/fixup_version_script.py b/ndk/fixup_version_script.py new file mode 100755 index 00000000000..87f10847687 --- /dev/null +++ b/ndk/fixup_version_script.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Annotates an existing version script with data for the NDK.""" +import argparse +import json +import logging +import os +import sys + + +ALL_ARCHITECTURES = ( + 'arm', + 'arm64', + 'mips', + 'mips64', + 'x86', + 'x86_64', +) + + +def logger(): + """Returns the default logger for this module.""" + return logging.getLogger(__name__) + + +def verify_version_script(lines, json_db): + """Checks that every symbol in the NDK is in the version script.""" + symbols = dict(json_db) + for line in lines: + if ';' in line: + name, _ = line.split(';') + name = name.strip() + + if name in symbols: + del symbols[name] + if len(symbols) > 0: + for symbol in symbols.keys(): + logger().error( + 'NDK symbol not present in version script: {}'.format(symbol)) + sys.exit(1) + + +def was_always_present(db_entry, arches): + """Returns whether the symbol has always been present or not.""" + for arch in arches: + is_64 = arch.endswith('64') + introduced_tag = 'introduced-' + arch + if introduced_tag not in db_entry: + return False + if is_64 and db_entry[introduced_tag] != 21: + return False + elif not is_64 and db_entry[introduced_tag] != 9: + return False + # Else we have the symbol in this arch and was introduced in the first + # version of it. + return True + + +def get_common_introduced(db_entry, arches): + """Returns the common introduction API level or None. + + If the symbol was introduced in the same API level for all architectures, + return that API level. If the symbol is not present in all architectures or + was introduced to them at different times, return None. + """ + introduced = None + for arch in arches: + introduced_tag = 'introduced-' + arch + if introduced_tag not in db_entry: + return None + if introduced is None: + introduced = db_entry[introduced_tag] + elif db_entry[introduced_tag] != introduced: + return None + # Else we have the symbol in this arch and it's the same introduction + # level. Keep going. + return introduced + + +def annotate_symbol(line, json_db): + """Returns the line with NDK data appended.""" + name_part, rest = line.split(';') + name = name_part.strip() + if name not in json_db: + return line + + rest = rest.rstrip() + tags = [] + db_entry = json_db[name] + if db_entry['is_var'] == 'true': + tags.append('var') + + arches = ALL_ARCHITECTURES + if '#' in rest: + had_tags = True + # Current tags aren't necessarily arch tags. Check them before using + # them. + _, old_tags = rest.split('#') + arch_tags = [] + for tag in old_tags.strip().split(' '): + if tag in ALL_ARCHITECTURES: + arch_tags.append(tag) + if len(arch_tags) > 0: + arches = arch_tags + else: + had_tags = False + + always_present = was_always_present(db_entry, arches) + common_introduced = get_common_introduced(db_entry, arches) + if always_present: + # No need to tag things that have always been there. + pass + elif common_introduced is not None: + tags.append('introduced={}'.format(common_introduced)) + else: + for arch in ALL_ARCHITECTURES: + introduced_tag = 'introduced-' + arch + if introduced_tag not in db_entry: + continue + tags.append( + '{}={}'.format(introduced_tag, db_entry[introduced_tag])) + + if tags: + if not had_tags: + rest += ' #' + rest += ' ' + ' '.join(tags) + return name_part + ';' + rest + '\n' + + +def annotate_version_script(version_script, json_db, lines): + """Rewrites a version script with NDK annotations.""" + for line in lines: + # Lines contain a semicolon iff they contain a symbol name. + if ';' in line: + version_script.write(annotate_symbol(line, json_db)) + else: + version_script.write(line) + + +def parse_args(): + """Returns parsed command line arguments.""" + parser = argparse.ArgumentParser() + + parser.add_argument( + 'data_file', metavar='DATA_FILE', type=os.path.realpath, + help='Path to JSON DB generated by build_symbol_db.py.') + + parser.add_argument( + 'version_script', metavar='VERSION_SCRIPT', type=os.path.realpath, + help='Version script to be annotated.') + + parser.add_argument('-v', '--verbose', action='count', default=0) + + return parser.parse_args() + + +def main(): + """Program entry point.""" + args = parse_args() + + verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) + verbosity = args.verbose + if verbosity > 2: + verbosity = 2 + + logging.basicConfig(level=verbose_map[verbosity]) + with open(args.data_file) as json_db_file: + json_db = json.load(json_db_file) + + with open(args.version_script, 'r') as version_script: + file_data = version_script.readlines() + verify_version_script(file_data, json_db) + with open(args.version_script, 'w') as version_script: + annotate_version_script(version_script, json_db, file_data) + + +if __name__ == '__main__': + main()