Background (carried over from original issue)
PR #3046 vendored liquid-dsp under third_party/liquid-dsp/ proactively as DSP toolkit infrastructure (closes #3043). The vendor is Linux + macOS only — Windows MSVC builds skip it entirely. This issue tracks landing MSVC support.
Strategic context (2026-05-24): Windows is expected to become the dominant operator platform for AetherSDR. Cross-platform support is non-negotiable — we must land Windows liquid-dsp before that user base lands. Issue is reactivated as a real priority, not "wait for a consumer."
Lessons from PR #3073 (the clang-cl attempt that closed)
PR #3073 tried Path A from the original issue (clang-cl ExternalProject sub-build). It got further than expected before failing in a way that invalidates the whole clang-cl approach:
What worked
find_program(CLANG_CL_EXE) resolves correctly on GitHub Actions windows-latest (VS 2022 ships clang-cl under VC/Tools/Llvm/x64/bin/)
- ExternalProject_Add with
CMAKE_C_COMPILER=clang-cl.exe + CMAKE_CXX_COMPILER=clang-cl.exe passes the inner project's configure step (after we discovered both compilers must be consistent — Modules/Platform/Windows-Clang.cmake:168 enforces "use either clang/clang++ or clang-cl as all C, C++, CUDA")
- All SIMD probes complete (failing them is fine — liquid-dsp falls back to scalar paths)
Where it died
Inner build's first compilation step (agc_rrrf.c) failed with hundreds of errors of this shape:
liquid.internal.h(852,35): error: expected ')'
int liquid_cplxpair(float complex * _z,
^
liquid.internal.h(892,14): error: expected ';' after top level declarator
float complex ellip_cdf(float complex _u,
^
Why this kills clang-cl
clang-cl does support the C99 _Complex keyword. The failure is at a different layer: liquid-dsp's source uses float complex syntax — complex is a C99 macro that <complex.h> should #define ... _Complex.
- glibc / libc++'s
<complex.h> defines #define complex _Complex ✓
- MSVC's UCRT
<complex.h> does not define this macro. MSVC uses opaque _Fcomplex / _Dcomplex types with different signatures.
When clang-cl runs in --target=x86_64-pc-windows-msvc mode (which it does by default — that's the whole point of clang-cl), it inherits MSVC's include paths. liquid-dsp's #include <complex.h> therefore picks up MSVC's incompatible header.
Hacking past the type-declaration parse with -Dcomplex=_Complex was investigated and rejected: it solves the parser but not the function ABI. The 153 .c files that call crealf() / cimagf() / cabsf() / cargf() against C99 signatures would still fail at link time because those symbols don't exist in UCRT's C99 form — UCRT exports MSVC-conventioned _FCbuild / _FCrealf / etc.
Bottom line: clang-cl in MSVC ABI mode inherits MSVC's standard library. MSVC's standard library doesn't implement C99 <complex.h>. Therefore clang-cl can't compile liquid-dsp regardless of language-frontend support.
The right plan: mingw-w64 ExternalProject
mingw-w64-gcc is a full GCC port for Windows targeting windows-gnu (not windows-msvc). It has:
- Real C99
<complex.h> with the complex macro and standard crealf / cimagf / cabsf / cargf functions
- UCRT default on Win10+ (since mingw-w64 v7+) — matches AetherSDR's MSVC build, no CRT mismatch
- Static
.a output that MSVC's link.exe accepts directly (no .lib conversion step needed)
Implementation
1. Windows CI (.github/workflows/ci.yml + windows-installer.yml):
- name: Install mingw-w64
run: choco install mingw -y
Adds ~30s to Windows CI. mingw-w64 ships in the choco cache on GHA's windows-latest image already.
2. CMakeLists.txt — replace clang-cl ExternalProject with mingw-w64:
if(MSVC)
find_program(MINGW_GCC NAMES x86_64-w64-mingw32-gcc gcc
HINTS "C:/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin")
if(MINGW_GCC)
message(STATUS "liquid-dsp: building via mingw-w64 (${MINGW_GCC})")
include(ExternalProject)
set(LIQUID_INSTALL_DIR "${CMAKE_BINARY_DIR}/liquid-dsp-install")
file(MAKE_DIRECTORY "${LIQUID_INSTALL_DIR}/include")
ExternalProject_Add(liquid_dsp_external
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/liquid-dsp"
INSTALL_DIR "${LIQUID_INSTALL_DIR}"
CMAKE_GENERATOR "Ninja"
CMAKE_ARGS
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_SYSTEM_NAME=Windows
-DCMAKE_C_COMPILER=${MINGW_GCC}
# Static-link libgcc/libwinpthread runtime helpers into
# libliquid.a so MSVC link.exe doesn't need to resolve
# GCC-specific runtime symbols separately.
-DCMAKE_C_FLAGS=-static-libgcc
-DCMAKE_EXE_LINKER_FLAGS=-static-libgcc
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
-DBUILD_EXAMPLES=OFF
-DBUILD_AUTOTESTS=OFF
-DBUILD_BENCHMARKS=OFF
-DBUILD_SANDBOX=OFF
-DBUILD_DOC=OFF
-DBUILD_STATIC_LIBS=ON
-DBUILD_SHARED_LIBS=OFF
-DFIND_FFTW=OFF
-DENABLE_LOGGING=OFF
BUILD_BYPRODUCTS "<INSTALL_DIR>/lib/libliquid.a"
EXCLUDE_FROM_ALL TRUE
)
add_library(liquid-static STATIC IMPORTED GLOBAL)
set_target_properties(liquid-static PROPERTIES
IMPORTED_LOCATION "${LIQUID_INSTALL_DIR}/lib/libliquid.a"
INTERFACE_INCLUDE_DIRECTORIES "${LIQUID_INSTALL_DIR}/include")
add_dependencies(liquid-static liquid_dsp_external)
else()
message(STATUS "liquid-dsp: mingw-w64 not found on PATH; "
"Windows builds will omit liquid-dsp. Install via "
"'choco install mingw' or set MINGW_GCC.")
endif()
else()
# existing Linux/macOS path — add_subdirectory(third_party/liquid-dsp) unchanged
endif()
3. AetherSDR/MSVC link line:
link.exe reads libliquid.a (MinGW archive) directly. No conversion step. The static-libgcc flag ensures libgcc's runtime helpers (__chkstk, divide / modulo intrinsics, etc.) are baked into the archive so the final link sees no GCC-specific external symbols.
4. Developer documentation:
docs/build-windows.md and the top-level README get a note: "Building from source on Windows requires mingw-w64 for the bundled liquid-dsp library. Install via choco install mingw or omit liquid-dsp by passing -DENABLE_LIQUID_DSP=OFF to cmake."
Risks
- mingw-w64 install adds Windows CI dependency. If choco mirrors are down, build breaks. Mitigation: GHA caches the choco install across runs; pin to a specific mingw-w64 version known to default to UCRT.
- Local Windows developers need mingw-w64. Document in README; add
-DENABLE_LIQUID_DSP=OFF opt-out so Windows-only contributors who don't care about liquid-dsp can still build.
- CRT mismatch hypothetically. mingw-w64 ≥7 defaults to UCRT; AetherSDR/MSVC uses UCRT. Verify with
dumpbin /imports liquid-static.lib showing api-ms-win-crt-*.dll not msvcrt.dll. If mingw-w64 in CI somehow uses msvcrt, switch to -mcrtdll=ucrtbase explicitly.
- libgcc runtime helpers in the archive. Adds ~10-20KB to the final binary. Negligible.
- ABI boundaries. liquid-dsp is pure C with
{float, float}-layout complex types and no FILE* in its API. Static linking is safe. Verify before merging: grep liquid-dsp's public headers for any FILE* / time_t / other CRT-specific types in API signatures.
Acceptance criteria
Effort estimate
1 PR, 2-3 hours focused work for the CMake + workflow changes. Maybe 1-2 additional iterations to sort the inevitable surprises in libgcc symbol resolution. Total wall-clock realistic: half a day to a day.
Why this matters
Strategic call from project owner (2026-05-24): Windows will become the dominant operator platform; cross-platform support is required. liquid-dsp is the foundation for future digital-mode work (FT8/FT4 per #85 Phase 4, native PSK31/RTTY decoders, etc.). Landing this before those consumers arrive avoids blocking them.
73, Jeremy KK7GWY & Claude (AI dev partner)
Background (carried over from original issue)
PR #3046 vendored liquid-dsp under
third_party/liquid-dsp/proactively as DSP toolkit infrastructure (closes #3043). The vendor is Linux + macOS only — Windows MSVC builds skip it entirely. This issue tracks landing MSVC support.Strategic context (2026-05-24): Windows is expected to become the dominant operator platform for AetherSDR. Cross-platform support is non-negotiable — we must land Windows liquid-dsp before that user base lands. Issue is reactivated as a real priority, not "wait for a consumer."
Lessons from PR #3073 (the clang-cl attempt that closed)
PR #3073 tried Path A from the original issue (clang-cl ExternalProject sub-build). It got further than expected before failing in a way that invalidates the whole clang-cl approach:
What worked
find_program(CLANG_CL_EXE)resolves correctly on GitHub Actions windows-latest (VS 2022 ships clang-cl underVC/Tools/Llvm/x64/bin/)CMAKE_C_COMPILER=clang-cl.exe+CMAKE_CXX_COMPILER=clang-cl.exepasses the inner project's configure step (after we discovered both compilers must be consistent —Modules/Platform/Windows-Clang.cmake:168enforces "use either clang/clang++ or clang-cl as all C, C++, CUDA")Where it died
Inner build's first compilation step (
agc_rrrf.c) failed with hundreds of errors of this shape:Why this kills clang-cl
clang-cl does support the C99
_Complexkeyword. The failure is at a different layer: liquid-dsp's source usesfloat complexsyntax —complexis a C99 macro that<complex.h>should#define ... _Complex.<complex.h>defines#define complex _Complex✓<complex.h>does not define this macro. MSVC uses opaque_Fcomplex/_Dcomplextypes with different signatures.When clang-cl runs in
--target=x86_64-pc-windows-msvcmode (which it does by default — that's the whole point of clang-cl), it inherits MSVC's include paths. liquid-dsp's#include <complex.h>therefore picks up MSVC's incompatible header.Hacking past the type-declaration parse with
-Dcomplex=_Complexwas investigated and rejected: it solves the parser but not the function ABI. The 153.cfiles that callcrealf()/cimagf()/cabsf()/cargf()against C99 signatures would still fail at link time because those symbols don't exist in UCRT's C99 form — UCRT exports MSVC-conventioned_FCbuild/_FCrealf/ etc.Bottom line: clang-cl in MSVC ABI mode inherits MSVC's standard library. MSVC's standard library doesn't implement C99
<complex.h>. Therefore clang-cl can't compile liquid-dsp regardless of language-frontend support.The right plan: mingw-w64 ExternalProject
mingw-w64-gcc is a full GCC port for Windows targeting
windows-gnu(notwindows-msvc). It has:<complex.h>with thecomplexmacro and standardcrealf/cimagf/cabsf/cargffunctions.aoutput that MSVC'slink.exeaccepts directly (no.libconversion step needed)Implementation
1. Windows CI (
.github/workflows/ci.yml+windows-installer.yml):Adds ~30s to Windows CI. mingw-w64 ships in the choco cache on GHA's
windows-latestimage already.2.
CMakeLists.txt— replace clang-cl ExternalProject with mingw-w64:3. AetherSDR/MSVC link line:
link.exereadslibliquid.a(MinGW archive) directly. No conversion step. The static-libgcc flag ensureslibgcc's runtime helpers (__chkstk, divide / modulo intrinsics, etc.) are baked into the archive so the final link sees no GCC-specific external symbols.4. Developer documentation:
docs/build-windows.mdand the top-level README get a note: "Building from source on Windows requires mingw-w64 for the bundled liquid-dsp library. Install viachoco install mingwor omit liquid-dsp by passing-DENABLE_LIQUID_DSP=OFFto cmake."Risks
-DENABLE_LIQUID_DSP=OFFopt-out so Windows-only contributors who don't care about liquid-dsp can still build.dumpbin /imports liquid-static.libshowingapi-ms-win-crt-*.dllnotmsvcrt.dll. If mingw-w64 in CI somehow uses msvcrt, switch to-mcrtdll=ucrtbaseexplicitly.{float, float}-layout complex types and noFILE*in its API. Static linking is safe. Verify before merging: grep liquid-dsp's public headers for anyFILE*/time_t/ other CRT-specific types in API signatures.Acceptance criteria
cmake -B build -S .on Windows MSVC + Ninja produces no configure-time errors and emits "liquid-dsp: building via mingw-w64" status linecmake --build buildproducesliquid-dsp-install/lib/libliquid.aand links it intoAetherSDR.exedumpbin /imports AetherSDR.exe | grep ucrtbaseshows UCRT linkage (no msvcrt.dll)tests/liquid_smoke_test.cppcallingagc_crcf_create_default()and freeing it) links and runs on Windows CIEffort estimate
1 PR, 2-3 hours focused work for the CMake + workflow changes. Maybe 1-2 additional iterations to sort the inevitable surprises in
libgccsymbol resolution. Total wall-clock realistic: half a day to a day.Why this matters
Strategic call from project owner (2026-05-24): Windows will become the dominant operator platform; cross-platform support is required. liquid-dsp is the foundation for future digital-mode work (FT8/FT4 per #85 Phase 4, native PSK31/RTTY decoders, etc.). Landing this before those consumers arrive avoids blocking them.
73, Jeremy KK7GWY & Claude (AI dev partner)