Home | History | Annotate | Download | only in prebuilt_hashes
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2018 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 #
     17 
     18 """
     19 Dump new HIDL types that are introduced in each dessert release.
     20 """
     21 
     22 from __future__ import print_function
     23 
     24 import argparse
     25 import collections
     26 import json
     27 import os
     28 import re
     29 
     30 class Globals:
     31     pass
     32 
     33 class Constants:
     34     CURRENT = 'current'
     35     HAL_PATH_PATTERN = r'/((?:[a-zA-Z_][a-zA-Z0-9_]*/)*)(\d+\.\d+)/([a-zA-Z_][a-zA-Z0-9_]*).hal'
     36     CURRENT_TXT_PATTERN = r'(?:.*/)?([0-9]+|current).txt'
     37 
     38 def trim_trailing_comments(line):
     39     idx = line.find('#')
     40     if idx < 0: return line
     41     return line[0:idx]
     42 
     43 def strip_begin(s, prefix):
     44     if s.startswith(prefix):
     45         return strip_begin(s[len(prefix):], prefix)
     46     return s
     47 
     48 def strip_end(s, suffix):
     49     if s.endswith(suffix):
     50         return strip_end(s[0:-len(suffix)], suffix)
     51     return s
     52 
     53 def get_interfaces(file_name):
     54     with open(file_name) as file:
     55         for line in file:
     56             line_tokens = trim_trailing_comments(line).strip().split()
     57             if not line_tokens:
     58                 continue
     59             assert len(line_tokens) == 2, \
     60                 "Unrecognized line in file {}:\n{}".format(file_name, line)
     61             yield line_tokens[1]
     62 
     63 def api_level_to_int(api_level):
     64     try:
     65         if api_level == Constants.CURRENT: return float('inf')
     66         return int(api_level)
     67     except ValueError:
     68         return None
     69 
     70 def get_interfaces_from_package_root(package, root):
     71     root = strip_end(root, '/')
     72     for dirpath, _, filenames in os.walk(root, topdown=False):
     73         dirpath = strip_begin(dirpath, root)
     74         for filename in filenames:
     75             filepath = os.path.join(dirpath, filename)
     76             mo = re.match(Constants.HAL_PATH_PATTERN, filepath)
     77             if not mo:
     78                 continue
     79             yield '{}.{}@{}::{}'.format(
     80                 package, mo.group(1).strip('/').replace('/', '.'), mo.group(2), mo.group(3))
     81 
     82 def filter_out(iterable):
     83     return iterable if not Globals.filter_out else filter(
     84         lambda s: all(re.search(pattern, s) is None for pattern in Globals.filter_out),
     85         iterable)
     86 
     87 def main():
     88     parser = argparse.ArgumentParser(description=__doc__)
     89     parser.add_argument('--pretty', help='Print pretty JSON', action='store_true')
     90     parser.add_argument('--package-root', metavar='PACKAGE:PATH', nargs='*',
     91         help='package root of current directory, e.g. android.hardware:hardware/interfaces')
     92     parser.add_argument('--filter-out', metavar='REGEX', nargs='*',
     93         help='A regular expression that filters out interfaces.')
     94     parser.add_argument('hashes', metavar='FILE', nargs='*',
     95         help='Locations of current.txt for each release.')
     96     parser.parse_args(namespace=Globals)
     97 
     98     interfaces_for_level = dict()
     99 
    100     for filename in Globals.hashes or tuple():
    101         mo = re.match(Constants.CURRENT_TXT_PATTERN, filename)
    102         assert mo is not None, \
    103             'Input hash file names must have the format {} but is {}'.format(Constants.CURRENT_TXT_PATTERN, filename)
    104 
    105         api_level = mo.group(1)
    106         assert api_level_to_int(api_level) is not None
    107 
    108         if api_level not in interfaces_for_level:
    109             interfaces_for_level[api_level] = set()
    110         interfaces_for_level[api_level].update(filter_out(get_interfaces(filename)))
    111 
    112     for package_root in Globals.package_root or tuple():
    113         tup = package_root.split(':')
    114         assert len(tup) == 2, \
    115             '--package-root must have the format PACKAGE:PATH, but is {}'.format(package_root)
    116         if Constants.CURRENT not in interfaces_for_level:
    117             interfaces_for_level[Constants.CURRENT] = set()
    118         interfaces_for_level[Constants.CURRENT].update(filter_out(get_interfaces_from_package_root(*tup)))
    119 
    120     seen_interfaces = set()
    121     new_interfaces_for_level = collections.OrderedDict()
    122     for level, interfaces in sorted(interfaces_for_level.items(), key=lambda tup: api_level_to_int(tup[0])):
    123         if level != Constants.CURRENT:
    124             removed_interfaces = seen_interfaces - interfaces
    125             assert not removed_interfaces, \
    126                 "The following interfaces are removed from API level {}:\n{}".format(
    127                     level, removed_interfaces)
    128         new_interfaces_for_level[level] = sorted(interfaces - seen_interfaces)
    129         seen_interfaces.update(interfaces)
    130 
    131     print(json.dumps(new_interfaces_for_level,
    132         separators=None if Globals.pretty else (',',':'),
    133         indent=4 if Globals.pretty else None))
    134 
    135 if __name__ == '__main__':
    136     main()
    137