Home | History | Annotate | Download | only in ndk
      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."""
     18 import argparse
     19 import collections
     20 import json
     21 import logging
     22 import os
     23 import sys
     24 
     25 
     26 ALL_ARCHITECTURES = (
     27     'arm',
     28     'arm64',
     29     'mips',
     30     'mips64',
     31     'x86',
     32     'x86_64',
     33 )
     34 
     35 
     36 def logger():
     37     """Returns the default logger for this module."""
     38     return logging.getLogger(__name__)
     39 
     40 
     41 def 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 
     58 def 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 
     74 def 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 
     95 def 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 
    145 def 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 
    155 def 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 
    169 def 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 
    190 def 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 
    214 if __name__ == '__main__':
    215     main()
    216