From 8a5f292673e8457111f69d6ef55c106748384d02 Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Tue, 6 Oct 2026 13:22:43 -0400 Subject: [PATCH 1/4] Find BLAS without vendor CMake configs and make MSVC /WX optional - MSVC: use the OpenBLAS::OpenBLAS target when a config provides it (vcpkg), otherwise fall back to finding openblas.lib and cblas.h directly, as conda-forge's openblas ships no CMake config. - Unix: link libcblas when it sits next to the BLAS FindBLAS picked, ahead of BLAS itself. conda-forge splits CBLAS into libcblas; linking it explicitly keeps the package working when users switch BLAS implementations. - Add SDE_WARNINGS_AS_ERRORS (default ON standalone, OFF under scikit-build) to gate MSVC /WX. Closes #126. Co-Authored-By: Claude Opus 5.5 (1M context) --- CMakeLists.txt | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f4f961..1a368af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,9 +21,22 @@ else() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") endif() +# MSVC warnings-as-errors: on for standalone builds, off for Python builds so +# downstream toolchains (e.g. conda-forge) aren't broken by new MSVC warnings +if(SKBUILD) + set(_sde_werror_default OFF) +else() + set(_sde_werror_default ON) +endif() +option(SDE_WARNINGS_AS_ERRORS "Treat MSVC warnings as errors (/WX)" + ${_sde_werror_default}) + # Warning flags (compiler-specific) if(MSVC) - add_compile_options(/W4 /WX) + add_compile_options(/W4) + if(SDE_WARNINGS_AS_ERRORS) + add_compile_options(/WX) + endif() else() add_compile_options( -Wall # Enable most warnings @@ -66,10 +79,36 @@ if(APPLE) find_library(ACCELERATE_FRAMEWORK Accelerate) target_link_libraries(dnlp_diff ${ACCELERATE_FRAMEWORK}) elseif(MSVC) - find_package(OpenBLAS CONFIG REQUIRED) - target_link_libraries(dnlp_diff OpenBLAS::OpenBLAS) + find_package(OpenBLAS CONFIG QUIET) + if(TARGET OpenBLAS::OpenBLAS) + # vcpkg + target_link_libraries(dnlp_diff OpenBLAS::OpenBLAS) + else() + # e.g. conda-forge's openblas ships openblas.lib and + # include/openblas/cblas.h, but no imported target + find_library(OPENBLAS_LIBRARY NAMES openblas libopenblas REQUIRED) + get_filename_component(_sde_openblas_libdir "${OPENBLAS_LIBRARY}" DIRECTORY) + get_filename_component(_sde_openblas_prefix "${_sde_openblas_libdir}" DIRECTORY) + find_path(OPENBLAS_INCLUDE_DIR cblas.h + HINTS "${_sde_openblas_prefix}/include" + PATH_SUFFIXES openblas REQUIRED) + target_link_libraries(dnlp_diff ${OPENBLAS_LIBRARY}) + target_include_directories(dnlp_diff PUBLIC ${OPENBLAS_INCLUDE_DIR}) + endif() else() find_package(BLAS REQUIRED) + # conda-forge splits the CBLAS interface into libcblas next to libblas. Link + # it explicitly (and before BLAS) so the package keeps working when users + # switch BLAS implementations, even if the build-time libblas exports cblas_*. + if(BLAS_LIBRARIES) + list(GET BLAS_LIBRARIES 0 _sde_blas_lib) + get_filename_component(_sde_blas_dir "${_sde_blas_lib}" DIRECTORY) + find_library(CBLAS_LIBRARY NAMES cblas + HINTS "${_sde_blas_dir}" NO_DEFAULT_PATH) + endif() + if(CBLAS_LIBRARY) + target_link_libraries(dnlp_diff ${CBLAS_LIBRARY}) + endif() target_link_libraries(dnlp_diff ${BLAS_LIBRARIES}) endif() From 75c4ca34ae5637c51df4e43bd3f62651f392d9be Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Tue, 6 Oct 2026 13:24:15 -0400 Subject: [PATCH 2/4] Add CI job building against conda-forge BLAS Builds and tests the engine on Linux and Windows against conda-forge's BLAS packages (no OpenBLAS CMake config on Windows, split libcblas on Linux). On Linux it also checks libcblas is a direct dependency and re-runs the tests after switching to the netlib BLAS variant. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/conda-forge.yml | 82 +++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .github/workflows/conda-forge.yml diff --git a/.github/workflows/conda-forge.yml b/.github/workflows/conda-forge.yml new file mode 100644 index 0000000..c23e794 --- /dev/null +++ b/.github/workflows/conda-forge.yml @@ -0,0 +1,82 @@ +name: conda-forge BLAS + +# Builds against conda-forge's BLAS packages, which ship no vendor CMake config +# (Windows) and split CBLAS into libcblas (Linux), as the sparsediffpy +# conda-forge recipe does. + +on: + push: + branches: [main, development] + pull_request: + branches: [main, development] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + + defaults: + run: + shell: bash -el {0} + + steps: + - uses: actions/checkout@v5 + + - name: Set up conda-forge environment (Linux) + if: runner.os == 'Linux' + uses: mamba-org/setup-micromamba@v2 + with: + environment-name: build + create-args: >- + cmake ninja c-compiler + libblas libcblas blas-devel + "libblas=*=*openblas" + + - name: Set up conda-forge environment (Windows) + if: runner.os == 'Windows' + uses: mamba-org/setup-micromamba@v2 + with: + environment-name: build + create-args: >- + cmake openblas libopenblas + + - name: Configure (Linux) + if: runner.os == 'Linux' + run: > + cmake -B build -S . -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_PREFIX_PATH="$CONDA_PREFIX" + -DCMAKE_C_FLAGS="-isystem $CONDA_PREFIX/include" + -DBLA_VENDOR=Generic + + - name: Configure (Windows) + if: runner.os == 'Windows' + run: > + cmake -B build -S . + -DCMAKE_PREFIX_PATH="$CONDA_PREFIX/Library" + + - name: Show BLAS selection + run: grep -E '^(BLAS_|CBLAS_LIBRARY|OPENBLAS_|OpenBLAS_DIR)' build/CMakeCache.txt || true + + - name: Build + run: cmake --build build --config Release + + - name: Run tests + run: cd build && ctest -C Release --output-on-failure + + - name: Check libcblas is linked explicitly (Linux) + if: runner.os == 'Linux' + run: | + readelf -d build/all_tests | grep NEEDED + readelf -d build/all_tests | grep -q 'libcblas\.so' + + # libblas from the netlib variant has no cblas_* symbols, so this only + # passes because libcblas was linked rather than resolved via libblas. + - name: Re-run tests with netlib BLAS (Linux) + if: runner.os == 'Linux' + run: | + micromamba install -y -n build "libblas=*=*netlib" "libcblas=*=*netlib" + cd build && ctest -C Release --output-on-failure From 9714fe51614b2bad4e504745e4a02581abb50ef5 Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Tue, 6 Oct 2026 13:30:21 -0400 Subject: [PATCH 3/4] Use setup-miniconda (Miniforge) for the conda-forge BLAS job setup-micromamba failed after creating the environment; Miniforge via setup-miniconda is the common setup for conda-forge-based CI. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/conda-forge.yml | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/conda-forge.yml b/.github/workflows/conda-forge.yml index c23e794..1457699 100644 --- a/.github/workflows/conda-forge.yml +++ b/.github/workflows/conda-forge.yml @@ -25,23 +25,21 @@ jobs: steps: - uses: actions/checkout@v5 - - name: Set up conda-forge environment (Linux) - if: runner.os == 'Linux' - uses: mamba-org/setup-micromamba@v2 + - uses: conda-incubator/setup-miniconda@v3 with: - environment-name: build - create-args: >- - cmake ninja c-compiler - libblas libcblas blas-devel - "libblas=*=*openblas" + miniforge-version: latest + activate-environment: build + conda-remove-defaults: "true" - - name: Set up conda-forge environment (Windows) + - name: Install conda-forge packages (Linux) + if: runner.os == 'Linux' + run: > + conda install -y cmake ninja c-compiler + libcblas blas-devel "libblas=*=*openblas" + + - name: Install conda-forge packages (Windows) if: runner.os == 'Windows' - uses: mamba-org/setup-micromamba@v2 - with: - environment-name: build - create-args: >- - cmake openblas libopenblas + run: conda install -y cmake openblas libopenblas - name: Configure (Linux) if: runner.os == 'Linux' @@ -78,5 +76,5 @@ jobs: - name: Re-run tests with netlib BLAS (Linux) if: runner.os == 'Linux' run: | - micromamba install -y -n build "libblas=*=*netlib" "libcblas=*=*netlib" + conda install -y "libblas=*=*netlib" "libcblas=*=*netlib" cd build && ctest -C Release --output-on-failure From 9ec1164079db7abecc1051513d84ca4da65e87be Mon Sep 17 00:00:00 2001 From: William Zijie Zhang Date: Tue, 6 Oct 2026 13:32:49 -0400 Subject: [PATCH 4/4] conda-forge CI: build against netlib BLAS, then switch to OpenBLAS conda-forge's global pinning builds against the netlib libblas/libcblas, which carry the switchable libblas.so.3/libcblas.so.3 SONAMEs; the openblas variant is a symlink to libopenblas.so.0, so linking against it records that instead. Mirror the pinning and test the switch the other way round. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/conda-forge.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/conda-forge.yml b/.github/workflows/conda-forge.yml index 1457699..d2cee06 100644 --- a/.github/workflows/conda-forge.yml +++ b/.github/workflows/conda-forge.yml @@ -34,8 +34,8 @@ jobs: - name: Install conda-forge packages (Linux) if: runner.os == 'Linux' run: > - conda install -y cmake ninja c-compiler - libcblas blas-devel "libblas=*=*openblas" + conda install -y cmake ninja c-compiler blas-devel + "libblas=*=*netlib" "libcblas=*=*netlib" - name: Install conda-forge packages (Windows) if: runner.os == 'Windows' @@ -65,16 +65,17 @@ jobs: - name: Run tests run: cd build && ctest -C Release --output-on-failure + # Like conda-forge's pinning, build against netlib so the binary records + # the switchable libblas.so.3/libcblas.so.3 SONAMEs (the openblas variant + # is a symlink to libopenblas.so.0). - name: Check libcblas is linked explicitly (Linux) if: runner.os == 'Linux' run: | readelf -d build/all_tests | grep NEEDED - readelf -d build/all_tests | grep -q 'libcblas\.so' + readelf -d build/all_tests | grep -q 'libcblas\.so\.3' - # libblas from the netlib variant has no cblas_* symbols, so this only - # passes because libcblas was linked rather than resolved via libblas. - - name: Re-run tests with netlib BLAS (Linux) + - name: Re-run tests after switching to OpenBLAS (Linux) if: runner.os == 'Linux' run: | - conda install -y "libblas=*=*netlib" "libcblas=*=*netlib" + conda install -y "libblas=*=*openblas" "libcblas=*=*openblas" cd build && ctest -C Release --output-on-failure