← Commit history

tools: build_native_release.py (unpublished native zip, spawn kind exe, verbs from describe)

John Lauer ·36d983d174 ·29d ago ·parent 877621d
2 files changed +83
rust/.gitignore+1
@@ -1 +1,2 @@ target/+dist/
tools/build_native_release.pyadded+82
@@ -0,0 +1,82 @@+#!/usr/bin/env python3+"""Build the native (Rust) kicad bridge zip for Windows. Does NOT publish.++  python3 tools/build_native_release.py            # builds, zips, prints sha256++Output: rust/dist/adom-bridge-kicad-native-v<ver>.zip containing kicad-bridge.exe,+bridge.json (spawn.kind exe, verbs generated from the binary's own kicad_describe) and+BRIDGE_VERSION. Version comes from rust/Cargo.toml [workspace.package].version.+The Python bridge keeps shipping through tools/build_release.py until the native binary+passes the verb runner; flipping the public manifest to this zip is a separate, human step.+"""+import hashlib, json, os, re, shutil, subprocess, sys, time, urllib.request, zipfile+from pathlib import Path++ROOT = Path(__file__).resolve().parent.parent+RUST = ROOT / "rust"+TARGET = "x86_64-pc-windows-gnu"++def sh(cmd, cwd=None):+    r = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)+    if r.returncode != 0:+        print(r.stdout[-2000:]); print(r.stderr[-4000:]); sys.exit(f"FAILED: {' '.join(cmd)}")+    return r.stdout++def version():+    m = re.search(r'^\[workspace\.package\]\s*\nversion\s*=\s*"([^"]+)"', (RUST / "Cargo.toml").read_text(), re.M)+    if not m: sys.exit("REFUSING: no workspace.package.version in rust/Cargo.toml")+    return m.group(1)++def describe_via_linux_binary():+    """Run the host build for a moment to read the verb list: bridge.json must list every verb or ab will not route it."""+    exe = RUST / "target/release/kicad-bridge"+    p = subprocess.Popen([str(exe), "--port", "47739"], stderr=subprocess.DEVNULL)+    try:+        for _ in range(40):+            try:+                req = urllib.request.Request("http://127.0.0.1:47739/command", data=b'{"command":"kicad_describe"}', headers={"Content-Type": "application/json"})+                with urllib.request.urlopen(req, timeout=2) as r:+                    return json.load(r)+            except Exception:+                time.sleep(0.1)+        sys.exit("REFUSING: the host binary never answered kicad_describe")+    finally:+        p.kill()++def main():+    ver = version()+    print(f"native kicad bridge {ver}")+    sh(["cargo", "build", "--release"], cwd=RUST)+    sh(["cargo", "build", "--release", "--target", TARGET], cwd=RUST)+    exe = RUST / "target" / TARGET / "release" / "kicad-bridge.exe"+    desc = describe_via_linux_binary()+    verbs = [v["name"] for v in desc["verbs"]]+    if "kicad_describe" not in verbs or "kicad_uninstall" not in verbs:+        sys.exit("REFUSING: describe or uninstall missing from the catalog")+    py = json.loads((ROOT / "bridge.json").read_text())+    manifest = dict(py)+    manifest["version"] = ver+    manifest["spawn"] = {"kind": "exe", "entrypoint": "kicad-bridge.exe", "port": 0, "healthEndpoint": "/status", "stopMethod": "kill", "killImageName": "kicad-bridge.exe"}+    manifest.pop("dependencies", None)+    manifest["verbs"] = verbs+    manifest["runtime"] = "native"+    manifest["platforms"] = {+        "windows": {"supported": True, "notes": "Pre-built x64 kicad-bridge.exe ships in the zip."},+        "macos": {"supported": False, "notes": "Kyle's build: cargo build --release on a Mac, crates/kicad-platform/src/macos.rs."},+        "linux": {"supported": False, "notes": "Barrett's build: cargo build --release on Ubuntu, crates/kicad-platform/src/linux.rs."},+    }+    dist = RUST / "dist"; dist.mkdir(exist_ok=True)+    stage = dist / "stage"; shutil.rmtree(stage, ignore_errors=True); stage.mkdir()+    shutil.copy2(exe, stage / "kicad-bridge.exe")+    (stage / "bridge.json").write_text(json.dumps(manifest, indent=2) + "\n")+    (stage / "BRIDGE_VERSION").write_text(ver + "\n")+    zpath = dist / f"adom-bridge-kicad-native-v{ver}.zip"+    with zipfile.ZipFile(zpath, "w", zipfile.ZIP_DEFLATED) as z:+        for f in sorted(stage.iterdir()):+            z.write(f, f.name)+    sha = hashlib.sha256(zpath.read_bytes()).hexdigest()+    print(f"built {zpath.name}\n  verbs : {len(verbs)}\n  exe   : {exe.stat().st_size} bytes\n  zip   : {zpath.stat().st_size} bytes\n  sha256: {sha}")+    print("not published: flip the manifest by hand once the verb runner passes on the native binary")++if __name__ == "__main__":+    main()