1#!/usr/bin/python3
2
3"""
4List VTS tests for each HAL by parsing module-info.json.
5
6Example usage:
7
8  # First, build modules-info.json
9  m -j "${ANDROID_PRODUCT_OUT#$ANDROID_BUILD_TOP/}/module-info.json"
10
11  # List with pretty-printed JSON. *IDL packages without a VTS module will show up
12  # as keys with empty lists.
13  ./list_hals_vts.py | python3 -m json.tool
14
15  # List with CSV. *IDL packages without a VTS module will show up as a line with
16  # empty value in the VTS module column.
17  ./list_hals_vts.py --csv
18"""
19
20import argparse
21import collections
22import csv
23import io
24import json
25import os
26import logging
27import pathlib
28import re
29import sys
30
31PATH_PACKAGE_PATTERN = re.compile(
32  r'^hardware/interfaces/(?P<path>(?:\w+/)*?)(?:aidl|(?P<version>\d+\.\d+))/.*')
33
34
35class CriticalHandler(logging.StreamHandler):
36  def emit(self, record):
37    super(CriticalHandler, self).emit(record)
38    if record.levelno >= logging.CRITICAL:
39      sys.exit(1)
40
41
42logger = logging.getLogger(__name__)
43logger.addHandler(CriticalHandler())
44
45
46def default_json():
47  out = os.environ.get('ANDROID_PRODUCT_OUT')
48  if not out: return None
49  return os.path.join(out, 'module-info.json')
50
51
52def infer_package(path):
53  """
54  Infer package from a relative path from build top where a VTS module lives.
55
56  :param path: a path like 'hardware/interfaces/vibrator/aidl/vts'
57  :return: The inferred *IDL package, e.g. 'android.hardware.vibrator'
58
59  >>> infer_package('hardware/interfaces/automotive/sv/1.0/vts/functional')
60  'android.hardware.automotive.sv@1.0'
61  >>> infer_package('hardware/interfaces/vibrator/aidl/vts')
62  'android.hardware.vibrator'
63  """
64  mo = re.match(PATH_PACKAGE_PATTERN, path)
65  if not mo: return None
66  package = 'android.hardware.' + ('.'.join(pathlib.Path(mo.group('path')).parts))
67  if mo.group('version'):
68    package += '@' + mo.group('version')
69  return package
70
71
72def load_modules_info(json_file):
73  """
74  :param json_file: The path to modules-info.json
75  :return: a dictionary, where the keys are inferred *IDL package names, and
76           values are a list of VTS modules with that inferred package name.
77  """
78  with open(json_file) as fp:
79    root = json.load(fp)
80    ret = collections.defaultdict(list)
81    for module_name, module_info in root.items():
82      if 'vts' not in module_info.get('compatibility_suites', []):
83        continue
84      for path in module_info.get('path', []):
85        inferred_package = infer_package(path)
86        if not inferred_package:
87          continue
88        ret[inferred_package].append(module_name)
89    return ret
90
91
92def add_missing_idl(vts_modules):
93  top = os.environ.get("ANDROID_BUILD_TOP")
94  interfaces = None
95  if top:
96    interfaces = os.path.join(top, "hardware", "interfaces")
97  else:
98    logger.warning("Missing ANDROID_BUILD_TOP")
99    interfaces = "hardware/interfaces"
100  if not os.path.isdir(interfaces):
101    logger.error("Not adding missing *IDL modules because missing hardware/interfaces dir")
102    return
103  assert not interfaces.endswith(os.path.sep)
104  for root, dirs, files in os.walk(interfaces):
105    for dir in dirs:
106      full_dir = os.path.join(root, dir)
107      assert full_dir.startswith(interfaces)
108      top_rel_dir = os.path.join('hardware', 'interfaces', full_dir[len(interfaces) + 1:])
109      inferred_package = infer_package(top_rel_dir)
110      if inferred_package is None:
111        continue
112      if inferred_package not in vts_modules:
113        vts_modules[inferred_package] = []
114
115
116def main():
117  parser = argparse.ArgumentParser(__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
118  parser.add_argument('json', metavar='module-info.json', default=default_json(), nargs='?')
119  parser.add_argument('--csv', action='store_true', help='Print CSV. If not specified print JSON.')
120  args = parser.parse_args()
121  if not args.json:
122    logger.critical('No module-info.json is specified or found.')
123  vts_modules = load_modules_info(args.json)
124  add_missing_idl(vts_modules)
125
126  if args.csv:
127    out = io.StringIO()
128    writer = csv.writer(out, )
129    writer.writerow(["package", "vts_module"])
130    for package, modules in vts_modules.items():
131      if not modules:
132        writer.writerow([package, ""])
133      for module in modules:
134        writer.writerow([package, module])
135    result = out.getvalue()
136  else:
137    result = json.dumps(vts_modules)
138
139  print(result)
140
141
142if __name__ == '__main__':
143  main()
144