Java 27 and 26: runnable demos and captured output for every JEP, plus version lanes
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Diff two javap dumps produced by dump_api.py and report changes to PUBLIC API only.
|
||||
|
||||
usage: apidiff.py <old-dump> <new-dump> [--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()
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dump the public API signatures of every exported class in the JDK's java.se-ish modules.
|
||||
usage: dump_api.py <JAVA_HOME> <out-file>"""
|
||||
import subprocess, sys, re, os
|
||||
home, out = sys.argv[1], sys.argv[2]
|
||||
env = {k:v for k,v in os.environ.items() if k!='JAVA_TOOL_OPTIONS'}
|
||||
jimage = subprocess.run([f'{home}/bin/jimage','list',f'{home}/lib/modules'],capture_output=True,text=True,env=env).stdout
|
||||
mods = {}
|
||||
cur=None
|
||||
for line in jimage.splitlines():
|
||||
if line.startswith('Module: '): cur=line.split(': ')[1].strip(); mods[cur]=[]
|
||||
elif cur and line.strip().endswith('.class'):
|
||||
mods[cur].append(line.strip()[:-6])
|
||||
WANT = re.compile(r'^(java|jdk\.(jfr|httpserver|incubator\.vector|management|net|jshell|jartool|jlink|jpackage|javadoc|compiler|jcmd|unsupported|security\.auth)|javafx)')
|
||||
res=[]
|
||||
for m,classes in sorted(mods.items()):
|
||||
if not WANT.match(m): continue
|
||||
names=[]
|
||||
for c in classes:
|
||||
if c=='module-info': continue
|
||||
pk=c.rsplit('/',1)[0].replace('/','.') if '/' in c else ''
|
||||
if re.search(r'(^|\.)(internal|impl)(\.|$)|^sun\.|^com\.sun\.(?!net\.httpserver|management|security\.auth|source)',pk): continue
|
||||
names.append(c.replace('/','.'))
|
||||
if not names: continue
|
||||
# javap handles binary names with $ for nested
|
||||
names=[n for n in names if not re.search(r'\$\d',n)]
|
||||
txt=''
|
||||
for i in range(0,len(names),200):
|
||||
p=subprocess.run([f'{home}/bin/javap','--module',m,'-public']+names[i:i+200],capture_output=True,text=True,env=env)
|
||||
txt+=p.stdout
|
||||
res.append(f'##### MODULE {m}\n'+txt)
|
||||
open(out,'w').write('\n'.join(res))
|
||||
print(out, sum(len(r) for r in res)//1024,'KB')
|
||||
Reference in New Issue
Block a user