refactor progress
This commit is contained in:
162
scripts/ci/erlang_hot_reload.py
Executable file
162
scripts/ci/erlang_hot_reload.py
Executable file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def diff_md5(local_path: str, remote_path: str, out_path: str) -> None:
|
||||
remote: dict[str, str] = {}
|
||||
with open(remote_path, "r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(None, 1)
|
||||
if len(parts) != 2:
|
||||
continue
|
||||
mod, md5 = parts
|
||||
remote[mod] = md5.strip()
|
||||
|
||||
changed_paths: list[str] = []
|
||||
with open(local_path, "r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split(" ", 2)
|
||||
if len(parts) != 3:
|
||||
continue
|
||||
mod, md5, path = parts
|
||||
remote_md5 = remote.get(mod)
|
||||
if remote_md5 is None or remote_md5 == "null" or remote_md5 != md5:
|
||||
changed_paths.append(path)
|
||||
|
||||
with open(out_path, "w", encoding="utf-8") as handle:
|
||||
for path in changed_paths:
|
||||
handle.write(f"{path}\n")
|
||||
|
||||
|
||||
def build_json(list_path: str) -> None:
|
||||
beams: list[dict[str, str]] = []
|
||||
with open(list_path, "r", encoding="utf-8") as handle:
|
||||
for path in handle:
|
||||
path = path.strip()
|
||||
if not path:
|
||||
continue
|
||||
mod = os.path.basename(path)
|
||||
if not mod.endswith(".beam"):
|
||||
continue
|
||||
mod = mod[:-5]
|
||||
with open(path, "rb") as beam_file:
|
||||
beam_data = beam_file.read()
|
||||
beams.append({
|
||||
"module": mod,
|
||||
"beam_b64": base64.b64encode(beam_data).decode("ascii"),
|
||||
})
|
||||
|
||||
payload = {"beams": beams, "purge": "soft"}
|
||||
print(json.dumps(payload, separators=(",", ":")))
|
||||
|
||||
|
||||
def verify(mode: str) -> int:
|
||||
raw = sys.stdin.read()
|
||||
if not raw.strip():
|
||||
print("::error::Empty reload response")
|
||||
return 1
|
||||
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception as exc:
|
||||
print(f"::error::Invalid JSON reload response: {exc}")
|
||||
return 1
|
||||
|
||||
results = data.get("results", [])
|
||||
if not isinstance(results, list):
|
||||
print("::error::Reload response missing results array")
|
||||
return 1
|
||||
|
||||
if mode == "strict":
|
||||
bad = [
|
||||
result for result in results
|
||||
if result.get("status") != "ok" or result.get("verified") is not True
|
||||
]
|
||||
if bad:
|
||||
print("::error::Hot reload verification failed")
|
||||
print(json.dumps(bad, indent=2))
|
||||
return 1
|
||||
|
||||
warns = [
|
||||
result for result in results
|
||||
if result.get("purged_old_code") is not True
|
||||
or (result.get("lingering_count") or 0) != 0
|
||||
]
|
||||
if warns:
|
||||
print("::warning::Old code is still lingering for some modules after reload")
|
||||
print(json.dumps(warns, indent=2))
|
||||
|
||||
print(f"Verified {len(results)} modules")
|
||||
return 0
|
||||
|
||||
if mode == "self":
|
||||
bad = [
|
||||
result for result in results
|
||||
if result.get("status") != "ok" or result.get("verified") is not True
|
||||
]
|
||||
if bad:
|
||||
print("::error::Hot reload verification failed")
|
||||
print(json.dumps(bad, indent=2))
|
||||
return 1
|
||||
|
||||
warns = [
|
||||
result for result in results
|
||||
if result.get("purged_old_code") is not True
|
||||
or (result.get("lingering_count") or 0) != 0
|
||||
]
|
||||
if warns:
|
||||
print("::warning::Self-reload modules may linger until request completes")
|
||||
print(json.dumps(warns, indent=2))
|
||||
|
||||
print(f"Verified {len(results)} self modules")
|
||||
return 0
|
||||
|
||||
print(f"::error::Unknown verify mode: {mode}")
|
||||
return 1
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
diff_parser = subparsers.add_parser("diff-md5")
|
||||
diff_parser.add_argument("local_path")
|
||||
diff_parser.add_argument("remote_path")
|
||||
diff_parser.add_argument("out_path")
|
||||
|
||||
build_parser = subparsers.add_parser("build-json")
|
||||
build_parser.add_argument("list_path")
|
||||
|
||||
verify_parser = subparsers.add_parser("verify")
|
||||
verify_parser.add_argument("--mode", choices=("strict", "self"), required=True)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
if args.command == "diff-md5":
|
||||
diff_md5(args.local_path, args.remote_path, args.out_path)
|
||||
return 0
|
||||
if args.command == "build-json":
|
||||
build_json(args.list_path)
|
||||
return 0
|
||||
if args.command == "verify":
|
||||
return verify(args.mode)
|
||||
print(f"::error::Unknown command: {args.command}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user