blob: 09077ffcff2cdebb36e892ea197fecc31d28e274 [file] [log] [blame]
#!/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 collections
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 create_version_script(version_script, json_db):
"""Creates a new version script based on an NDK library definition."""
json_db = collections.OrderedDict(sorted(json_db.items()))
version_script.write('LIB {\n')
version_script.write(' global:\n')
for symbol in json_db.keys():
line = annotate_symbol(' {};\n'.format(symbol), json_db)
version_script.write(line)
version_script.write(' local:\n')
version_script.write(' *;\n')
version_script.write('};')
def parse_args():
"""Returns parsed command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
'--create', action='store_true',
help='Create a new version script instead of annotating.')
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)
if args.create:
with open(args.version_script, 'w') as version_script:
create_version_script(version_script, json_db)
else:
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()