diff --git a/build_libicsneo.py b/build_libicsneo.py index 13b1fc6f..93d7b16a 100644 --- a/build_libicsneo.py +++ b/build_libicsneo.py @@ -14,19 +14,28 @@ 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 -# (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" +# 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" 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" @@ -102,6 +111,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") @@ -111,9 +132,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. + *_cmake_ninja_args(), "-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 +150,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. + *_cmake_ninja_args(), "-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", ]