#!/usr/bin/env python3 """Diff two javap dumps produced by dump_api.py and report changes to PUBLIC API only. usage: apidiff.py [--vector-only] A class is reported only if its declaration starts with 'public' in the old or the new dump, so package-private implementation classes (which javap -public still lists) do not drown the signal. """ import collections import re import sys def parse(path): classes = collections.OrderedDict() decl = {} cur = None for line in open(path): line = line.rstrip("\n") if line.startswith("#####") or line.startswith("Compiled from") or not line.strip(): continue if not line.startswith(" "): m = re.search(r"(?:class|interface|enum|@interface|record)\s+([\w.$]+)", line) cur = m.group(1) if m else line classes[cur] = set() decl[cur] = line elif cur is not None and line.strip() != "}": classes[cur].add(line.strip()) return classes, decl def is_public(decl_line): return decl_line.startswith("public ") or decl_line.startswith("protected ") def main(): old, old_decl = parse(sys.argv[1]) new, new_decl = parse(sys.argv[2]) vector_only = "--vector-only" in sys.argv def keep(name): if vector_only and not name.startswith("jdk.incubator.vector."): return False if name.startswith("file") or "Error: Access Flags" in old_decl.get(name, "") + new_decl.get(name, ""): return False return is_public(old_decl.get(name, "")) or is_public(new_decl.get(name, "")) print("## public classes removed") for c in sorted(set(old) - set(new)): if keep(c): print(" " + c) print("## public classes added") for c in sorted(set(new) - set(old)): if keep(c): print(" " + c) print("## public class changes") for c in sorted(set(old) & set(new)): if not keep(c): continue removed = old[c] - new[c] added = new[c] - old[c] d_old, d_new = old_decl[c], new_decl[c] if removed or added or d_old != d_new: print(c) if d_old != d_new: print(" - " + d_old.strip()) print(" + " + d_new.strip()) for x in sorted(removed): print(" - " + x) for x in sorted(added): print(" + " + x) main()