Add migration-5-to-7 module: Hibernate 5.6.15 vs 7.4.14 jar diff, compile checks, schema defaults and cheat-sheet snippets
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YPgGH6aMbSLANL3E2HHmS6
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare the public API of hibernate-core 5.6.15.Final (org.hibernate) with 7.4.14.Final (org.hibernate.orm).
|
||||
|
||||
javax.persistence is read as jakarta.persistence, so the namespace move does not drown the real changes.
|
||||
Writes output/01-versions-and-baseline.txt, output/02-removed-api.txt and output/02-removed-api-summary.txt.
|
||||
Needs a JDK (javap) and access to Maven Central.
|
||||
"""
|
||||
import collections
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
JARS = ROOT / "target" / "jars"
|
||||
OUT = ROOT / "output"
|
||||
JAVAP = os.path.join(os.environ["JAVA_HOME"], "bin", "javap") if os.environ.get("JAVA_HOME") else "javap"
|
||||
|
||||
OLD = ("org/hibernate", "5.6.15.Final")
|
||||
NEW = ("org/hibernate/orm", "7.4.14.Final")
|
||||
|
||||
|
||||
def fetch(url, dest):
|
||||
if dest.exists():
|
||||
return
|
||||
for _ in range(10):
|
||||
try:
|
||||
with urllib.request.urlopen(url) as r:
|
||||
dest.write_bytes(r.read())
|
||||
return
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code != 429:
|
||||
raise
|
||||
time.sleep(5)
|
||||
raise RuntimeError("rate limited: " + url)
|
||||
|
||||
|
||||
def jar(side):
|
||||
group, v = side
|
||||
dest = JARS / f"hibernate-core-{v}.jar"
|
||||
fetch(f"https://repo1.maven.org/maven2/{group}/hibernate-core/{v}/hibernate-core-{v}.jar", dest)
|
||||
return dest
|
||||
|
||||
|
||||
def classes(path):
|
||||
with zipfile.ZipFile(path) as z:
|
||||
return sorted(n[:-6].replace("/", ".") for n in z.namelist()
|
||||
if n.endswith(".class") and not n.endswith("module-info.class") and "META-INF" not in n)
|
||||
|
||||
|
||||
def major(path):
|
||||
with zipfile.ZipFile(path) as z:
|
||||
name = next(n for n in z.namelist() if n.endswith(".class") and "META-INF" not in n and not n.endswith("module-info.class"))
|
||||
return int.from_bytes(z.read(name)[6:8], "big")
|
||||
|
||||
|
||||
def jakarta(text):
|
||||
return text.replace("javax.persistence", "jakarta.persistence").replace("javax.transaction", "jakarta.transaction")
|
||||
|
||||
|
||||
def norm(sig):
|
||||
sig = jakarta(sig)
|
||||
sig = re.sub(r"\s+throws\s+.*$", "", sig)
|
||||
prev = None
|
||||
while prev != sig:
|
||||
prev = sig
|
||||
sig = re.sub(r"<[^<>]*>", "", sig)
|
||||
return " ".join(sig.split())
|
||||
|
||||
|
||||
MEMBER = re.compile(r"^ ((?:public|protected)[^;]*);$")
|
||||
|
||||
|
||||
def dump(path):
|
||||
names = [c for c in classes(path) if ".internal." not in c and ".spi." not in c and not c.startswith("org.hibernate.sql.ast")
|
||||
and not c.startswith("org.hibernate.sql.results") and not c.startswith("org.hibernate.query.sqm.tree")
|
||||
and not c.startswith("org.hibernate.grammars")]
|
||||
result = {}
|
||||
for i in range(0, len(names), 150):
|
||||
chunk = names[i:i + 150]
|
||||
p = subprocess.run([JAVAP, "-v", "-protected", "-cp", str(path)] + chunk, capture_output=True, text=True)
|
||||
for block in re.split(r"(?m)^Classfile ", p.stdout)[1:]:
|
||||
lines = block.splitlines()
|
||||
cls = jakarta(re.search(r"!/(.*)\.class", lines[0]).group(1).replace("/", "."))
|
||||
public, seen_flags, in_pool, cur = False, False, False, None
|
||||
members = {}
|
||||
cls_dep = bool(re.search(r"^Deprecated: true", block, re.M))
|
||||
for line in lines[1:]:
|
||||
if not seen_flags and line.startswith(" flags:"):
|
||||
public = "ACC_PUBLIC" in line
|
||||
seen_flags = True
|
||||
if line.startswith("Constant pool:"):
|
||||
in_pool = True
|
||||
continue
|
||||
if in_pool:
|
||||
if line.startswith("{"):
|
||||
in_pool = False
|
||||
continue
|
||||
m = MEMBER.match(line)
|
||||
if m:
|
||||
cur = " ".join(m.group(1).split())
|
||||
members[cur] = False
|
||||
continue
|
||||
if line.strip() == "Deprecated: true" and cur is not None:
|
||||
members[cur] = True
|
||||
if line.startswith("}"):
|
||||
cur = None
|
||||
result[cls] = (public, cls_dep, members)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
JARS.mkdir(parents=True, exist_ok=True)
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
old, new = jar(OLD), jar(NEW)
|
||||
mo, mn = major(old), major(new)
|
||||
versions = [
|
||||
f"{'':<12}{'5.x':<46}{'7.x'}",
|
||||
f"{'coordinates':<12}{'org.hibernate:hibernate-core:5.6.15.Final':<46}org.hibernate.orm:hibernate-core:7.4.14.Final",
|
||||
f"{'class files':<12}{'Java ' + str(mo - 44):<46}Java {mn - 44}",
|
||||
f"{'persistence':<12}{'javax.persistence (JPA 2.2)':<46}jakarta.persistence (Jakarta Persistence 3.2)",
|
||||
]
|
||||
do, dn = dump(old), dump(new)
|
||||
co = {jakarta(c) for c in classes(old)}
|
||||
cn = {jakarta(c) for c in classes(new)}
|
||||
simple_new = collections.defaultdict(list)
|
||||
for c in cn:
|
||||
simple_new[c.split(".")[-1]].append(c)
|
||||
removed_cls, removed_mem = [], []
|
||||
for c in sorted(co - cn):
|
||||
if c in do and do[c][0]:
|
||||
removed_cls.append((c, do[c][1], simple_new.get(c.split(".")[-1], [])))
|
||||
for c in sorted(do):
|
||||
if c in dn and do[c][0]:
|
||||
new_norm = {norm(x) for x in dn[c][2]}
|
||||
for sig, dep in do[c][2].items():
|
||||
if norm(sig) not in new_norm:
|
||||
removed_mem.append((c, jakarta(sig), dep))
|
||||
lines = [f"== hibernate-core: public classes missing: {len(removed_cls)}, members removed from classes that remain: {len(removed_mem)}, "
|
||||
f"new classes: {len(cn - co)}"]
|
||||
for c, dep, same_name in removed_cls:
|
||||
if same_name:
|
||||
note = "same simple name exists in " + ", ".join(sorted(p.rsplit('.', 1)[0] for p in same_name)[:2])
|
||||
else:
|
||||
note = "no class with this name"
|
||||
lines.append(f" class {c} [{'deprecated in 5.6' if dep else 'NOT deprecated in 5.6'}; {note}]")
|
||||
for c, sig, dep in removed_mem:
|
||||
lines.append(f" member {c}: {sig} [{'deprecated in 5.6' if dep else 'NOT deprecated in 5.6'}]")
|
||||
(OUT / "01-versions-and-baseline.txt").write_text("\n".join(versions) + "\n")
|
||||
(OUT / "02-removed-api.txt").write_text("\n".join(lines) + "\n")
|
||||
# summary: counts per package, and how many of the missing classes have a namesake elsewhere
|
||||
per_pkg = collections.Counter(c.rsplit(".", 1)[0] for c, _, _ in removed_cls)
|
||||
dep_n = sum(1 for _, d, _ in removed_cls if d)
|
||||
ren_n = sum(1 for _, _, s in removed_cls if s)
|
||||
summary = [lines[0],
|
||||
f" of the missing classes: {dep_n} were deprecated in 5.6.15, {ren_n} have a class of the same simple name somewhere in 7.4.14",
|
||||
" packages with the most missing classes:"]
|
||||
for pkg, n in per_pkg.most_common(12):
|
||||
summary.append(f" {n:>4} {pkg}")
|
||||
(OUT / "02-removed-api-summary.txt").write_text("\n".join(summary) + "\n")
|
||||
print("\n".join(versions))
|
||||
print("\n".join(summary))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# Compiles src/compat/*.java against Hibernate 5.6.15 (h5/) and 7.4.14 (h7/) and records javac's answer in output/.
|
||||
set -u
|
||||
cd "$(dirname "$0")/.."
|
||||
mkdir -p target output
|
||||
compile() { # version dir file outfile
|
||||
local v=$1 dir=$2 f=$3 out=$4
|
||||
rm -rf target/classes-$f-$v; mkdir -p target/classes-$f-$v
|
||||
{
|
||||
echo "Hibernate $v, compiling $f.java"
|
||||
javac -Xlint:deprecation -proc:none -d target/classes-$f-$v -cp "$(cat target/cp-$v.txt)" src/compat/$f.java 2>&1 | grep -v "^Picked up"
|
||||
echo "javac exit status: ${PIPESTATUS[0]}"
|
||||
} >> $out
|
||||
}
|
||||
for pair in "h5:5.6.15" "h7:7.4.14"; do
|
||||
dir=${pair%%:*}; v=${pair##*:}
|
||||
(cd $dir && mvn -q -B dependency:build-classpath -Dmdep.outputFile=../target/cp-$v.txt >/dev/null) || { echo "classpath failed for $v"; exit 1; }
|
||||
for spec in "OldHibernate:03-old-hibernate-apis" "NewHibernate:04-new-hibernate-apis" "OldAnnotations:05-old-annotations" "NewAnnotations:06-new-annotations" "JavaxNamespace:09-namespace-javax" "JakartaNamespace:09-namespace-jakarta"; do
|
||||
f=${spec%%:*}; n=${spec##*:}
|
||||
out=output/$n-$v.txt; : > $out
|
||||
compile $v $dir $f $out
|
||||
done
|
||||
done
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rewrites everything in output/. Needs JDK 17+, Maven 3.9, Python 3 and internet access to Maven Central.
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
python3 scripts/api_diff.py
|
||||
(cd h5 && mvn -q -B clean test)
|
||||
(cd h7 && mvn -q -B clean test)
|
||||
python3 scripts/schema_diff.py
|
||||
./scripts/compile-old-apis.sh
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Lines up output/06-schema-5.6.15.txt and output/06-schema-7.4.14.txt side by side into output/06-schema-diff.txt."""
|
||||
import pathlib
|
||||
|
||||
OUT = pathlib.Path(__file__).resolve().parent.parent / "output"
|
||||
|
||||
|
||||
def read(version):
|
||||
seq, cols, section = {}, {}, None
|
||||
for line in (OUT / f"06-schema-{version}.txt").read_text().splitlines():
|
||||
if line.startswith("-- sequences"):
|
||||
section = "seq"
|
||||
elif line.startswith("-- columns"):
|
||||
section = "col"
|
||||
elif section == "seq":
|
||||
name, inc = line.rsplit(" ", 1)
|
||||
seq[name] = inc
|
||||
elif section == "col":
|
||||
name, typ = line.split(" ", 1)
|
||||
cols[name] = typ
|
||||
return seq, cols
|
||||
|
||||
|
||||
s5, c5 = read("5.6.15")
|
||||
s7, c7 = read("7.4.14")
|
||||
rows = [f"{'':<14}{'Hibernate 5.6.15':<26}{'Hibernate 7.4.14':<26}", "-- id sequences (name, increment)"]
|
||||
rows.append(f"{'':<14}{', '.join(f'{k} {v}' for k, v in s5.items()):<26}{', '.join(f'{k} {v}' for k, v in s7.items())}")
|
||||
rows.append("-- column types in H2")
|
||||
for name in sorted(set(c5) | set(c7)):
|
||||
a, b = c5.get(name, "-"), c7.get(name, "-")
|
||||
rows.append(f"{name:<14}{a:<26}{b:<26}{'(same)' if a == b else '<-- differs'}")
|
||||
(OUT / "06-schema-diff.txt").write_text("\n".join(rows) + "\n")
|
||||
print("\n".join(rows))
|
||||
Reference in New Issue
Block a user