Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/env python3
      2 
      3 import argparse
      4 import fnmatch
      5 import json
      6 import os
      7 import re
      8 import sys
      9 
     10 HELP_MSG = '''
     11 This script analyses the usage of build-time variables which are defined in BoardConfig*.mk
     12 and used by framework modules (installed in system.img). Please 'lunch' and 'make' before
     13 running it.
     14 '''
     15 
     16 TOP = os.environ.get('ANDROID_BUILD_TOP')
     17 OUT = os.environ.get('OUT')
     18 
     19 white_list = [
     20     'TARGET_ARCH',
     21     'TARGET_ARCH_VARIANT',
     22     'TARGET_CPU_VARIANT',
     23     'TARGET_CPU_ABI',
     24     'TARGET_CPU_ABI2',
     25 
     26     'TARGET_2ND_ARCH',
     27     'TARGET_2ND_ARCH_VARIANT',
     28     'TARGET_2ND_CPU_VARIANT',
     29     'TARGET_2ND_CPU_ABI',
     30     'TARGET_2ND_CPU_ABI2',
     31 
     32     'TARGET_NO_BOOTLOADER',
     33     'TARGET_NO_KERNEL',
     34     'TARGET_NO_RADIOIMAGE',
     35     'TARGET_NO_RECOVERY',
     36 
     37     'TARGET_BOARD_PLATFORM',
     38 
     39     'ARCH_ARM_HAVE_ARMV7A',
     40     'ARCH_ARM_HAVE_NEON',
     41     'ARCH_ARM_HAVE_VFP',
     42     'ARCH_ARM_HAVE_VFP_D32',
     43 
     44     'BUILD_NUMBER'
     45 ]
     46 
     47 
     48 # used by find_board_configs_mks() and find_makefiles()
     49 def find_files(folders, filter):
     50   ret = []
     51 
     52   for folder in folders:
     53     for root, dirs, files in os.walk(os.path.join(TOP, folder), topdown=True):
     54       dirs[:] = [d for d in dirs if not d[0] == '.']
     55       for file in files:
     56         if filter(file):
     57           ret.append(os.path.join(root, file))
     58 
     59   return ret
     60 
     61 # find board configs (BoardConfig*.mk)
     62 def find_board_config_mks(folders = ['build', 'device', 'vendor', 'hardware']):
     63   return find_files(folders, lambda x:
     64                     fnmatch.fnmatch(x, 'BoardConfig*.mk'))
     65 
     66 # find makefiles (*.mk or Makefile) under specific folders
     67 def find_makefiles(folders = ['system', 'frameworks', 'external']):
     68   return find_files(folders, lambda x:
     69                     fnmatch.fnmatch(x, '*.mk') or fnmatch.fnmatch(x, 'Makefile'))
     70 
     71 # read module-info.json and find makefiles of modules in system image
     72 def find_system_module_makefiles():
     73   makefiles = []
     74   out_system_path = os.path.join(OUT[len(TOP) + 1:], 'system')
     75 
     76   with open(os.path.join(OUT, 'module-info.json')) as module_info_json:
     77     module_info = json.load(module_info_json)
     78     for module in module_info:
     79       installs = module_info[module]['installed']
     80       paths = module_info[module]['path']
     81 
     82       installed_in_system = False
     83 
     84       for install in installs:
     85         if install.startswith(out_system_path):
     86           installed_in_system = True
     87           break
     88 
     89       if installed_in_system:
     90         for path in paths:
     91           makefile = os.path.join(TOP, path, 'Android.mk')
     92           makefiles.append(makefile)
     93 
     94   return makefiles
     95 
     96 # find variables defined in board_config_mks
     97 def find_defined_variables(board_config_mks):
     98   re_def = re.compile('^[\s]*([\w\d_]*)[\s]*:=')
     99   variables = dict()
    100 
    101   for board_config_mk in board_config_mks:
    102     for line in open(board_config_mk, encoding='latin1'):
    103       mo = re_def.search(line)
    104       if mo is None:
    105         continue
    106 
    107       variable = mo.group(1)
    108       if variable in white_list:
    109         continue
    110 
    111       if variable not in variables:
    112         variables[variable] = set()
    113 
    114       variables[variable].add(board_config_mk[len(TOP) + 1:])
    115 
    116   return variables
    117 
    118 # count variable usage in makefiles
    119 def find_usage(variable, makefiles):
    120   re_usage = re.compile('\$\(' + variable + '\)')
    121   usage = set()
    122 
    123   for makefile in makefiles:
    124     if not os.path.isfile(makefile):
    125       # TODO: support bp
    126       continue
    127 
    128     with open(makefile, encoding='latin1') as mk_file:
    129       mk_str = mk_file.read()
    130 
    131     if re_usage.search(mk_str) is not None:
    132       usage.add(makefile[len(TOP) + 1:])
    133 
    134   return usage
    135 
    136 def main():
    137   parser = argparse.ArgumentParser(description=HELP_MSG)
    138   parser.add_argument("-v", "--verbose",
    139                       help="print definition and usage locations",
    140                       action="store_true")
    141   args = parser.parse_args()
    142 
    143   print('TOP : ' + TOP)
    144   print('OUT : ' + OUT)
    145   print()
    146 
    147   sfe_makefiles = find_makefiles()
    148   system_module_makefiles = find_system_module_makefiles()
    149   board_config_mks = find_board_config_mks()
    150   variables = find_defined_variables(board_config_mks)
    151 
    152   if args.verbose:
    153     print('sfe_makefiles', len(sfe_makefiles))
    154     print('system_module_makefiles', len(system_module_makefiles))
    155     print('board_config_mks', len(board_config_mks))
    156     print('variables', len(variables))
    157     print()
    158 
    159   glossary = (
    160       '*Output in CSV format\n\n'
    161 
    162       '*definition count      :'
    163       ' This variable is defined in how many BoardConfig*.mk\'s\n'
    164 
    165       '*usage in SFE          :'
    166       ' This variable is used by how many makefiles under system/, frameworks/ and external/ folders\n'
    167 
    168       '*usage in system image :'
    169       ' This variable is used by how many system image modules\n')
    170 
    171   csv_string = (
    172       'variable name,definition count,usage in SFE,usage in system image\n')
    173 
    174   for variable, locations in sorted(variables.items()):
    175     usage_in_sfe = find_usage(variable, sfe_makefiles)
    176     usage_of_system_modules = find_usage(variable, system_module_makefiles)
    177     usage = usage_in_sfe | usage_of_system_modules
    178 
    179     if len(usage) == 0:
    180       continue
    181 
    182     csv_string += ','.join([variable,
    183                             str(len(locations)),
    184                             str(len(usage_in_sfe)),
    185                             str(len(usage_of_system_modules))]) + '\n'
    186 
    187     if args.verbose:
    188       print((variable + ' ').ljust(80, '='))
    189 
    190       print('Defined in (' + str(len(locations)) + ') :')
    191       for location in sorted(locations):
    192         print('  ' + location)
    193 
    194       print('Used in (' + str(len(usage)) + ') :')
    195       for location in sorted(usage):
    196         print('  ' + location)
    197 
    198       print()
    199 
    200   if args.verbose:
    201     print('\n')
    202 
    203   print(glossary)
    204   print(csv_string)
    205 
    206 if __name__ == '__main__':
    207   if TOP is None:
    208     sys.exit('$ANDROID_BUILD_TOP is undefined, please lunch and make before running this script')
    209 
    210   if OUT is None:
    211     sys.exit('$OUT is undefined, please lunch and make before running this script')
    212 
    213   if not os.path.isfile(os.path.join(OUT, 'module-info.json')):
    214     sys.exit('module-info.json is missing, please lunch and make before running this script')
    215 
    216   main()
    217 
    218