#!/usr/bin/env python3 import argparse import fnmatch import json import os import re import sys HELP_MSG = ''' This script analyses the usage of build-time variables which are defined in BoardConfig*.mk and used by framework modules (installed in system.img). Please 'lunch' and 'make' before running it. ''' TOP = os.environ.get('ANDROID_BUILD_TOP') OUT = os.environ.get('OUT') white_list = [ 'TARGET_ARCH', 'TARGET_ARCH_VARIANT', 'TARGET_CPU_VARIANT', 'TARGET_CPU_ABI', 'TARGET_CPU_ABI2', 'TARGET_2ND_ARCH', 'TARGET_2ND_ARCH_VARIANT', 'TARGET_2ND_CPU_VARIANT', 'TARGET_2ND_CPU_ABI', 'TARGET_2ND_CPU_ABI2', 'TARGET_NO_BOOTLOADER', 'TARGET_NO_KERNEL', 'TARGET_NO_RADIOIMAGE', 'TARGET_NO_RECOVERY', 'TARGET_BOARD_PLATFORM', 'ARCH_ARM_HAVE_ARMV7A', 'ARCH_ARM_HAVE_NEON', 'ARCH_ARM_HAVE_VFP', 'ARCH_ARM_HAVE_VFP_D32', 'BUILD_NUMBER' ] # used by find_board_configs_mks() and find_makefiles() def find_files(folders, filter): ret = [] for folder in folders: for root, dirs, files in os.walk(os.path.join(TOP, folder), topdown=True): dirs[:] = [d for d in dirs if not d[0] == '.'] for file in files: if filter(file): ret.append(os.path.join(root, file)) return ret # find board configs (BoardConfig*.mk) def find_board_config_mks(folders = ['build', 'device', 'vendor', 'hardware']): return find_files(folders, lambda x: fnmatch.fnmatch(x, 'BoardConfig*.mk')) # find makefiles (*.mk or Makefile) under specific folders def find_makefiles(folders = ['system', 'frameworks', 'external']): return find_files(folders, lambda x: fnmatch.fnmatch(x, '*.mk') or fnmatch.fnmatch(x, 'Makefile')) # read module-info.json and find makefiles of modules in system image def find_system_module_makefiles(): makefiles = [] out_system_path = os.path.join(OUT[len(TOP) + 1:], 'system') with open(os.path.join(OUT, 'module-info.json')) as module_info_json: module_info = json.load(module_info_json) for module in module_info: installs = module_info[module]['installed'] paths = module_info[module]['path'] installed_in_system = False for install in installs: if install.startswith(out_system_path): installed_in_system = True break if installed_in_system: for path in paths: makefile = os.path.join(TOP, path, 'Android.mk') makefiles.append(makefile) return makefiles # find variables defined in board_config_mks def find_defined_variables(board_config_mks): re_def = re.compile('^[\s]*([\w\d_]*)[\s]*:=') variables = dict() for board_config_mk in board_config_mks: for line in open(board_config_mk, encoding='latin1'): mo = re_def.search(line) if mo is None: continue variable = mo.group(1) if variable in white_list: continue if variable not in variables: variables[variable] = set() variables[variable].add(board_config_mk[len(TOP) + 1:]) return variables # count variable usage in makefiles def find_usage(variable, makefiles): re_usage = re.compile('\$\(' + variable + '\)') usage = set() for makefile in makefiles: if not os.path.isfile(makefile): # TODO: support bp continue with open(makefile, encoding='latin1') as mk_file: mk_str = mk_file.read() if re_usage.search(mk_str) is not None: usage.add(makefile[len(TOP) + 1:]) return usage def main(): parser = argparse.ArgumentParser(description=HELP_MSG) parser.add_argument("-v", "--verbose", help="print definition and usage locations", action="store_true") args = parser.parse_args() print('TOP : ' + TOP) print('OUT : ' + OUT) print() sfe_makefiles = find_makefiles() system_module_makefiles = find_system_module_makefiles() board_config_mks = find_board_config_mks() variables = find_defined_variables(board_config_mks) if args.verbose: print('sfe_makefiles', len(sfe_makefiles)) print('system_module_makefiles', len(system_module_makefiles)) print('board_config_mks', len(board_config_mks)) print('variables', len(variables)) print() glossary = ( '*Output in CSV format\n\n' '*definition count :' ' This variable is defined in how many BoardConfig*.mk\'s\n' '*usage in SFE :' ' This variable is used by how many makefiles under system/, frameworks/ and external/ folders\n' '*usage in system image :' ' This variable is used by how many system image modules\n') csv_string = ( 'variable name,definition count,usage in SFE,usage in system image\n') for variable, locations in sorted(variables.items()): usage_in_sfe = find_usage(variable, sfe_makefiles) usage_of_system_modules = find_usage(variable, system_module_makefiles) usage = usage_in_sfe | usage_of_system_modules if len(usage) == 0: continue csv_string += ','.join([variable, str(len(locations)), str(len(usage_in_sfe)), str(len(usage_of_system_modules))]) + '\n' if args.verbose: print((variable + ' ').ljust(80, '=')) print('Defined in (' + str(len(locations)) + ') :') for location in sorted(locations): print(' ' + location) print('Used in (' + str(len(usage)) + ') :') for location in sorted(usage): print(' ' + location) print() if args.verbose: print('\n') print(glossary) print(csv_string) if __name__ == '__main__': if TOP is None: sys.exit('$ANDROID_BUILD_TOP is undefined, please lunch and make before running this script') if OUT is None: sys.exit('$OUT is undefined, please lunch and make before running this script') if not os.path.isfile(os.path.join(OUT, 'module-info.json')): sys.exit('module-info.json is missing, please lunch and make before running this script') main()