From 7690d0d4eea0ffa429351b0b1caa34cdb3e0d37f Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Thu, 13 Dec 2018 22:08:29 +0100 Subject: API Lint: Add support for base current.txt Allows specifying a base current.txt and previous.txt file when linting system-current.txt and test-current.txt to avoid false positive error messages due to public API members not being duplicated in the respective non-public APIs Test: python tools/apilint/apilint.py --base-current=api/current.txt api/system-current.txt Change-Id: I306a99b1423584ef3fcdc9272a83cb5eacc37227 --- tools/apilint/apilint.py | 87 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index cb8fef946baf..b5a990e6e3cf 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -183,6 +183,11 @@ class Class(): self.name = self.fullname[self.fullname.rindex(".")+1:] + def merge_from(self, other): + self.ctors.extend(other.ctors) + self.fields.extend(other.fields) + self.methods.extend(other.methods) + def __hash__(self): return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods))) @@ -204,9 +209,28 @@ class Package(): return self.raw -def _parse_stream(f, clazz_cb=None): - line = 0 +def _parse_stream(f, clazz_cb=None, base_f=None): api = {} + + if base_f: + base_classes = _parse_stream_to_generator(base_f) + else: + base_classes = [] + + for clazz in _parse_stream_to_generator(f): + base_class = _parse_to_matching_class(base_classes, clazz) + if base_class: + clazz.merge_from(base_class) + + if clazz_cb: + clazz_cb(clazz) + else: # In callback mode, don't keep track of the full API + api[clazz.fullname] = clazz + + return api + +def _parse_stream_to_generator(f): + line = 0 pkg = None clazz = None blame = None @@ -225,26 +249,41 @@ def _parse_stream(f, clazz_cb=None): if raw.startswith("package"): pkg = Package(line, raw, blame) elif raw.startswith(" ") and raw.endswith("{"): - # When provided with class callback, we treat as incremental - # parse and don't build up entire API - if clazz and clazz_cb: - clazz_cb(clazz) clazz = Class(pkg, line, raw, blame) - if not clazz_cb: - api[clazz.fullname] = clazz elif raw.startswith(" ctor"): clazz.ctors.append(Method(clazz, line, raw, blame)) elif raw.startswith(" method"): clazz.methods.append(Method(clazz, line, raw, blame)) elif raw.startswith(" field"): clazz.fields.append(Field(clazz, line, raw, blame)) + elif raw.startswith(" }") and clazz: + while True: + retry = yield clazz + if not retry: + break + # send() was called, asking us to redeliver clazz on next(). Still need to yield + # a dummy value to the send() first though. + if (yield "Returning clazz on next()"): + raise TypeError("send() must be followed by next(), not send()") - # Handle last trailing class - if clazz and clazz_cb: - clazz_cb(clazz) - return api +def _parse_to_matching_class(classes, needle): + """Takes a classes generator and parses it until it returns the class we're looking for + + This relies on classes being sorted by package and class name.""" + for clazz in classes: + if clazz.pkg.name < needle.pkg.name: + # We haven't reached the right package yet + continue + if clazz.name < needle.name: + # We haven't reached the right class yet + continue + if clazz.fullname == needle.fullname: + return clazz + # We ran past the right class. Send it back into the generator, then report failure. + classes.send(clazz) + return None class Failure(): def __init__(self, sig, clazz, detail, error, rule, msg): @@ -1504,12 +1543,12 @@ def examine_clazz(clazz): verify_singleton(clazz) -def examine_stream(stream): +def examine_stream(stream, base_stream=None): """Find all style issues in the given API stream.""" global failures, noticed failures = {} noticed = {} - _parse_stream(stream, examine_clazz) + _parse_stream(stream, examine_clazz, base_f=base_stream) return (failures, noticed) @@ -1650,6 +1689,12 @@ if __name__ == "__main__": parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt") parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None, help="previous.txt") + parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None, + help="The base current.txt to use when examining system-current.txt or" + " test-current.txt") + parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None, + help="The base previous.txt to use when examining system-previous.txt or" + " test-previous.txt") parser.add_argument("--no-color", action='store_const', const=True, help="Disable terminal colors") parser.add_argument("--allow-google", action='store_const', const=True, @@ -1669,7 +1714,9 @@ if __name__ == "__main__": ALLOW_GOOGLE = True current_file = args['current.txt'] + base_current_file = args['base_current'] previous_file = args['previous.txt'] + base_previous_file = args['base_previous'] if args['show_deprecations_at_birth']: with current_file as f: @@ -1688,10 +1735,18 @@ if __name__ == "__main__": sys.exit() with current_file as f: - cur_fail, cur_noticed = examine_stream(f) + if base_current_file: + with base_current_file as base_f: + cur_fail, cur_noticed = examine_stream(f, base_f) + else: + cur_fail, cur_noticed = examine_stream(f) if not previous_file is None: with previous_file as f: - prev_fail, prev_noticed = examine_stream(f) + if base_previous_file: + with base_previous_file as base_f: + prev_fail, prev_noticed = examine_stream(f, base_f) + else: + prev_fail, prev_noticed = examine_stream(f) # ignore errors from previous API level for p in prev_fail: -- cgit v1.2.3-59-g8ed1b