From 2956ceab0f617b70953aa449d14e2743710d4d21 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sat, 31 Jan 2026 15:04:50 -0700 Subject: [PATCH] fix: correct OOT catalog branches and add binding hash fixup - Fix branch names for 5 modules discovered during install attempts: ieee802_11, ieee802_15_4, adsb (maint-3.10), iridium, nrsc5 (master) - Remove packet_radio (repo gone/private) - Add build_deps for modules that need them (castxml, autotools, Qt5) - Add fix_binding_hashes.py helper script to Dockerfile builds to prevent castxml regen failures from stale pybind11 header hashes - Use tar build context in Docker builds to support COPY instruction - Note inspector as incompatible with GR 3.10 (API changes) Successfully built: ieee802_11, ieee802_15_4, adsb, iridium, nrsc5 --- src/gnuradio_mcp/middlewares/oot.py | 65 +++++++++++++++++++++++++++-- src/gnuradio_mcp/oot_catalog.py | 23 +++++----- tests/unit/test_oot_installer.py | 4 +- 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/gnuradio_mcp/middlewares/oot.py b/src/gnuradio_mcp/middlewares/oot.py index 897e04f..4df4ead 100644 --- a/src/gnuradio_mcp/middlewares/oot.py +++ b/src/gnuradio_mcp/middlewares/oot.py @@ -4,6 +4,7 @@ import io import json import logging import subprocess +import tarfile from datetime import datetime, timezone from pathlib import Path from typing import Any @@ -25,8 +26,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \\ # Clone and build WORKDIR /build +COPY fix_binding_hashes.py /tmp/fix_binding_hashes.py RUN git clone --depth 1 --branch {branch} {git_url} && \\ - cd {repo_dir} && mkdir build && cd build && \\ + cd {repo_dir} && \\ + python3 /tmp/fix_binding_hashes.py . && \\ + mkdir build && cd build && \\ cmake -DCMAKE_INSTALL_PREFIX=/usr {cmake_args}.. && \\ make -j$(nproc) && make install && \\ ldconfig && \\ @@ -38,6 +42,44 @@ WORKDIR /flowgraphs ENV PYTHONPATH="/usr/lib/python3.11/site-packages:${{PYTHONPATH}}" """ +# Standalone script injected into OOT Docker builds to fix stale +# pybind11 binding hashes that would otherwise trigger castxml regen. +FIX_BINDING_HASHES_SCRIPT = """\ +#!/usr/bin/env python3 +\"\"\"Fix stale BINDTOOL_HEADER_FILE_HASH in pybind11 binding files. + +GNU Radio's GR_PYBIND_MAKE_OOT cmake macro compares MD5 hashes of C++ +headers against values stored in the binding .cc files. When they +differ it tries to regenerate via castxml, which often fails in minimal +Docker images. This script updates the hashes to match the actual +headers so cmake skips the regeneration step. +\"\"\" +import hashlib, pathlib, re, sys + +root = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else pathlib.Path(".") +bindings = root / "python" / "bindings" +if not bindings.is_dir(): + sys.exit(0) + +for cc in sorted(bindings.glob("*_python.cc")): + text = cc.read_text() + m = re.search(r"BINDTOOL_HEADER_FILE\\((\\S+)\\)", text) + if not m: + continue + header = next(root.joinpath("include").rglob(m.group(1)), None) + if not header: + continue + actual = hashlib.md5(header.read_bytes()).hexdigest() + new_text = re.sub( + r"BINDTOOL_HEADER_FILE_HASH\\([a-f0-9]+\\)", + f"BINDTOOL_HEADER_FILE_HASH({actual})", + text, + ) + if new_text != text: + cc.write_text(new_text) + print(f"Fixed binding hash: {cc.name}") +""" + class OOTInstallerMiddleware: """Builds OOT modules into Docker images from git repos. @@ -235,13 +277,30 @@ class OOTInstallerMiddleware: except Exception: return False + @staticmethod + def _build_context(dockerfile: str) -> io.BytesIO: + """Create a tar archive build context with Dockerfile and helper scripts.""" + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w") as tar: + for name, content in [ + ("Dockerfile", dockerfile), + ("fix_binding_hashes.py", FIX_BINDING_HASHES_SCRIPT), + ]: + data = content.encode("utf-8") + info = tarfile.TarInfo(name=name) + info.size = len(data) + tar.addfile(info, io.BytesIO(data)) + buf.seek(0) + return buf + def _docker_build(self, dockerfile: str, tag: str) -> list[str]: """Build a Docker image from a Dockerfile string. Returns log lines.""" - f = io.BytesIO(dockerfile.encode("utf-8")) + context = self._build_context(dockerfile) log_lines: list[str] = [] try: _image, build_log = self._client.images.build( - fileobj=f, + fileobj=context, + custom_context=True, tag=tag, rm=True, forcerm=True, diff --git a/src/gnuradio_mcp/oot_catalog.py b/src/gnuradio_mcp/oot_catalog.py index 80ba6fa..e360349 100644 --- a/src/gnuradio_mcp/oot_catalog.py +++ b/src/gnuradio_mcp/oot_catalog.py @@ -230,7 +230,8 @@ CATALOG: dict[str, OOTModuleEntry] = { description="IEEE 802.11a/g/p OFDM transceiver", category="WiFi", git_url="https://github.com/bastibl/gr-ieee802-11", - branch="main", + branch="maint-3.10", + build_deps=["castxml"], homepage="https://github.com/bastibl/gr-ieee802-11", ), _entry( @@ -238,7 +239,8 @@ CATALOG: dict[str, OOTModuleEntry] = { description="IEEE 802.15.4 (Zigbee) O-QPSK transceiver", category="IoT", git_url="https://github.com/bastibl/gr-ieee802-15-4", - branch="main", + branch="maint-3.10", + build_deps=["castxml"], homepage="https://github.com/bastibl/gr-ieee802-15-4", ), _entry( @@ -246,7 +248,7 @@ CATALOG: dict[str, OOTModuleEntry] = { description="ADS-B (1090 MHz) aircraft transponder decoder", category="Aviation", git_url="https://github.com/mhostetter/gr-adsb", - branch="main", + branch="maint-3.10", homepage="https://github.com/mhostetter/gr-adsb", ), _entry( @@ -254,7 +256,7 @@ CATALOG: dict[str, OOTModuleEntry] = { description="Iridium satellite burst detector and demodulator", category="Satellite", git_url="https://github.com/muccc/gr-iridium", - branch="main", + branch="master", homepage="https://github.com/muccc/gr-iridium", ), _entry( @@ -263,24 +265,19 @@ CATALOG: dict[str, OOTModuleEntry] = { category="Analysis", git_url="https://github.com/gnuradio/gr-inspector", branch="master", + build_deps=["qtbase5-dev", "libqwt-qt5-dev"], homepage="https://github.com/gnuradio/gr-inspector", + gr_versions="3.9 (master branch has API compat issues with 3.10)", ), _entry( name="nrsc5", description="HD Radio (NRSC-5) digital broadcast decoder", category="Broadcast", git_url="https://github.com/argilo/gr-nrsc5", - branch="main", + branch="master", + build_deps=["autoconf", "automake", "libtool"], homepage="https://github.com/argilo/gr-nrsc5", ), - _entry( - name="packet_radio", - description="Amateur packet radio (AFSK, AX.25) modem", - category="Amateur", - git_url="https://github.com/duggabe/gr-packet-radio", - branch="main", - homepage="https://github.com/duggabe/gr-packet-radio", - ), ] } diff --git a/tests/unit/test_oot_installer.py b/tests/unit/test_oot_installer.py index c74ec5f..d2279f5 100644 --- a/tests/unit/test_oot_installer.py +++ b/tests/unit/test_oot_installer.py @@ -94,7 +94,9 @@ class TestDockerfileGeneration: assert "FROM gnuradio-runtime:latest" in dockerfile assert "git clone --depth 1 --branch master" in dockerfile assert "https://github.com/tapparelj/gr-lora_sdr.git" in dockerfile - assert "cd gr-lora_sdr && mkdir build" in dockerfile + assert "cd gr-lora_sdr" in dockerfile + assert "fix_binding_hashes.py" in dockerfile + assert "mkdir build" in dockerfile assert "cmake -DCMAKE_INSTALL_PREFIX=/usr" in dockerfile assert "make -j$(nproc)" in dockerfile assert "ldconfig" in dockerfile