Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python3
      2 
      3 # This tool updates extracts the information from Android.bp and updates the
      4 # datasets for eligible VNDK libraries.
      5 
      6 import argparse
      7 import collections
      8 import csv
      9 import json
     10 import os.path
     11 import posixpath
     12 import re
     13 import sys
     14 
     15 def load_make_vars(path):
     16     result = collections.OrderedDict([
     17         ('SOONG_LLNDK_LIBRARIES', set()),
     18         ('SOONG_VNDK_SAMEPROCESS_LIBRARIES', set()),
     19         ('SOONG_VNDK_CORE_LIBRARIES', set()),
     20         ('SOONG_VNDK_PRIVATE_LIBRARIES', set()),
     21     ])
     22 
     23     assign_len = len(' := ')
     24 
     25     with open(path, 'r') as fp:
     26         for line in fp:
     27             for key, value in result.items():
     28                 if line.startswith(key):
     29                     mod_names = line[len(key) + assign_len:].strip().split(' ')
     30                     value.update(mod_names)
     31 
     32     return result.values()
     33 
     34 def load_install_paths(module_info_path):
     35     with open(module_info_path, 'r') as fp:
     36         data = json.load(fp)
     37 
     38     result = set()
     39     name_path_dict = {}
     40     patt = re.compile(
     41             '.*[\\\\/]target[\\\\/]product[\\\\/][^\\\\/]+([\\\\/].*)$')
     42     for name, module in data.items():
     43         for path in module['installed']:
     44             match = patt.match(path)
     45             if not match:
     46                 continue
     47             path = match.group(1)
     48             path = path.replace(os.path.sep, '/')
     49             path = path.replace('/lib/', '/${LIB}/')
     50             path = path.replace('/lib64/', '/${LIB}/')
     51             path = re.sub('/vndk-sp(?:-[^/$]*)/', '/vndk-sp${VNDK_VER}/', path)
     52             path = re.sub('/vndk(?:-[^/$]*)/', '/vndk${VNDK_VER}/', path)
     53             result.add(path)
     54 
     55             if name.endswith('_32'):
     56                 name = name[0:-3]
     57 
     58             name_path_dict[name] = path
     59 
     60     return (result, name_path_dict)
     61 
     62 def _is_stale_module(path, installed_paths):
     63     if path in installed_paths:
     64         return False
     65     # libclang_rt.asan-${arch}-android and
     66     # libclang_rt.ubsan_standalone-${arch}-android may vary between different
     67     # architectures.
     68     if posixpath.basename(path).startswith('libclang_rt'):
     69         return False
     70     return True
     71 
     72 def remove_stale_modules(data, installed_paths):
     73     result = {}
     74     for path, row in data.items():
     75         if not _is_stale_module(path, installed_paths):
     76             result[path] = row
     77     return result
     78 
     79 def _parse_args():
     80     parser = argparse.ArgumentParser()
     81     parser.add_argument('tag_file')
     82     parser.add_argument('-o', '--output', required=True)
     83     parser.add_argument('--make-vars', required=True,
     84                         help='out/soong/make_vars-$(TARGET).mk')
     85     parser.add_argument('--module-info', required=True,
     86                         help='out/target/product/$(TARGET)/module-info.json')
     87     return parser.parse_args()
     88 
     89 def main():
     90     args = _parse_args()
     91 
     92     # Load libraries from `out/soong/make_vars-$(TARGET).mk`.
     93     llndk, vndk_sp, vndk, vndk_private = load_make_vars(args.make_vars)
     94 
     95     # Load eligible list csv file.
     96     with open(args.tag_file, 'r') as fp:
     97         reader = csv.reader(fp)
     98         header = next(reader)
     99         data = dict()
    100         regex_patterns = []
    101         for path, tag, comments in reader:
    102             if path.startswith('[regex]'):
    103                 regex_patterns.append([path, tag, comments])
    104             else:
    105                 data[path] = [path, tag, comments]
    106 
    107     # Delete non-existing libraries.
    108     installed_paths, name_path_dict = load_install_paths(args.module_info)
    109     data = remove_stale_modules(data, installed_paths)
    110 
    111     # Reset all /system/${LIB} libraries to FWK-ONLY.
    112     for path, row in data.items():
    113         if posixpath.dirname(path) == '/system/${LIB}':
    114             row[1] = 'FWK-ONLY'
    115 
    116     # Helper function to update the tag and the comments of an entry
    117     def update_tag(path, tag, comments=None):
    118         try:
    119             data[path][1] = tag
    120             if comments is not None:
    121                 data[path][2] = comments
    122         except KeyError:
    123             data[path] = [path, tag, comments if comments is not None else '']
    124 
    125     # Helper function to find the subdir and the module name
    126     def get_subdir_and_name(name, name_path_dict, prefix_core, prefix_vendor):
    127         try:
    128             path = name_path_dict[name + '.vendor']
    129             assert path.startswith(prefix_vendor)
    130             name_vendor = path[len(prefix_vendor):]
    131         except KeyError:
    132             name_vendor = name + '.so'
    133 
    134         try:
    135             path = name_path_dict[name]
    136             assert path.startswith(prefix_core)
    137             name_core = path[len(prefix_core):]
    138         except KeyError:
    139             name_core = name + '.so'
    140 
    141         assert name_core == name_vendor
    142         return name_core
    143 
    144     # Update LL-NDK tags
    145     prefix_core = '/system/${LIB}/'
    146     for name in llndk:
    147         try:
    148             path = name_path_dict[name]
    149             assert path.startswith(prefix_core)
    150             name = path[len(prefix_core):]
    151         except KeyError:
    152             name = name + '.so'
    153         update_tag('/system/${LIB}/' + name, 'LL-NDK')
    154 
    155     # Update VNDK-SP and VNDK-SP-Private tags
    156     prefix_core = '/system/${LIB}/'
    157     prefix_vendor = '/system/${LIB}/vndk-sp${VNDK_VER}/'
    158 
    159     for name in (vndk_sp - vndk_private):
    160         name = get_subdir_and_name(name, name_path_dict, prefix_core,
    161                                    prefix_vendor)
    162         update_tag(prefix_core + name, 'VNDK-SP')
    163         update_tag(prefix_vendor + name, 'VNDK-SP')
    164 
    165     for name in (vndk_sp & vndk_private):
    166         name = get_subdir_and_name(name, name_path_dict, prefix_core,
    167                                    prefix_vendor)
    168         update_tag(prefix_core + name, 'VNDK-SP-Private')
    169         update_tag(prefix_vendor + name, 'VNDK-SP-Private')
    170 
    171     # Update VNDK and VNDK-Private tags
    172     prefix_core = '/system/${LIB}/'
    173     prefix_vendor = '/system/${LIB}/vndk${VNDK_VER}/'
    174 
    175     for name in (vndk - vndk_private):
    176         name = get_subdir_and_name(name, name_path_dict, prefix_core,
    177                                    prefix_vendor)
    178         update_tag(prefix_core + name, 'VNDK')
    179         update_tag(prefix_vendor + name, 'VNDK')
    180 
    181     for name in (vndk & vndk_private):
    182         name = get_subdir_and_name(name, name_path_dict, prefix_core,
    183                                    prefix_vendor)
    184         update_tag(prefix_core + name, 'VNDK-Private')
    185         update_tag(prefix_vendor + name, 'VNDK-Private')
    186 
    187     # Workaround for FWK-ONLY-RS
    188     libs = [
    189         'libft2',
    190         'libmediandk',
    191     ]
    192     for name in libs:
    193         update_tag('/system/${LIB}/' + name + '.so', 'FWK-ONLY-RS')
    194 
    195     # Workaround for LL-NDK APEX bionic
    196     libs = [
    197         'libc',
    198         'libdl',
    199         'libm',
    200     ]
    201     for name in libs:
    202         update_tag('/apex/com.android.runtime/${LIB}/bionic/' + name + '.so',
    203                    'LL-NDK')
    204 
    205     # Workaround for LL-NDK-Private
    206     libs = [
    207         'ld-android',
    208         'libc_malloc_debug',
    209         'libnetd_client',
    210         'libtextclassifier_hash',
    211     ]
    212     for name in libs:
    213         update_tag('/system/${LIB}/' + name + '.so', 'LL-NDK-Private')
    214 
    215     # Workaround for extra VNDK-SP-Private.  The extra VNDK-SP-Private shared
    216     # libraries are VNDK-SP-Private when BOARD_VNDK_VERSION is set but are not
    217     # VNDK-SP-Private when BOARD_VNDK_VERSION is set.
    218     libs = [
    219         'libdexfile',
    220     ]
    221 
    222     prefix_core = '/system/${LIB}/'
    223     prefix_vendor = '/system/${LIB}/vndk-sp${VNDK_VER}/'
    224 
    225     for name in libs:
    226         assert name not in vndk_sp
    227         assert name not in vndk_private
    228         name = get_subdir_and_name(name, name_path_dict, prefix_core,
    229                                    prefix_vendor)
    230         update_tag(prefix_core + name, 'VNDK-SP-Private',
    231                    'Workaround for degenerated VDNK')
    232         update_tag(prefix_vendor + name, 'VNDK-SP-Private',
    233                    'Workaround for degenerated VDNK')
    234 
    235     # Workaround for libclang_rt.*.so
    236     lib_sets = {
    237         'LL-NDK': llndk,
    238         'VNDK': vndk,
    239     }
    240     prefixes = {
    241         'libclang_rt.asan': 'LL-NDK',
    242         'libclang_rt.hwasan': 'LL-NDK',
    243         'libclang_rt.scudo': 'VNDK',
    244         'libclang_rt.ubsan_standalone': 'VNDK',
    245     }
    246     for prefix, tag in prefixes.items():
    247         if any(name.startswith(prefix) for name in lib_sets[tag]):
    248             for path in list(data.keys()):
    249                 if os.path.basename(path).startswith(prefix):
    250                     update_tag(path, tag)
    251 
    252     # Merge regular expression patterns into final dataset
    253     for regex in regex_patterns:
    254         data[regex[0]] = regex
    255 
    256     # Write updated eligible list file
    257     with open(args.output, 'w') as fp:
    258         writer = csv.writer(fp, lineterminator='\n')
    259         writer.writerow(header)
    260         writer.writerows(sorted(data.values()))
    261 
    262 if __name__ == '__main__':
    263     sys.exit(main())
    264