← All Pull Requests

kicad-lane: 1.0.97, tested on KiCad 10.0.8; releases record the KiCad they were tested on #59

Merged opened by Colby Knox 2026-10-31

1.0.97: tested on KiCad 10.0.8, and every build now records the KiCad its battery ran on.

  • KiCad 10.0.8: the test laptop was updated from 10.0.7 through the bridge's own kicad_tool_update apply (the official installer, per user, no UAC). This build's full battery ran on 10.0.8.
  • Release records the tested KiCad: build_native_release.py --battery refuses a failed, partial or foreground-grabbing run, and refuses a run that tested another bridge version. Otherwise it writes the result:
    • into the manifest as testedWith {kicad, battery, target, at}, which carries to public on promotion;
    • into the release notes.
  • kicad_readiness: testedKicad is the KiCad this build's battery passed on (null in a dev build). newerThanTested is true when the installed KiCad is newer.
  • The battery report records the KiCad and bridge it tested (report.json "tested", and the report's heading).

Battery 355 pass / 0 fail / 1 skip on KiCad 10.0.8 (bridge 1.0.97), no coverage gap, no foreground grabs.

🤖 Generated with Claude Code

Diff Skip to comments

--- a/rust/Cargo.lock+++ b/rust/Cargo.lock@@ -1,1307 +1,1308 @@⋯ 318 unchanged lines ⋯  [[package]] name = "kicad-bridge"-version = "1.0.96"+version = "1.0.97" dependencies = [  "adom-eda-checks",  "kicad-core",⋯ 5 unchanged lines ⋯  [[package]] name = "kicad-core"-version = "1.0.96"+version = "1.0.97" dependencies = [  "kicad-ipc-rs",  "kicad-platform",⋯ 20 unchanged lines ⋯  [[package]] name = "kicad-platform"-version = "1.0.96"+version = "1.0.97" dependencies = [  "serde",  "serde_json",⋯ 940 unchanged lines ⋯ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"+--- a/rust/Cargo.toml+++ b/rust/Cargo.toml@@ -1,27 +1,28 @@⋯ 2 unchanged lines ⋯ members = ["crates/*"]  [workspace.package]-version = "1.0.96"+version = "1.0.97" edition = "2021" license = "MIT" publish = false⋯ 15 unchanged lines ⋯ codegen-units = 1 strip = true panic = "abort"+--- a/rust/crates/kicad-bridge/src/verbs.rs+++ b/rust/crates/kicad-bridge/src/verbs.rs@@ -1,656 +1,687 @@⋯ 158 unchanged lines ⋯     }) } +/// The KiCad version this build's battery passed on, set by tools/build_native_release.py --battery (None in a dev+/// build).+pub const TESTED_KICAD: Option<&str> = option_env!("ADOM_TESTED_KICAD");++/// True when version `a` (10.0.9) is newer than `b` (10.0.8), number by number.+fn newer(a: &str, b: &str) -> bool {+    let n = |s: &str| s.split(|c: char| !c.is_ascii_digit()).filter(|x| !x.is_empty()).map(|x| x.parse::<u64>().unwrap_or(0)).collect::<Vec<_>>();+    n(a) > n(b)+}+ fn readiness(state: &mut State) -> Value {     let info = state.refresh();     let mut v = json!({⋯ 5 unchanged lines ⋯         "configDir": info.primary().and_then(|p| detect::config_dir(&p.version)).map(|p| p.to_string_lossy().replace('\\', "/")),         "runtime": {"kind": "native", "provisioned": false},         "platform": native().capabilities(),+        "testedKicad": TESTED_KICAD,     });+    // a KiCad newer than the one this build's battery ran on works as far as KiCad kept its formats and API; say so+    let installed = info.primary().map(|i| if i.cli_version.is_empty() { i.version.clone() } else { i.cli_version.clone() });+    if let (Some(t), Some(i)) = (TESTED_KICAD, installed.as_deref()) {+        v["newerThanTested"] = json!(newer(i, t));+    }     if !info.installed {         v["errorCode"] = json!("not_installed");         v["alternatives"] = alternatives();⋯ 477 unchanged lines ⋯         _ => catalog.into(),     } }++#[cfg(test)]+mod tested_tests {+    use super::newer;++    #[test]+    fn versions_compare_number_by_number() {+        assert!(newer("10.0.9", "10.0.8"));+        assert!(newer("10.1.0", "10.0.10"));+        assert!(!newer("10.0.8", "10.0.8"));+        assert!(!newer("9.0.7", "10.0.8"));+        assert!(newer("10.0.10", "10.0.9"), "10 is not less than 9 as text would have it");+    }+}+--- a/skills/kicad-bridge-test/run_verb_tests.py+++ b/skills/kicad-bridge-test/run_verb_tests.py@@ -1,2021 +1,2028 @@⋯ 650 unchanged lines ⋯         ab(a.target, "kicad_close", {"force": True, "reason": "battery --close-open-kicad: the open editors are known to be disposable"}, timeout=120)         time.sleep(4) +    # the KiCad and the bridge this run tests: the release records them (testedWith)+    _lv = ab(a.target, "kicad_list_versions", {}, timeout=120)+    _inst = (_lv.get("installs") or _lv.get("versions") or [{}])[0]+    tested = {"kicad": _inst.get("cliVersion") or _inst.get("version"), "kicadSource": _inst.get("source"), "bridge": _lv.get("bridgeVersion")}+    print(f"[tested] KiCad {tested['kicad']} ({tested['kicadSource']}), bridge {tested['bridge']}")     # phase 0: push fixtures     print(f"[0] pushing rp2040 fixture to {base} ...")     files = [str(PACK / f) for f in ("rp2040-breakout.kicad_pcb", "rp2040-breakout.kicad_sch",⋯ 1332 unchanged lines ⋯     passes = [v for v, r in results.items() if r["result"] == "PASS"]     skips = [v for v, r in results.items() if r["result"] == "SKIP"]     report = {-        "target": a.target, "caller": AI_THREAD, "started": datetime.now(timezone.utc).isoformat(),+        "target": a.target, "caller": AI_THREAD, "started": datetime.now(timezone.utc).isoformat(), "tested": tested,         "elapsed_s": round(time.time() - t0, 1),         "totals": {"pass": len(passes), "fail": len(fails), "skip": len(skips)},         "coverage_gap": missing, "failures": fails, "results": results,⋯ 3 unchanged lines ⋯     }     (outdir / "report.json").write_text(json.dumps(report, indent=2))     md = [f"# KiCad bridge verb test — {a.target} — {report['started'][:16]}",+          f"Bridge {tested.get('bridge')} on KiCad {tested.get('kicad')}.",           f"**{len(passes)} pass / {len(fails)} fail / {len(skips)} skip** in {report['elapsed_s']}s", ""]     for v in sorted(results):         r = results[v]⋯ 14 unchanged lines ⋯  if __name__ == "__main__":     sys.exit(main())+--- a/tools/build_native_release.py+++ b/tools/build_native_release.py@@ -1,312 +1,336 @@⋯ 92 unchanged lines ⋯     if publish and not notes.strip():         sys.exit("REFUSING: --publish needs --notes \"what changed\" (the page reuses it as the changelog)")     ver = version()+    # --battery <dir or report.json>: the run that tested this build. Its KiCad version goes into the binary+    # (kicad_readiness), the manifest (testedWith) and the notes; a failed or partial run refuses the release.+    tested = None+    if "--battery" in sys.argv:+        bp = Path(sys.argv[sys.argv.index("--battery") + 1])+        rep = json.loads(((bp / "report.json") if bp.is_dir() else bp).read_text())+        t, tot = rep.get("tested") or {}, rep.get("totals") or {}+        if tot.get("fail") or rep.get("coverage_gap") or rep.get("foreground_grabs"):+            sys.exit(f"REFUSING: the battery in {bp} failed ({tot.get('fail')} fail, coverage gap {rep.get('coverage_gap')}, grabs {len(rep.get('foreground_grabs') or [])})")+        if not t.get("kicad"):+            sys.exit(f"REFUSING: the battery in {bp} does not record the KiCad it ran on (run_verb_tests.py from 1.0.97 does)")+        if t.get("bridge") and t["bridge"] != ver:+            sys.exit(f"REFUSING: the battery tested bridge {t['bridge']}, not {ver}")+        tested = {"kicad": t["kicad"], "battery": f"{tot.get('pass')}/{tot.get('fail')}/{tot.get('skip')}", "target": rep.get("target"), "at": rep.get("started")}+        os.environ["ADOM_TESTED_KICAD"] = t["kicad"]+        notes = (notes.rstrip() + f"\n\nTested on KiCad {t['kicad']} (battery {tested['battery']} pass/fail/skip on {tested['target']}).") if notes else notes+        print(f"tested on KiCad {t['kicad']}, battery {tested['battery']}")+    elif publish:+        print("WARNING: no --battery given: the release will not record which KiCad it was tested on")     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}")⋯ 13 unchanged lines ⋯             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+        return publish_zip(ver, zpath, sha, notes, tested) 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"⋯ 83 unchanged lines ⋯     if not publish:         print("not published (pass --publish --notes \"...\" to ship to insiders)")         return-    publish_zip(ver, zpath, sha, notes)+    publish_zip(ver, zpath, sha, notes, tested)  -def publish_zip(ver, zpath, sha, notes):+def publish_zip(ver, zpath, sha, notes, tested=None):     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.⋯ 15 unchanged lines ⋯              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")+    if tested:+        m["testedWith"] = tested+    else:+        m.pop("testedWith", None)     # 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")}⋯ 77 unchanged lines ⋯  if __name__ == "__main__":     main()+

Comments

No comments yet.

Log in to comment.