Skip to content

Commit

Permalink
Merge "Add a couple scripts to fixup version scripts."
Browse files Browse the repository at this point in the history
  • Loading branch information
Treehugger Robot authored and Gerrit Code Review committed Sep 10, 2016
2 parents d9eabd8 + a88921b commit 87d9b0a
Show file tree
Hide file tree
Showing 2 changed files with 342 additions and 0 deletions.
150 changes: 150 additions & 0 deletions ndk/build_symbol_db.py
Original file line number Diff line number Diff line change
@@ -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()
192 changes: 192 additions & 0 deletions ndk/fixup_version_script.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 87d9b0a

Please sign in to comment.