Po-Chien Hsueh | f86af51 | 2017-01-20 17:02:57 +0800 | [diff] [blame] | 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 | |