app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
← Commit history
1.0.58: Linux build in the same zip (wiki #127/#128: Flatpak KiCad, kicad-cli verbs, IPC live verbs; window verbs not yet). kicad_silk_fields_batch updates the parent footprints when KiCad refuses a field update (#129). Drill files with every Gerber export, kicad_export_pos (JLCPCB CPL), kicad_render_board, board-area SVG with background. Fixes #120 (read-only verbs leave dialogs open), #121 (Zoom to Selected Objects hint, ambiguous menu labels refused), #122 (same-name boards), #123 (PCM build-a-repository docs), #124 (DRC snapshot keeps project libraries), #125 (courtyard check uses real outlines).
1 file changed
+32−4
tools/build_native_release.py+32−4@@ -29,7 +29,7 @@ TARGET = "x86_64-pc-windows-gnu" 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."},+ "linux": {"supported": True, "notes": "x86_64 kicad-bridge (glibc 2.39+: Ubuntu 24.04, Debian 13 and newer) ships in the same zip. kicad-cli verbs, file edits and the KiCad IPC API work; window verbs (screenshots, keys, menus) answer not_supported_on_platform for now. KiCad from the distro, snap or Flatpak (kicad_upgrade installs the Flathub app with --user). Wiki #127."}, } def authenticode_size(path):@@ -94,6 +94,23 @@ def main(): if publish and not re.fullmatch(r"\d+\.\d+\.\d+", ver): sys.exit(f"REFUSING: {ver} is not a plain x.y.z; set [workspace.package].version in rust/Cargo.toml before publishing (ab and the tiers want one plain semver per build)") print(f"native kicad bridge {ver}")+ dist = RUST / "dist"+ zpath = dist / f"adom-bridge-kicad-native-v{ver}.zip"+ if "--reuse-zip" in sys.argv:+ # Ship the exact zip that was staged and verified on a desktop; never a rebuild of it.+ if not zpath.is_file():+ sys.exit(f"REFUSING: --reuse-zip but {zpath} does not exist")+ with zipfile.ZipFile(zpath) as z:+ verbs = json.loads(z.read("bridge.json"))["verbs"]+ inner = dist / "stage-check.exe"; inner.write_bytes(z.read("kicad-bridge.exe"))+ if "kicad-bridge" not in z.namelist():+ sys.exit("REFUSING: the zip has no Linux binary")+ ok = authenticode_size(inner) > 0; inner.unlink()+ if publish and not ok:+ sys.exit("REFUSING: the exe inside the reused zip is unsigned")+ sha = hashlib.sha256(zpath.read_bytes()).hexdigest()+ print(f"reusing {zpath.name} ({len(verbs)} verbs, sha256 {sha})")+ return publish_zip(ver, zpath, sha, notes) if publish else None sh(["cargo", "build", "--release"], cwd=RUST) sh(["cargo", "build", "--release", "--target", TARGET], cwd=RUST) exe = RUST / "target" / TARGET / "release" / "kicad-bridge.exe"@@ -104,7 +121,8 @@ def main(): 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"}+ # One zip, both binaries (wiki #127): ab 2.1.175+ picks this OS's spawn.entrypoints value.+ manifest["spawn"] = {"kind": "exe", "entrypoint": "kicad-bridge.exe", "entrypoints": {"windows": "kicad-bridge.exe", "linux": "kicad-bridge"}, "port": 0, "healthEndpoint": "/status", "stopMethod": "kill", "killImageName": "kicad-bridge.exe"} manifest.pop("dependencies", None) manifest["verbs"] = verbs manifest["runtime"] = "native"@@ -112,6 +130,9 @@ def main(): 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")+ linux_bin = RUST / "target" / "release" / "kicad-bridge"+ shutil.copy2(linux_bin, stage / "kicad-bridge")+ os.chmod(stage / "kicad-bridge", 0o755) # SIGNING, the last change to the exe (anything after it would break the signature). Windows 11 # Smart App Control blocks unsigned exes, so a published build is ALWAYS signed as Adom # Industries, Inc.; the signer is a private tool outside this public repo (ADOM_SIGN_EXE,@@ -156,10 +177,14 @@ def main(): if not ok: sys.exit("REFUSING: the exe inside the zip is unsigned; nothing was published") print(f"exe signed: {'yes (Adom Industries, Inc.)' if signed else 'NO (local build only; --publish signs)'}")- 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(f"built {zpath.name}\n verbs : {len(verbs)}\n linux : {linux_bin.stat().st_size} bytes\n exe : {exe.stat().st_size} bytes\n zip : {zpath.stat().st_size} bytes\n sha256: {sha}") if not publish: print("not published (pass --publish --notes \"...\" to ship to insiders)") return+ publish_zip(ver, zpath, sha, notes)+++def publish_zip(ver, zpath, sha, notes): from datetime import datetime, timezone REF = "adom/kicad-bridge" # 1. The repo must tell the same story as the zip: push every rust source file.@@ -178,7 +203,10 @@ def main(): m.update(version=ver, sha256=sha, size=zpath.stat().st_size, url=f"https://wiki.adom.inc/download/{REF}/{ver}/{zpath.name}", released_at=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),- runtime="native", platforms=PLATFORMS)+ runtime="native")+ # ab reads every platforms entry as a download (url, sha256, size): one zip serves both OSes.+ dl = {"url": m["url"], "sha256": sha, "size": zpath.stat().st_size}+ m["platforms"] = {k: dict(v, **dl) for k, v in PLATFORMS.items() if v.get("supported")} mpath.write_text(json.dumps(m, indent=2) + "\n") # 4. INSIDERS tier only. The public manifest moves through tools/promote.py. ins = dict(m); ins["tier"] = "insiders"