vpnhide/scripts/codegen_lib.py
Danila Gornushko 1c3e0a88d1
Close audit findings in clean-device, release, changelog, update-json, codegen (#209)
* fix(scripts): close audit findings in clean-device, release, changelog, update-json

Five tooling fixes from the codebase audit.

- clean-device.sh wiped only three /data/adb dirs and the legacy uids file, so a
  "clean" test left the canonical config, the root-owned APatch superkey under
  /data/adb/vpnhide, and the KPM/ports state behind. Mirror the full
  FULL_RESET_FILES / FULL_RESET_DIRS list from FullReset.kt.
- release.py ran the irreversible changelog rotation (save_json + delete the
  fragment files) BEFORE patching the version-bearing source files, so a drifted
  module.prop / Cargo.toml / gradle format failed mid-release with the changelog
  already mutated and the duplicate-version guard then refusing a retry. Dry-run-
  validate every version-patch regex before the changelog step.
- changelog.py printed the new fragment path relative to cwd, raising ValueError
  when cwd is not an ancestor of changelog.d (e.g. run from scripts/), a spurious
  traceback after the fragment was written fine. Make it relative to REPO_ROOT.
- update-json.sh computed versionCode with bare $(( )), so a zero-padded version
  component (1.08.0) was read as octal and aborted on digit 8/9. Force base-10.
- codegen-hooks.py now uses write_if_changed and reports only what changed, like
  codegen-interfaces.py, instead of rewriting every file and bumping mtimes.

* refactor(scripts): extract scripts/codegen_lib.py shared by both generators

codegen-interfaces.py and codegen-hooks.py each re-derived the same scaffolding:
REPO_ROOT, the "AUTO-GENERATED … Regenerate with …" banner, the verbatim
11-segment path to the LSPosed app's generated Kotlin package, and the
write-if-changed + change-reporting tail of main(). That duplication had already
drifted. Move it into scripts/codegen_lib.py (REPO_ROOT, generated_header,
lsposed_generated_kt, write_if_changed, emit_outputs), imported by both — the
shared-lib pattern scripts/ already uses for changelog_lib.py.

Generated output is byte-identical (verified: re-running both codegen scripts
leaves no diff in any generated file). The escaping helpers stay per-script —
they legitimately differ (C/Rust/Kotlin) and consolidating them risks output
drift for no gain.

* style: ruff format codegen_lib + generators
2026-06-30 11:31:45 +03:00

69 lines
2.4 KiB
Python

"""Shared scaffolding for the codegen scripts (codegen-interfaces.py and
codegen-hooks.py).
Both generators rendered the same boilerplate independently: the repo root, the
"AUTO-GENERATED … Regenerate with …" banner, the 11-segment path to the LSPosed
app's generated Kotlin package, and the write-if-changed + change-reporting tail
of main(). That duplication had already drifted (one script rewrote every file
unconditionally). Keep it here, imported by both, the way scripts/ already does
for changelog_lib.py / build_lib.py.
"""
from __future__ import annotations
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
def generated_header(source: str, regen_cmd: str) -> str:
"""The banner text for a generated file (without the per-language comment
marker): ``AUTO-GENERATED from <source> … Regenerate with: <regen_cmd>``."""
return f"AUTO-GENERATED from {source} — do not edit by hand. Regenerate with: {regen_cmd}"
def lsposed_generated_kt(filename: str, *, test: bool = False) -> Path:
"""Path to a generated Kotlin file under the LSPosed app's generated package
(``…/src/{main,test}/kotlin/dev/okhsunrog/vpnhide/generated/<filename>``)."""
source = "test" if test else "main"
return (
REPO_ROOT
/ "lsposed"
/ "app"
/ "src"
/ source
/ "kotlin"
/ "dev"
/ "okhsunrog"
/ "vpnhide"
/ "generated"
/ filename
)
def write_if_changed(path: Path, content: str) -> bool:
"""Write ``content`` to ``path`` only if it differs (preserving mtime when it
doesn't, so downstream Gradle/cargo don't needlessly rebuild). Returns True
if the file was written."""
path.parent.mkdir(parents=True, exist_ok=True)
if path.exists() and path.read_text(encoding="utf-8") == content:
return False
path.write_text(content, encoding="utf-8")
return True
def emit_outputs(outputs: dict[Path, str]) -> int:
"""Write each {path: content} via [write_if_changed], report only what
changed, and return 0 — the shared tail of both generators' main()."""
changed = []
for path, content in outputs.items():
if write_if_changed(path, content):
changed.append(path.relative_to(REPO_ROOT))
if changed:
print("Regenerated:")
for p in changed:
print(f" {p}")
else:
print("All generated files already up to date.")
return 0