#!/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()
|