summaryrefslogtreecommitdiffstats
path: root/scripts/boardconfig_usage_analysis.py
blob: 019af6a04184b061a93a42a0ea1651664964c16d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#!/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()