From ad1a1d402d47ed4bb9bde60155926dc5d2c25b7a Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Tue, 29 Sep 2026 13:28:33 +0100 Subject: [PATCH 1/6] FileSink: Don't force all settings when inputFrequencyOffset changes, as it resets the waterfall. --- plugins/channelrx/filesink/filesinkgui.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/channelrx/filesink/filesinkgui.cpp b/plugins/channelrx/filesink/filesinkgui.cpp index adbcb4c4a..5e754ff42 100644 --- a/plugins/channelrx/filesink/filesinkgui.cpp +++ b/plugins/channelrx/filesink/filesinkgui.cpp @@ -86,8 +86,12 @@ bool FileSinkGUI::handleMessage(const Message& message) if (m_fixedPosition) { + const qint32 inputFrequencyOffset = m_settings.m_inputFrequencyOffset; setFrequencyFromPos(); - applySettings(QStringList(), true); + + if (m_settings.m_inputFrequencyOffset != inputFrequencyOffset) { + applySettings(QStringList("inputFrequencyOffset")); + } } else { From c226cf5302f468c6e77e41ad57de6363109bffc3 Mon Sep 17 00:00:00 2001 From: Robin Getz Date: Tue, 29 Sep 2026 21:09:50 -0400 Subject: [PATCH 2/6] external/CMakeLists.txt: Fix invalid DEPENDS arguments Remove DEPENDS from add_custom_command(TARGET ...) calls. DEPENDS is supported by the OUTPUT form but not the TARGET form used here. CMake 3.30 and earlier silently ignored the unsupported argument. CMake 3.31 introduced CMP0175, which warns about invalid arguments. Signed-off-by: Robin Getz --- external/CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index eabfd9b1c..d46e8f716 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -110,21 +110,21 @@ if (LINUX) macro(makeLink src dest target) if (NOT EXISTS ${dest}) add_custom_command(TARGET ${target} PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${src} ${dest} DEPENDS ${dest} COMMENT "mklink ${src} -> ${dest}") + COMMAND ${CMAKE_COMMAND} -E create_symlink ${src} ${dest} COMMENT "mklink ${src} -> ${dest}") endif () endmacro() # macro that copy folder macro(makeCopy src dest target) if (NOT EXISTS ${dest}) add_custom_command(TARGET ${target} PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${src} ${dest} DEPENDS ${dest} COMMENT "copy ${src} -> ${dest}") + COMMAND ${CMAKE_COMMAND} -E copy_directory ${src} ${dest} COMMENT "copy ${src} -> ${dest}") endif () endmacro() # macro that copy file macro(makeCopyFile src dest target) if (NOT EXISTS ${dest}) add_custom_command(TARGET ${target} PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dest} DEPENDS ${dest} COMMENT "copy ${src} -> ${dest}") + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dest} COMMENT "copy ${src} -> ${dest}") endif () endmacro() else () @@ -132,7 +132,7 @@ else () macro(makeLink src dest target) if (NOT EXISTS ${dest}) add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${src} ${dest} DEPENDS ${dest} COMMENT "mklink ${src} -> ${dest}") + COMMAND ${CMAKE_COMMAND} -E create_symlink ${src} ${dest} COMMENT "mklink ${src} -> ${dest}") endif () endmacro() @@ -140,7 +140,7 @@ else () macro(makeCopyDir src dest target) if (NOT EXISTS ${dest}) add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${src} ${dest} DEPENDS ${dest} COMMENT "copy ${src} -> ${dest}") + COMMAND ${CMAKE_COMMAND} -E copy_directory ${src} ${dest} COMMENT "copy ${src} -> ${dest}") endif () endmacro() @@ -148,7 +148,7 @@ else () macro(makeCopy src dest target) if (NOT EXISTS ${dest}) add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dest} DEPENDS ${dest} COMMENT "copy ${src} -> ${dest}") + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dest} COMMENT "copy ${src} -> ${dest}") endif () endmacro() endif (LINUX) From e8950002fecac02836e441699e4b7e027e5b50e8 Mon Sep 17 00:00:00 2001 From: Robin Getz Date: Tue, 29 Sep 2026 21:55:15 -0400 Subject: [PATCH 3/6] external/CMakeLists.txt : Fix Boost FetchContent extraction timestamps Set DOWNLOAD_EXTRACT_TIMESTAMP to TRUE for the Boost download to use extraction timestamps and avoid CMP0135 warnings and stale file timestamps when the downloaded archive changes. Signed-off-by: Robin Getz --- external/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index d46e8f716..30f474dc9 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -370,6 +370,7 @@ endif (NOT FFTW3F_FOUND AND NOT USE_PRECOMPILED_LIBS) include(FetchContent) FetchContent_Declare(boost URL "https://sourceforge.net/projects/boost/files/boost/${BOOST_TAG}/boost_${BOOST_TAG2}.tar.gz/download" + DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) find_package(Boost QUIET) From be3af7019b208a611827e0d0bb9bfe132cbba67e Mon Sep 17 00:00:00 2001 From: Robin Getz Date: Tue, 29 Sep 2026 22:28:52 -0400 Subject: [PATCH 4/6] external/CMakeLists.txt: Fix CSPICE & BladeRF library path for no-build-type builds Use CSPICE's & BladeRF's binary-tree library path when CMAKE_BUILD_TYPE is None, while retaining SDRangel's external library path for configured builds. Signed-off-by: Robin Getz --- external/CMakeLists.txt | 49 ++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 30f474dc9..903861a43 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -896,14 +896,24 @@ if(ENABLE_FEATURE_STARTRACKER) if (WIN32) set(CSPICE_LIBRARIES "${SDRANGEL_BINARY_LIB_DIR}/cspice.lib" CACHE INTERNAL "") elseif (LINUX) - # We need a shared library on Linux, as the static library has non PIC code, and we need to link it into a shared library - # ExternalProject cspice currently outputs libcspice.so under - # ${EXTERNAL_BUILD_LIBRARIES}/cspice/src/cspice-build/lib/libcspice.so - # so point CSPICE_LIBRARIES there. - set(CSPICE_LIBRARIES - "${EXTERNAL_BUILD_LIBRARIES}/lib${LIB_SUFFIX}/libcspice.so" - CACHE INTERNAL "" - ) + # We need a shared library on Linux, as the static library has non PIC code, + # and we need to link it into a shared library + if (CMAKE_BUILD_TYPE STREQUAL "None") + # ExternalProject cspice currently outputs libcspice.so under + # ${EXTERNAL_BUILD_LIBRARIES}/cspice/src/cspice-build/lib/libcspice.so + # so point CSPICE_LIBRARIES there. + set(CSPICE_LIBRARIES + "${EXTERNAL_BUILD_LIBRARIES}/cspice/src/cspice-build/lib/libcspice.so" + CACHE INTERNAL "" + ) + else() + # Release, Debug, and RelWithDebInfo have configuration-specific output + # directories supplied by SDRangel. + set(CSPICE_LIBRARIES + "${EXTERNAL_BUILD_LIBRARIES}/lib${LIB_SUFFIX}/libcspice.so" + CACHE INTERNAL "" + ) + endif() # Disable -Werror=format-security for cspice only: distro hardening flags (e.g. Ubuntu Noble) # inject -Werror=format-security which fails cspice sources that lack -Wformat. set(CSPICE_ARGS "-DCSPICE_BUILD_STATIC_LIBRARY=OFF" @@ -1227,8 +1237,27 @@ if (LINUX) if (ENABLE_BLADERF AND (NOT LIBBLADERF_FOUND OR LIBBLADERF_EXTERNAL)) set_global_cache(LIBBLADERF_FOUND ON) set(LIBBLADERF_EXTERNAL ON CACHE INTERNAL "") - set(LIBBLADERF_INCLUDE_DIRS "${EXTERNAL_BUILD_LIBRARIES}/bladerf/src/bladerf/host/libraries/libbladeRF/include" CACHE INTERNAL "") - set(LIBBLADERF_LIBRARIES "${EXTERNAL_BUILD_LIBRARIES}/lib${LIB_SUFFIX}/libbladeRF.so.2" CACHE INTERNAL "") + set(LIBBLADERF_INCLUDE_DIRS + "${EXTERNAL_BUILD_LIBRARIES}/bladerf/src/bladerf/host/libraries/libbladeRF/include" + CACHE INTERNAL "" + ) + if (CMAKE_BUILD_TYPE STREQUAL "None") + # ExternalProject bladerf currently outputs libbladeRF.so.2 under + # ${EXTERNAL_BUILD_LIBRARIES}/bladerf/src/bladerf-build/output/libbladeRF.so.2 + # so point LIBBLADERF_LIBRARIES there. + set(LIBBLADERF_LIBRARIES + "${EXTERNAL_BUILD_LIBRARIES}/bladerf/src/bladerf-build/output/libbladeRF.so.2" + CACHE INTERNAL "" + ) + else() + # Release, Debug, and RelWithDebInfo have configuration-specific output + # directories supplied by SDRangel. + set(LIBBLADERF_LIBRARIES + "${EXTERNAL_BUILD_LIBRARIES}/lib${LIB_SUFFIX}/libbladeRF.so.2" + CACHE INTERNAL "" + ) + endif() + ExternalProject_Add(bladerf GIT_REPOSITORY https://github.com/Nuand/bladeRF.git GIT_TAG ${BLADERF_TAG} From 655753d9b90af6a25130def54bbb049b593c1513 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Wed, 30 Sep 2026 23:13:57 +0100 Subject: [PATCH 5/6] Attempt to fix Windows github action hang. Possibly due to positioning asking for user permission. --- .../feature/mcpserver/bridge/CMakeLists.txt | 16 ++++++- .../mcpserver/bridge/run-tools-dump.cmake | 33 +++++++++++++ plugins/feature/mcpserver/toolsdump.cpp | 47 +++++++++++++++---- 3 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 plugins/feature/mcpserver/bridge/run-tools-dump.cmake diff --git a/plugins/feature/mcpserver/bridge/CMakeLists.txt b/plugins/feature/mcpserver/bridge/CMakeLists.txt index 23f9d996c..10b89c41a 100644 --- a/plugins/feature/mcpserver/bridge/CMakeLists.txt +++ b/plugins/feature/mcpserver/bridge/CMakeLists.txt @@ -93,6 +93,11 @@ if(BUILD_MCP_BUNDLE) set_target_properties(sdrangel-mcp-tools-dump PROPERTIES BUILD_RPATH "${MACOS_EXTERNAL_LIBS_FIXUP}") endif() + set(MCPTOOLSDUMP_TIMEOUT 60 CACHE STRING "Timeout in seconds for generating the MCP tool list") + if(NOT MCPTOOLSDUMP_TIMEOUT MATCHES "^[1-9][0-9]*$") + message(FATAL_ERROR "MCPTOOLSDUMP_TIMEOUT must be a positive integer") + endif() + set(MCPBRIDGE_BUNDLE_DIR "${CMAKE_CURRENT_BINARY_DIR}/bundle") # Alongside the built binaries, which is bin/ on Windows and the build directory itself # elsewhere. That is also where the running application looks for it in a build tree @@ -109,14 +114,23 @@ if(BUILD_MCP_BUNDLE) COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/cmake/cpack/sdrangel_icon.png" "${MCPBRIDGE_BUNDLE_DIR}/icon.png" # The tool list, straight from the server's own registry, beside the bridge that serves # it when SDRangel is not up - COMMAND $ "${MCPBRIDGE_BUNDLE_DIR}/server/tools.json" + COMMAND ${CMAKE_COMMAND} + "-DTOOLSDUMP=$" + "-DTOOLS_JSON=${MCPBRIDGE_BUNDLE_DIR}/server/tools.json" + "-DTOOLSDUMP_TIMEOUT=${MCPTOOLSDUMP_TIMEOUT}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/run-tools-dump.cmake" COMMAND ${CMAKE_COMMAND} -E rm -f "${MCPBRIDGE_BUNDLE_FILE}" # Relative paths and a working directory, so that manifest.json lands at the root of # the archive, which is where the installer looks for it + COMMAND ${CMAKE_COMMAND} -E echo "mcpbundle: archiving ${MCPBRIDGE_BUNDLE_FILE}" COMMAND ${CMAKE_COMMAND} -E tar cf "${MCPBRIDGE_BUNDLE_FILE}" --format=zip -- manifest.json icon.png server + COMMAND ${CMAKE_COMMAND} -E echo "mcpbundle: archive complete" WORKING_DIRECTORY "${MCPBRIDGE_BUNDLE_DIR}" DEPENDS sdrangel-mcp-bridge sdrangel-mcp-tools-dump "${MCPBRIDGE_BUNDLE_DIR}/manifest.json" COMMENT "Packaging ${MCPBRIDGE_BUNDLE_FILE}" + # Stream diagnostics to GitHub Actions even while the helper is still running. + # Otherwise Ninja buffers them until the entire bundle command finishes. + USES_TERMINAL VERBATIM ) diff --git a/plugins/feature/mcpserver/bridge/run-tools-dump.cmake b/plugins/feature/mcpserver/bridge/run-tools-dump.cmake new file mode 100644 index 000000000..d828fb678 --- /dev/null +++ b/plugins/feature/mcpserver/bridge/run-tools-dump.cmake @@ -0,0 +1,33 @@ +# Run at build time, with the parent custom target's terminal attached. Keep stdout/stderr +# inherited so the last checkpoint is visible even if the process never exits. +foreach(required TOOLSDUMP TOOLS_JSON TOOLSDUMP_TIMEOUT) + if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "") + message(FATAL_ERROR "Missing ${required} for MCP tool list generation") + endif() +endforeach() + +if(NOT TOOLSDUMP_TIMEOUT MATCHES "^[1-9][0-9]*$") + message(FATAL_ERROR "TOOLSDUMP_TIMEOUT must be a positive integer") +endif() + +message(STATUS "mcpbundle: starting ${TOOLSDUMP}") +message(STATUS "mcpbundle: output ${TOOLS_JSON}; timeout ${TOOLSDUMP_TIMEOUT}s") +get_filename_component(toolsdump_working_directory "${TOOLS_JSON}" DIRECTORY) +execute_process( + COMMAND "${TOOLSDUMP}" "${TOOLS_JSON}" + WORKING_DIRECTORY "${toolsdump_working_directory}" + TIMEOUT "${TOOLSDUMP_TIMEOUT}" + RESULT_VARIABLE toolsdump_result +) + +if(NOT "${toolsdump_result}" STREQUAL "0") + message(FATAL_ERROR + "MCP tool list generation failed: ${toolsdump_result}. " + "See the last toolsdump checkpoint above; no 'entered main' checkpoint points to " + "executable/DLL startup, and 'initializing MainCore' points to runtime service initialization.") +endif() + +if(NOT EXISTS "${TOOLS_JSON}") + message(FATAL_ERROR "MCP tool list generator exited successfully without creating ${TOOLS_JSON}") +endif() +message(STATUS "mcpbundle: tool list generation complete") diff --git a/plugins/feature/mcpserver/toolsdump.cpp b/plugins/feature/mcpserver/toolsdump.cpp index 65dbc0e43..5e5c886e8 100644 --- a/plugins/feature/mcpserver/toolsdump.cpp +++ b/plugins/feature/mcpserver/toolsdump.cpp @@ -21,8 +21,9 @@ // the tool list once and does not ask again, so the bridge has to be able to answer with the // tools this very build of the server provides, before it has ever spoken to it. // -// The registry is metadata and handler functions: building it touches neither the Web API nor -// any device, so a null adapter is all it needs. +// A null adapter prevents Web API calls, but MCPTools members still initialize MainCore, +// including its pipe workers and platform location/permission services. Log that separately +// from registry construction, since it can block on a headless CI runner. #include #include @@ -33,13 +34,23 @@ #include #include +#include #include #include +#include "maincore.h" #include "mcptools.h" int main(int argc, char *argv[]) { + // Ninja and CI capture stderr through pipes; publish each checkpoint immediately. + std::setvbuf(stderr, nullptr, _IONBF, 0); +#ifdef _WIN32 + std::fprintf(stderr, "toolsdump: entered main (pid %lu)\n", GetCurrentProcessId()); +#else + std::fprintf(stderr, "toolsdump: entered main\n"); +#endif + std::fprintf(stderr, "toolsdump: creating QCoreApplication\n"); QCoreApplication application(argc, argv); if (argc < 2) @@ -49,35 +60,55 @@ int main(int argc, char *argv[]) } std::fprintf(stderr, "toolsdump: application up\n"); + + // MainCore starts position updates as it is constructed, and the Windows position plugin + // asks the system for location access and spins until it has an answer. On a machine where + // nobody has ever answered, such as a CI runner, that is for ever. Nothing here needs a + // plugin, so leave Qt nowhere to find one and MainCore carries on without a position source + QCoreApplication::setLibraryPaths(QStringList()); + + std::fprintf(stderr, "toolsdump: initializing MainCore (pipe workers, permissions, positioning)\n"); + MainCore::instance(); + std::fprintf(stderr, "toolsdump: MainCore ready; constructing registry\n"); MCPTools tools(nullptr); std::fprintf(stderr, "toolsdump: registry built\n"); QJsonObject result; + std::fprintf(stderr, "toolsdump: listing tools\n"); result["tools"] = tools.listTools(); std::fprintf(stderr, "toolsdump: listed\n"); QFile file(QString::fromLocal8Bit(argv[1])); + std::fprintf(stderr, "toolsdump: opening %s\n", argv[1]); + if (!file.open(QIODevice::WriteOnly)) { std::fprintf(stderr, "cannot write %s: %s\n", argv[1], qPrintable(file.errorString())); return 1; } + std::fprintf(stderr, "toolsdump: writing JSON\n"); file.write(QJsonDocument(result).toJson(QJsonDocument::Compact)); + std::fprintf(stderr, "toolsdump: closing output\n"); file.close(); std::fprintf(stderr, "toolsdump: written\n"); - std::fprintf(stderr, "%s: %d tools\n", argv[1], result["tools"].toArray().size()); + std::fprintf(stderr, "%s: %lld tools\n", argv[1], + static_cast(result["tools"].toArray().size())); - // The registry brings background threads up with it and they do not all stop when main - // returns, which leaves the process, and so the build, hanging. Even _Exit does not get - // out, since the runtime's own teardown waits on them, so end the process from the kernel - // side. The file is written and flushed by this point + // Preserve the existing teardown workaround: MainCore has background workers, and + // this one-shot helper never runs the Qt event loop. This does not protect against + // a hang during initialization; the build target applies a separate process timeout. + std::fprintf(stderr, "toolsdump: flushing streams before process exit\n"); std::fflush(nullptr); #ifdef _WIN32 - TerminateProcess(GetCurrentProcess(), 0); + std::fprintf(stderr, "toolsdump: calling TerminateProcess\n"); + if (!TerminateProcess(GetCurrentProcess(), 0)) { + std::fprintf(stderr, "toolsdump: TerminateProcess failed: %lu\n", GetLastError()); + } #endif + std::fprintf(stderr, "toolsdump: calling _Exit\n"); std::_Exit(0); } From df2b7a10c03a4d960431a4d7e746c48876036d9b Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Thu, 1 Oct 2026 08:06:05 +0100 Subject: [PATCH 6/6] Remove default text, so not contradictory. For #2973 --- plugins/feature/mcpserver/bridge/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/feature/mcpserver/bridge/readme.md b/plugins/feature/mcpserver/bridge/readme.md index bed9d79ae..b81bb172f 100644 --- a/plugins/feature/mcpserver/bridge/readme.md +++ b/plugins/feature/mcpserver/bridge/readme.md @@ -36,7 +36,7 @@ CMake builds one on its own; the `mcpb` CLI only adds validation and signing, an npm to build SDRangel would be a poor trade for that. The bundle is not signed, so Claude warns that the publisher is unverified when it is installed. -Both options default to off, so a normal build is untouched. With `BUILD_MCP_BUNDLE=ON` the +With `BUILD_MCP_BUNDLE=ON` the bundle is part of `all`, so building the Windows installer picks it up with nothing else to remember: