| #!/usr/bin/env python |
| # |
| # Copyright (C) 2018 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| """ |
| Dump new HIDL types that are introduced in each dessert release. |
| """ |
| |
| from __future__ import print_function |
| |
| import argparse |
| import collections |
| import json |
| import os |
| import re |
| |
| class Globals: |
| pass |
| |
| class Constants: |
| CURRENT = 'current' |
| HAL_PATH_PATTERN = r'/((?:[a-zA-Z_][a-zA-Z0-9_]*/)*)(\d+\.\d+)/([a-zA-Z_][a-zA-Z0-9_]*).hal' |
| CURRENT_TXT_PATTERN = r'(?:.*/)?([0-9]+|current).txt' |
| |
| def trim_trailing_comments(line): |
| idx = line.find('#') |
| if idx < 0: return line |
| return line[0:idx] |
| |
| def strip_begin(s, prefix): |
| if s.startswith(prefix): |
| return strip_begin(s[len(prefix):], prefix) |
| return s |
| |
| def strip_end(s, suffix): |
| if s.endswith(suffix): |
| return strip_end(s[0:-len(suffix)], suffix) |
| return s |
| |
| def get_interfaces(file_name): |
| with open(file_name) as file: |
| for line in file: |
| line_tokens = trim_trailing_comments(line).strip().split() |
| if not line_tokens: |
| continue |
| assert len(line_tokens) == 2, \ |
| "Unrecognized line in file {}:\n{}".format(file_name, line) |
| yield line_tokens[1] |
| |
| def api_level_to_int(api_level): |
| try: |
| if api_level == Constants.CURRENT: return float('inf') |
| return int(api_level) |
| except ValueError: |
| return None |
| |
| def get_interfaces_from_package_root(package, root): |
| root = strip_end(root, '/') |
| for dirpath, _, filenames in os.walk(root, topdown=False): |
| dirpath = strip_begin(dirpath, root) |
| for filename in filenames: |
| filepath = os.path.join(dirpath, filename) |
| mo = re.match(Constants.HAL_PATH_PATTERN, filepath) |
| if not mo: |
| continue |
| yield '{}.{}@{}::{}'.format( |
| package, mo.group(1).strip('/').replace('/', '.'), mo.group(2), mo.group(3)) |
| |
| def filter_out(iterable): |
| return iterable if not Globals.filter_out else filter( |
| lambda s: all(re.search(pattern, s) is None for pattern in Globals.filter_out), |
| iterable) |
| |
| def main(): |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument('--pretty', help='Print pretty JSON', action='store_true') |
| parser.add_argument('--package-root', metavar='PACKAGE:PATH', nargs='*', |
| help='package root of current directory, e.g. android.hardware:hardware/interfaces') |
| parser.add_argument('--filter-out', metavar='REGEX', nargs='*', |
| help='A regular expression that filters out interfaces.') |
| parser.add_argument('hashes', metavar='FILE', nargs='*', |
| help='Locations of current.txt for each release.') |
| parser.parse_args(namespace=Globals) |
| |
| interfaces_for_level = dict() |
| |
| for filename in Globals.hashes or tuple(): |
| mo = re.match(Constants.CURRENT_TXT_PATTERN, filename) |
| assert mo is not None, \ |
| 'Input hash file names must have the format {} but is {}'.format(Constants.CURRENT_TXT_PATTERN, filename) |
| |
| api_level = mo.group(1) |
| assert api_level_to_int(api_level) is not None |
| |
| if api_level not in interfaces_for_level: |
| interfaces_for_level[api_level] = set() |
| interfaces_for_level[api_level].update(filter_out(get_interfaces(filename))) |
| |
| for package_root in Globals.package_root or tuple(): |
| tup = package_root.split(':') |
| assert len(tup) == 2, \ |
| '--package-root must have the format PACKAGE:PATH, but is {}'.format(package_root) |
| if Constants.CURRENT not in interfaces_for_level: |
| interfaces_for_level[Constants.CURRENT] = set() |
| interfaces_for_level[Constants.CURRENT].update(filter_out(get_interfaces_from_package_root(*tup))) |
| |
| seen_interfaces = set() |
| new_interfaces_for_level = collections.OrderedDict() |
| for level, interfaces in sorted(interfaces_for_level.items(), key=lambda tup: api_level_to_int(tup[0])): |
| if level != Constants.CURRENT: |
| removed_interfaces = seen_interfaces - interfaces |
| assert not removed_interfaces, \ |
| "The following interfaces are removed from API level {}:\n{}".format( |
| level, removed_interfaces) |
| new_interfaces_for_level[level] = sorted(interfaces - seen_interfaces) |
| seen_interfaces.update(interfaces) |
| |
| print(json.dumps(new_interfaces_for_level, |
| separators=None if Globals.pretty else (',',':'), |
| indent=4 if Globals.pretty else None)) |
| |
| if __name__ == '__main__': |
| main() |