1#!/usr/bin/env python3 2 3import argparse 4import fnmatch 5import json 6import os 7import re 8import sys 9 10HELP_MSG = ''' 11This script analyses the usage of build-time variables which are defined in BoardConfig*.mk 12and used by framework modules (installed in system.img). Please 'lunch' and 'make' before 13running it. 14''' 15 16TOP = os.environ.get('ANDROID_BUILD_TOP') 17OUT = os.environ.get('OUT') 18 19white_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() 49def 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) 62def 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 67def 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 72def 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 97def 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 119def 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 136def 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 206if __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