1#!/usr/bin/env python 2# 3# Copyright (C) 2016 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17"""Annotates an existing version script with data for the NDK.""" 18import argparse 19import collections 20import json 21import logging 22import os 23import sys 24 25 26ALL_ARCHITECTURES = ( 27 'arm', 28 'arm64', 29 'mips', 30 'mips64', 31 'x86', 32 'x86_64', 33) 34 35 36def logger(): 37 """Returns the default logger for this module.""" 38 return logging.getLogger(__name__) 39 40 41def verify_version_script(lines, json_db): 42 """Checks that every symbol in the NDK is in the version script.""" 43 symbols = dict(json_db) 44 for line in lines: 45 if ';' in line: 46 name, _ = line.split(';') 47 name = name.strip() 48 49 if name in symbols: 50 del symbols[name] 51 if len(symbols) > 0: 52 for symbol in symbols.keys(): 53 logger().error( 54 'NDK symbol not present in version script: {}'.format(symbol)) 55 sys.exit(1) 56 57 58def was_always_present(db_entry, arches): 59 """Returns whether the symbol has always been present or not.""" 60 for arch in arches: 61 is_64 = arch.endswith('64') 62 introduced_tag = 'introduced-' + arch 63 if introduced_tag not in db_entry: 64 return False 65 if is_64 and db_entry[introduced_tag] != 21: 66 return False 67 elif not is_64 and db_entry[introduced_tag] != 9: 68 return False 69 # Else we have the symbol in this arch and was introduced in the first 70 # version of it. 71 return True 72 73 74def get_common_introduced(db_entry, arches): 75 """Returns the common introduction API level or None. 76 77 If the symbol was introduced in the same API level for all architectures, 78 return that API level. If the symbol is not present in all architectures or 79 was introduced to them at different times, return None. 80 """ 81 introduced = None 82 for arch in arches: 83 introduced_tag = 'introduced-' + arch 84 if introduced_tag not in db_entry: 85 return None 86 if introduced is None: 87 introduced = db_entry[introduced_tag] 88 elif db_entry[introduced_tag] != introduced: 89 return None 90 # Else we have the symbol in this arch and it's the same introduction 91 # level. Keep going. 92 return introduced 93 94 95def annotate_symbol(line, json_db): 96 """Returns the line with NDK data appended.""" 97 name_part, rest = line.split(';') 98 name = name_part.strip() 99 if name not in json_db: 100 return line 101 102 rest = rest.rstrip() 103 tags = [] 104 db_entry = json_db[name] 105 if db_entry['is_var'] == 'true': 106 tags.append('var') 107 108 arches = ALL_ARCHITECTURES 109 if '#' in rest: 110 had_tags = True 111 # Current tags aren't necessarily arch tags. Check them before using 112 # them. 113 _, old_tags = rest.split('#') 114 arch_tags = [] 115 for tag in old_tags.strip().split(' '): 116 if tag in ALL_ARCHITECTURES: 117 arch_tags.append(tag) 118 if len(arch_tags) > 0: 119 arches = arch_tags 120 else: 121 had_tags = False 122 123 always_present = was_always_present(db_entry, arches) 124 common_introduced = get_common_introduced(db_entry, arches) 125 if always_present: 126 # No need to tag things that have always been there. 127 pass 128 elif common_introduced is not None: 129 tags.append('introduced={}'.format(common_introduced)) 130 else: 131 for arch in ALL_ARCHITECTURES: 132 introduced_tag = 'introduced-' + arch 133 if introduced_tag not in db_entry: 134 continue 135 tags.append( 136 '{}={}'.format(introduced_tag, db_entry[introduced_tag])) 137 138 if tags: 139 if not had_tags: 140 rest += ' #' 141 rest += ' ' + ' '.join(tags) 142 return name_part + ';' + rest + '\n' 143 144 145def annotate_version_script(version_script, json_db, lines): 146 """Rewrites a version script with NDK annotations.""" 147 for line in lines: 148 # Lines contain a semicolon iff they contain a symbol name. 149 if ';' in line: 150 version_script.write(annotate_symbol(line, json_db)) 151 else: 152 version_script.write(line) 153 154 155def create_version_script(version_script, json_db): 156 """Creates a new version script based on an NDK library definition.""" 157 json_db = collections.OrderedDict(sorted(json_db.items())) 158 159 version_script.write('LIB {\n') 160 version_script.write(' global:\n') 161 for symbol in json_db.keys(): 162 line = annotate_symbol(' {};\n'.format(symbol), json_db) 163 version_script.write(line) 164 version_script.write(' local:\n') 165 version_script.write(' *;\n') 166 version_script.write('};') 167 168 169def parse_args(): 170 """Returns parsed command line arguments.""" 171 parser = argparse.ArgumentParser() 172 173 parser.add_argument( 174 '--create', action='store_true', 175 help='Create a new version script instead of annotating.') 176 177 parser.add_argument( 178 'data_file', metavar='DATA_FILE', type=os.path.realpath, 179 help='Path to JSON DB generated by build_symbol_db.py.') 180 181 parser.add_argument( 182 'version_script', metavar='VERSION_SCRIPT', type=os.path.realpath, 183 help='Version script to be annotated.') 184 185 parser.add_argument('-v', '--verbose', action='count', default=0) 186 187 return parser.parse_args() 188 189 190def main(): 191 """Program entry point.""" 192 args = parse_args() 193 194 verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) 195 verbosity = args.verbose 196 if verbosity > 2: 197 verbosity = 2 198 199 logging.basicConfig(level=verbose_map[verbosity]) 200 with open(args.data_file) as json_db_file: 201 json_db = json.load(json_db_file) 202 203 if args.create: 204 with open(args.version_script, 'w') as version_script: 205 create_version_script(version_script, json_db) 206 else: 207 with open(args.version_script, 'r') as version_script: 208 file_data = version_script.readlines() 209 verify_version_script(file_data, json_db) 210 with open(args.version_script, 'w') as version_script: 211 annotate_version_script(version_script, json_db, file_data) 212 213 214if __name__ == '__main__': 215 main() 216