Skip to content

liquid-dsp Windows/MSVC support — mingw-w64 ExternalProject (Path B locked in) #3049

Description

@ten9876

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

  • cmake -B build -S . on Windows MSVC + Ninja produces no configure-time errors and emits "liquid-dsp: building via mingw-w64" status line
  • cmake --build build produces liquid-dsp-install/lib/libliquid.a and links it into AetherSDR.exe
  • dumpbin /imports AetherSDR.exe | grep ucrtbase shows UCRT linkage (no msvcrt.dll)
  • A trivial test consumer (tests/liquid_smoke_test.cpp calling agc_crcf_create_default() and freeing it) links and runs on Windows CI
  • All existing Linux + macOS builds remain unaffected
  • Windows CI passes end-to-end including the installer workflow

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    WindowsWindows-specific issuedependenciesPull requests that update a dependency fileenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is takenpriority: highHigh priority

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions