diff options
author | Jeff Sharkey <jsharkey@android.com> | 2014-09-04 15:46:20 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2014-09-04 15:46:23 -0700 |
commit | 037458a5bac2968eb0415c408d68c013d177ea3e (patch) | |
tree | cb1df0787e3d88fc1ac8626aa221ca5f9c8c8dde /tools/apilint | |
parent | 1498f9c615395de17e11204b962d7d925e5f222d (diff) | |
download | frameworks_base-037458a5bac2968eb0415c408d68c013d177ea3e.zip frameworks_base-037458a5bac2968eb0415c408d68c013d177ea3e.tar.gz frameworks_base-037458a5bac2968eb0415c408d68c013d177ea3e.tar.bz2 |
Catch incompatible API regressions.
Ignore deprecation, synchronized, and throws definitions. Look
through full inheritance hierarchy to catch refactored classes like
BaseBundle.
Change-Id: I10ab0b4a0ef64e7508f38d0c223f08711293d643
Diffstat (limited to 'tools/apilint')
-rw-r--r-- | tools/apilint/apilint.py | 94 |
1 files changed, 88 insertions, 6 deletions
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index d9d571a..393d2ec 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -66,6 +66,8 @@ class Field(): else: self.value = None + self.ident = self.raw.replace(" deprecated ", " ") + def __repr__(self): return self.raw @@ -94,6 +96,15 @@ class Method(): if r == "throws": break self.args.append(r) + # identity for compat purposes + ident = self.raw + ident = ident.replace(" deprecated ", " ") + ident = ident.replace(" synchronized ", " ") + ident = re.sub("<.+?>", "", ident) + if " throws " in ident: + ident = ident[:ident.index(" throws ")] + self.ident = ident + def __repr__(self): return self.raw @@ -113,11 +124,17 @@ class Class(): self.fullname = raw[raw.index("class")+1] elif "interface" in raw: self.fullname = raw[raw.index("interface")+1] + else: + raise ValueError("Funky class type %s" % (self.raw)) + + if "extends" in raw: + self.extends = raw[raw.index("extends")+1] + else: + self.extends = None self.fullname = self.pkg.name + "." + self.fullname self.name = self.fullname[self.fullname.rindex(".")+1:] - def __repr__(self): return self.raw @@ -188,10 +205,10 @@ def _fail(clazz, detail, msg): failures[sig] = res def warn(clazz, detail, msg): - _fail(clazz, detail, "%sWarning:%s %s" % (format(fg=YELLOW, bg=BLACK), format(reset=True), msg)) + _fail(clazz, detail, "%sWarning:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), format(reset=True), msg)) def error(clazz, detail, msg): - _fail(clazz, detail, "%sError:%s %s" % (format(fg=RED, bg=BLACK), format(reset=True), msg)) + _fail(clazz, detail, "%sError:%s %s" % (format(fg=RED, bg=BLACK, bold=True), format(reset=True), msg)) def verify_constants(clazz): @@ -656,7 +673,8 @@ def verify_flags(clazz): known[scope] |= val -def verify_all(api): +def verify_style(api): + """Find all style issues in the given API level.""" global failures failures = {} @@ -697,19 +715,83 @@ def verify_all(api): return failures +def verify_compat(cur, prev): + """Find any incompatible API changes between two levels.""" + global failures + + def class_exists(api, test): + return test.fullname in api + + def ctor_exists(api, clazz, test): + for m in clazz.ctors: + if m.ident == test.ident: return True + return False + + def all_methods(api, clazz): + methods = list(clazz.methods) + if clazz.extends is not None: + methods.extend(all_methods(api, api[clazz.extends])) + return methods + + def method_exists(api, clazz, test): + methods = all_methods(api, clazz) + for m in methods: + if m.ident == test.ident: return True + return False + + def field_exists(api, clazz, test): + for f in clazz.fields: + if f.ident == test.ident: return True + return False + + failures = {} + for key in sorted(prev.keys()): + prev_clazz = prev[key] + + if not class_exists(cur, prev_clazz): + error(prev_clazz, None, "Class removed or incompatible change") + continue + + cur_clazz = cur[key] + + for test in prev_clazz.ctors: + if not ctor_exists(cur, cur_clazz, test): + error(prev_clazz, prev_ctor, "Constructor removed or incompatible change") + + methods = all_methods(prev, prev_clazz) + for test in methods: + if not method_exists(cur, cur_clazz, test): + error(prev_clazz, test, "Method removed or incompatible change") + + for test in prev_clazz.fields: + if not field_exists(cur, cur_clazz, test): + error(prev_clazz, test, "Field removed or incompatible change") + + return failures + + cur = parse_api(sys.argv[1]) -cur_fail = verify_all(cur) +cur_fail = verify_style(cur) if len(sys.argv) > 2: prev = parse_api(sys.argv[2]) - prev_fail = verify_all(prev) + prev_fail = verify_style(prev) # ignore errors from previous API level for p in prev_fail: if p in cur_fail: del cur_fail[p] + # look for compatibility issues + compat_fail = verify_compat(cur, prev) + + print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True))) + for f in sorted(compat_fail): + print compat_fail[f] + print + +print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True))) for f in sorted(cur_fail): print cur_fail[f] print |