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
(cherry picked from commit 7690d0d4eea0ffa429351b0b1caa34cdb3e0d37f)
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index cb8fef9..b5a990e 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -183,6 +183,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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: