From 9b5a8bb24139ff9fdca9bd7af7c220a9feb226a9 Mon Sep 17 00:00:00 2001 From: David Rebbe Date: Tue, 7 Jul 2026 16:52:08 -0400 Subject: [PATCH 1/3] Build libicsneo master: Ninja generator + shared icspb protobuf bootstrap libicsneo now requires icspb, whose protobuf codegen breaks under the default Unix Makefiles generator: protobuf v33's protobuf_generate() never creates PROTOC_OUT_DIR and only Ninja pre-creates declared output dirs, so protoc fails with '_deps/icspb-build/protos/: No such file or directory' (reproduced and A/B-verified in docker on 4ed29b4). - bump LIBICSNEO_VERSION to current master (4ed29b4) - configure with -G Ninja on Linux/macOS; add ninja to build requires - share ICSPB_BOOTSTRAP_DIR across cibuildwheel builds so protobuf compiles once per arch, not once per python (vital under QEMU aarch64) - macOS: pass OSX arch/deployment target via environment so icspb's nested protobuf bootstrap builds universal2 instead of arm64-only Verified locally: cp312-manylinux_x86_64 wheel builds end-to-end in docker; libicsneolegacy.so links icspb descriptors; wheel passes check-wheel-contents. Co-Authored-By: Claude Fable 5 --- build_libicsneo.py | 40 +++++++++++++++++++++++++++++++++------- pyproject.toml | 3 +++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/build_libicsneo.py b/build_libicsneo.py index 13b1fc6f..548c7dec 100644 --- a/build_libicsneo.py +++ b/build_libicsneo.py @@ -14,19 +14,29 @@ LIBUSB_BUILD = f"{LIBUSB_ROOT}/build" LIBUSB_INSTALL = f"{LIBUSB_ROOT}/install" -# NOTE: this pin's vendored icsnVC40.h predates Vspy 3.26.3.9 +# NOTE: this pin's vendored icsnVC40.h still predates Vspy 3.26.3.9 # (SRADGalaxy2Settings 840 vs 1204, no Comet3/Gigastar2), so Linux/macOS -# wheels lag the Windows ABI. Newer upstream doesn't help yet: master -# (4ed29b4, 2026-07-07) still has the old header AND adds a mandatory -# icspb protobuf dependency whose codegen fails on clean CI runners. -# Bump once upstream syncs the header and icspb builds. -LIBICSNEO_VERSION = "830fe1a" +# wheels lag the Windows ABI until upstream syncs the header; bump again +# when it does. libicsneo requires icspb (protobuf) as of 2026: its +# codegen needs the Ninja generator (protobuf_generate never creates +# PROTOC_OUT_DIR; Make doesn't pre-create declared output dirs, Ninja +# does), and it bootstraps protobuf from source at configure time — see +# ICSPB_BOOTSTRAP_DIR below. +LIBICSNEO_VERSION = "4ed29b4" LIBICSNEO_ROOT = f"{ROOT}/libicsneo/{LIBICSNEO_VERSION}" LIBICSNEO_SOURCE = f"{LIBICSNEO_ROOT}/source" LIBICSNEO_BUILD = f"{LIBICSNEO_ROOT}/build" LIBICSNEO_INSTALL = f"{LIBICSNEO_ROOT}/install" print(f"LIBICSNEO PATH: {LIBICSNEO_ROOT}") +# icspb bootstraps protobuf from source at configure time. Keep the +# bootstrap OUTSIDE the libicsneo build dir so it survives the per-python +# `git clean` in _build_libicsneo_linux and is reused across all +# cibuildwheel builds in a job (it self-partitions by -, +# so sharing one root across archs is safe). Building protobuf once per +# arch instead of once per python matters most under QEMU aarch64. +ICSPB_BOOTSTRAP_DIR = f"{ROOT}/icspb_bootstrap" + LIBPCAP_VERSION = "1.10.4" LIBPCAP_ROOT = f"{ROOT}/libpcap/{LIBPCAP_VERSION}" LIBPCAP_SOURCE = f"{LIBPCAP_ROOT}/source" @@ -111,9 +121,13 @@ def _build_libicsneo_linux(): subprocess.check_output( [ "cmake", + # Ninja is required: icspb's protobuf_generate never creates its + # output dir, and only Ninja pre-creates declared output dirs. + "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", "-DLIBICSNEO_BUILD_ICSNEOLEGACY=ON", f"-DCMAKE_PREFIX_PATH={LIBUSB_INSTALL};{LIBPCAP_INSTALL}", + f"-DICSPB_BOOTSTRAP_DIR={ICSPB_BOOTSTRAP_DIR}", "-S", LIBICSNEO_SOURCE, "-B", LIBICSNEO_BUILD, "-Wno-dev", @@ -125,17 +139,29 @@ def _build_libicsneo_linux(): ) def _build_libicsneo_macos(): + env = os.environ.copy() + # Set as env vars (not only -D flags) so icspb's nested protobuf + # bootstrap — a separate cmake invocation via execute_process, which + # inherits the environment but not our -D cache entries — also builds + # universal2 static libs instead of arm64-only ones. + env["CMAKE_OSX_ARCHITECTURES"] = "arm64;x86_64" + env["CMAKE_OSX_DEPLOYMENT_TARGET"] = "10.13" subprocess.check_output( [ "cmake", + # Ninja is required: icspb's protobuf_generate never creates its + # output dir, and only Ninja pre-creates declared output dirs. + "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64", "-DLIBICSNEO_BUILD_ICSNEOLEGACY=ON", "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13", f"-DCMAKE_PREFIX_PATH={LIBUSB_INSTALL};{LIBPCAP_INSTALL}", + f"-DICSPB_BOOTSTRAP_DIR={ICSPB_BOOTSTRAP_DIR}", "-S", LIBICSNEO_SOURCE, "-B", LIBICSNEO_BUILD - ] + ], + env=env, ) subprocess.check_output( diff --git a/pyproject.toml b/pyproject.toml index 9886c776..faea49e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,9 @@ requires = [ "wheel", "dunamai", "pytest>=8.4.2", + # Puts the ninja executable on the isolated build env's PATH; required by + # build_libicsneo.py's `cmake -G Ninja` on Linux/macOS (see comments there). + "ninja", ] From 6843f716134d2aed3ae9fa2eb131925082dfcdbc Mon Sep 17 00:00:00 2001 From: David Rebbe Date: Tue, 7 Jul 2026 17:20:00 -0400 Subject: [PATCH 2/3] Override cached CMAKE_MAKE_PROGRAM with the current build env's ninja cibuildwheel deletes each python's isolated build env, but the macOS libicsneo build dir is reused across pythons; CMake cached the previous env's ninja path and cp311+ configures failed with 'no such file or directory'. Reproduced the stale-cache mechanism in docker and verified that passing -DCMAKE_MAKE_PROGRAM= at configure fixes the reconfigure. (Ubuntu's failure in the same run was fail-fast collateral; Windows 'failure' was a cancelled job with every step green.) Co-Authored-By: Claude Fable 5 --- build_libicsneo.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/build_libicsneo.py b/build_libicsneo.py index 548c7dec..032c7891 100644 --- a/build_libicsneo.py +++ b/build_libicsneo.py @@ -112,6 +112,18 @@ def _build_libpcap(): subprocess.check_output(["make", "-j" + CPUS], cwd=LIBPCAP_BUILD) subprocess.check_output(["make", "install"], cwd=LIBPCAP_BUILD) +def _cmake_ninja_args(): + # pip's ninja lives inside each isolated build env, and cibuildwheel + # deletes that env between python versions. A reused CMake build dir + # caches the old (now dead) path in CMAKE_MAKE_PROGRAM and fails at + # the next configure, so always override with the current ninja. + ninja = shutil.which("ninja") + args = ["-G", "Ninja"] + if ninja: + args.append(f"-DCMAKE_MAKE_PROGRAM={ninja}") + return args + + def _build_libicsneo_linux(): print("Cleaning libicsneo...") subprocess.check_output(["git", "clean", "-xdf"], cwd="libicsneo") @@ -123,7 +135,7 @@ def _build_libicsneo_linux(): "cmake", # Ninja is required: icspb's protobuf_generate never creates its # output dir, and only Ninja pre-creates declared output dirs. - "-G", "Ninja", + *_cmake_ninja_args(), "-DCMAKE_BUILD_TYPE=Release", "-DLIBICSNEO_BUILD_ICSNEOLEGACY=ON", f"-DCMAKE_PREFIX_PATH={LIBUSB_INSTALL};{LIBPCAP_INSTALL}", @@ -151,7 +163,7 @@ def _build_libicsneo_macos(): "cmake", # Ninja is required: icspb's protobuf_generate never creates its # output dir, and only Ninja pre-creates declared output dirs. - "-G", "Ninja", + *_cmake_ninja_args(), "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64", "-DLIBICSNEO_BUILD_ICSNEOLEGACY=ON", From e3def7651962e2f3b0ff27f9f38a6c0f4feb9dab Mon Sep 17 00:00:00 2001 From: David Rebbe Date: Wed, 8 Jul 2026 10:22:28 -0400 Subject: [PATCH 3/3] Bump libicsneo to master 0dd8dbf: header synced to 3.26.3.9 Upstream's vendored icsnVC40.h is now byte-identical to this repo's (verified), closing the Linux/macOS ABI gap for the grown/new device settings structs (Galaxy2 1204, Comet3 918, Gigastar2 2400, GLOBAL_SETTINGS 2406). Co-Authored-By: Claude Fable 5 --- build_libicsneo.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/build_libicsneo.py b/build_libicsneo.py index 032c7891..93d7b16a 100644 --- a/build_libicsneo.py +++ b/build_libicsneo.py @@ -14,15 +14,14 @@ LIBUSB_BUILD = f"{LIBUSB_ROOT}/build" LIBUSB_INSTALL = f"{LIBUSB_ROOT}/install" -# NOTE: this pin's vendored icsnVC40.h still predates Vspy 3.26.3.9 -# (SRADGalaxy2Settings 840 vs 1204, no Comet3/Gigastar2), so Linux/macOS -# wheels lag the Windows ABI until upstream syncs the header; bump again -# when it does. libicsneo requires icspb (protobuf) as of 2026: its -# codegen needs the Ninja generator (protobuf_generate never creates -# PROTOC_OUT_DIR; Make doesn't pre-create declared output dirs, Ninja -# does), and it bootstraps protobuf from source at configure time — see -# ICSPB_BOOTSTRAP_DIR below. -LIBICSNEO_VERSION = "4ed29b4" +# libicsneo master 2026-07-08; its vendored icsnVC40.h is byte-identical +# to include/ics/icsnVC40.h (Vspy 3.26.3.9), so Linux/macOS wheels match +# the Windows ABI. Keep the two headers in lockstep on future bumps. +# libicsneo requires icspb (protobuf) as of 2026: its codegen needs the +# Ninja generator (protobuf_generate never creates PROTOC_OUT_DIR; Make +# doesn't pre-create declared output dirs, Ninja does), and it bootstraps +# protobuf from source at configure time — see ICSPB_BOOTSTRAP_DIR below. +LIBICSNEO_VERSION = "0dd8dbf" LIBICSNEO_ROOT = f"{ROOT}/libicsneo/{LIBICSNEO_VERSION}" LIBICSNEO_SOURCE = f"{LIBICSNEO_ROOT}/source" LIBICSNEO_BUILD = f"{LIBICSNEO_ROOT}/build"