• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
19Dump new HIDL types that are introduced in each dessert release.
20"""
21
22from __future__ import print_function
23
24import argparse
25import collections
26import json
27import os
28import re
29
30class Globals:
31    pass
32
33class 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
38def trim_trailing_comments(line):
39    idx = line.find('#')
40    if idx < 0: return line
41    return line[0:idx]
42
43def strip_begin(s, prefix):
44    if s.startswith(prefix):
45        return strip_begin(s[len(prefix):], prefix)
46    return s
47
48def strip_end(s, suffix):
49    if s.endswith(suffix):
50        return strip_end(s[0:-len(suffix)], suffix)
51    return s
52
53def 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
63def 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
70def 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
82def 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
87def 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
135if __name__ == '__main__':
136    main()
137