|
|
@@ -0,0 +1,113 @@
|
|
|
+"""Regenerate motian_bridge/dialects/v20/VKFly.py from the dialect submodule.
|
|
|
+
|
|
|
+The generated module must never be hand-edited. Run this script after updating
|
|
|
+the `msg_definitions` submodule, then commit both the submodule pointer and the
|
|
|
+regenerated file.
|
|
|
+"""
|
|
|
+
|
|
|
+import argparse
|
|
|
+import hashlib
|
|
|
+import logging
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+import tempfile
|
|
|
+from pathlib import Path
|
|
|
+
|
|
|
+if __package__ in (None, ""):
|
|
|
+ # Allow `python tools/gen_dialect.py` as well as `from tools import ...`.
|
|
|
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
+
|
|
|
+ROOT = Path(__file__).resolve().parent.parent
|
|
|
+OUTPUT = ROOT / "motian_bridge" / "dialects" / "v20" / "VKFly.py"
|
|
|
+SOURCE = ROOT / "msg_definitions" / "msg_definitions" / "VKFly.xml"
|
|
|
+INCLUDES = ("common.xml", "standard.xml", "minimal.xml")
|
|
|
+
|
|
|
+LOG = logging.getLogger("gen_dialect")
|
|
|
+
|
|
|
+
|
|
|
+def _sha256(path: Path) -> str:
|
|
|
+ digest = hashlib.sha256()
|
|
|
+ with open(path, "rb") as stream:
|
|
|
+ for block in iter(lambda: stream.read(1 << 20), b""):
|
|
|
+ digest.update(block)
|
|
|
+ return digest.hexdigest()
|
|
|
+
|
|
|
+
|
|
|
+def check_sources() -> None:
|
|
|
+ """Fail early with an actionable message instead of a mavgen traceback."""
|
|
|
+ missing = [path for path in (SOURCE, *(SOURCE.parent / name for name in INCLUDES))
|
|
|
+ if not path.is_file()]
|
|
|
+ if missing:
|
|
|
+ listing = "\n".join(f" - {path}" for path in missing)
|
|
|
+ raise SystemExit(
|
|
|
+ "MAVLink XML sources are missing:\n"
|
|
|
+ f"{listing}\n"
|
|
|
+ "Initialize the dialect submodule first:\n"
|
|
|
+ " git submodule update --init --recursive"
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def mavgen(extra_args: list[str], output: Path) -> None:
|
|
|
+ command = [
|
|
|
+ sys.executable, "-m", "pymavlink.tools.mavgen",
|
|
|
+ "--lang", "Python",
|
|
|
+ "--wire-protocol", "2.0",
|
|
|
+ "--output", str(output),
|
|
|
+ *extra_args,
|
|
|
+ str(SOURCE),
|
|
|
+ ]
|
|
|
+ LOG.debug("running: %s", " ".join(command))
|
|
|
+ subprocess.run(command, cwd=ROOT, check=True)
|
|
|
+
|
|
|
+
|
|
|
+def generate(dry_run: bool = False, check: bool = False, no_validate: bool = False) -> bool:
|
|
|
+ """Generate the dialect. Returns True when the committed file was updated."""
|
|
|
+ check_sources()
|
|
|
+ previous = _sha256(OUTPUT) if OUTPUT.is_file() else None
|
|
|
+ with tempfile.TemporaryDirectory(prefix="mavgen-") as directory:
|
|
|
+ candidate = Path(directory) / "VKFly.py"
|
|
|
+ mavgen(["--no-validate"] if no_validate else [], candidate)
|
|
|
+ generated = candidate.read_bytes()
|
|
|
+ if check:
|
|
|
+ if previous is None:
|
|
|
+ raise SystemExit(f"{OUTPUT} does not exist; run: python tools/gen_dialect.py")
|
|
|
+ if _sha256(OUTPUT) != hashlib.sha256(generated).hexdigest():
|
|
|
+ raise SystemExit(
|
|
|
+ f"{OUTPUT} is stale. Regenerate with: python tools/gen_dialect.py"
|
|
|
+ )
|
|
|
+ LOG.info("generated dialect matches the submodule XML")
|
|
|
+ return False
|
|
|
+ if previous == hashlib.sha256(generated).hexdigest():
|
|
|
+ LOG.info("generated dialect already up to date")
|
|
|
+ return False
|
|
|
+ if dry_run:
|
|
|
+ LOG.info("dry run: %s would be regenerated", OUTPUT)
|
|
|
+ return True
|
|
|
+ OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
|
|
+ OUTPUT.write_bytes(generated)
|
|
|
+ LOG.info("wrote %s (%d bytes)", OUTPUT, len(generated))
|
|
|
+ return True
|
|
|
+
|
|
|
+
|
|
|
+def main() -> None:
|
|
|
+ parser = argparse.ArgumentParser(description=__doc__)
|
|
|
+ parser.add_argument("--check", action="store_true",
|
|
|
+ help="verify the committed file matches the XML; do not write")
|
|
|
+ parser.add_argument("--dry-run", action="store_true",
|
|
|
+ help="report whether regeneration is needed; do not write")
|
|
|
+ parser.add_argument("--no-validate", action="store_true",
|
|
|
+ help="pass --no-validate to mavgen (skips upstream enum warnings)")
|
|
|
+ parser.add_argument("--quiet", action="store_true")
|
|
|
+ args = parser.parse_args()
|
|
|
+ logging.basicConfig(
|
|
|
+ level=logging.WARNING if args.quiet else logging.INFO,
|
|
|
+ format="%(levelname)s %(name)s: %(message)s",
|
|
|
+ )
|
|
|
+ try:
|
|
|
+ generate(dry_run=args.dry_run, check=args.check, no_validate=args.no_validate)
|
|
|
+ except subprocess.CalledProcessError as exc:
|
|
|
+ raise SystemExit(f"mavgen failed with exit code {exc.returncode}")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|