From e512506d91d1585b452c6051bd32aebfa0482842 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Wed, 22 Jul 2026 14:21:37 +0100 Subject: [PATCH 1/5] Scope CUDA link deps to their targets in meson.build The use_gpu block appended cuda/cublas/cusparse to the shared `_deps` list, so those libraries leaked into every extension module defined afterwards (_scs_mkl, _scs_accelerate, _scs_cudss) when use_gpu was combined with those backends. link_cudss had the same issue with cuda/cudss. Build a per-target dependency list (mirroring the existing `_mkl_deps = _deps + [...]` pattern) instead of mutating `_deps`. Also note in a comment that the GPU sources are listed explicitly because Meson's fs module has no glob(), and must be kept in sync with the .c files in the submodule. Co-Authored-By: Claude Opus 4.8 --- meson.build | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/meson.build b/meson.build index 1d37946..babd9b7 100644 --- a/meson.build +++ b/meson.build @@ -287,17 +287,18 @@ endif if get_option('use_gpu') gpu_c_args = common_c_args + ['-DPY_GPU=1', '-DINDIRECT=1'] - _deps += cuda_dep cublas_dep = cc.find_library('cublas', required: false) if not cublas_dep.found() cublas_dep = dependency('cublas', required: true) endif - _deps += cublas_dep cusparse_dep = cc.find_library('cusparse', required: false) if not cusparse_dep.found() cusparse_dep = dependency('cusparse', required: true) endif - _deps += cusparse_dep + # Scope the CUDA libraries to this target rather than mutating the shared + # `_deps`, so they don't leak into _scs_mkl / _scs_accelerate / _scs_cudss + # when use_gpu is combined with those backends. + gpu_deps = _deps + [cuda_dep, cublas_dep, cusparse_dep] if get_option('gpu_atrans') gpu_c_args += '-DGPU_TRANSPOSE_MAT=1' endif @@ -310,10 +311,12 @@ if get_option('use_gpu') # In Meson, sources with a `.cu` extension are compiled with nvcc by default. # To compile `.c` files with nvcc, they must be explicitly targeted. # It is strongly recommended to rename CUDA-C files to `.cu`. + # Meson's fs module has no glob(), so these are listed explicitly; keep in + # sync with the .c files under scs_source/linsys/gpu{,/indirect}. 'scs_source/linsys/gpu/gpu.c', 'scs_source/linsys/gpu/indirect/private.c', c_args: gpu_c_args, - dependencies: _deps, + dependencies: gpu_deps, include_directories: [ common_includes, 'scs_source/linsys/gpu/', 'scs_source/linsys/gpu/indirect' ], @@ -382,9 +385,10 @@ if host_machine.system() == 'darwin' endif if get_option('link_cudss') - _deps += cuda_dep cudss_dep = dependency('cudss', required: true) - _deps += cudss_dep + # Scope the CUDA libraries to this target rather than mutating the shared + # `_deps` (see the use_gpu block above). + cudss_deps = _deps + [cuda_dep, cudss_dep] py.extension_module( '_scs_cudss', 'scs/scspy.c', @@ -394,7 +398,7 @@ if get_option('link_cudss') spectral_cone_sources, c_args: common_c_args + ['-DPY_CUDSS'], include_directories: common_includes + ['scs_source/linsys/cudss/direct'], - dependencies: _deps, + dependencies: cudss_deps, install_dir: scs_dir, install: true, ) From 1c2aa549406e066047409d8f59dca425d1177ff4 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Wed, 22 Jul 2026 14:27:18 +0100 Subject: [PATCH 2/5] ci: add build-only GPU job to validate use_gpu link path Neither the use_gpu build nor the cuBLAS/cuSPARSE linking (fixed in #221) was covered by CI. Add a build_gpu job that installs the CUDA toolkit libraries from conda-forge and builds with -Duse_gpu=true. Linking against cuBLAS/cuSPARSE needs the toolkit but not a physical GPU, so the job builds and link-checks without running the solver. A shared library links successfully even with undefined symbols, so the job asserts via readelf that _scs_gpu records libcublas and libcusparse as NEEDED entries -- the #221 bug was a missing link that only surfaced at import time as `undefined symbol: cusparseDestroySpMat`. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b167333..b2fd6b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,6 +102,54 @@ jobs: pixi r pytest rm -rf build/ + build_gpu: + # Build-only: linking against cuBLAS/cuSPARSE needs the CUDA toolkit but + # not a physical GPU, so this validates the use_gpu build/link path without + # running the solver (running would require GPU hardware). + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + strategy: + fail-fast: false + matrix: + python-version: [ "3.12" ] + + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + - uses: prefix-dev/setup-pixi@v0.10.0 + with: + cache: false + pixi-version: v0.46.0 + - name: Add .pixi/envs/default to the $PATH + run: echo "$(pwd)/.pixi/envs/default/bin" >> $GITHUB_PATH + shell: bash + - name: Install dependencies + run: | + pixi add --platform linux-64 scipy numpy pip pytest meson meson-python \ + openblas python==${{ matrix.python-version }} \ + cuda-version=12.* cuda-nvcc cuda-cudart-dev libcublas-dev libcusparse-dev + - name: Build (use_gpu=true) + run: | + export CUDA_PATH="$(pwd)/.pixi/envs/default" + export CUDA_HOME="$CUDA_PATH" + pixi r python -m pip install -v . --no-build-isolation \ + -Csetup-args=-Duse_gpu=true + - name: Check _scs_gpu is linked against cuBLAS and cuSPARSE + # A shared library links successfully even with undefined symbols, so the + # real regression test is that libcublas/libcusparse are recorded as + # NEEDED entries (the bug fixed in #221 was a missing link, seen only at + # import time as `undefined symbol: cusparseDestroySpMat`). + run: | + so=$(pixi r python -c "import scs, glob, os; print(glob.glob(os.path.join(os.path.dirname(scs.__file__), '_scs_gpu*.so'))[0])") + echo "Inspecting $so" + readelf -d "$so" + readelf -d "$so" | grep -q 'libcublas' || { echo "ERROR: _scs_gpu not linked against cuBLAS"; exit 1; } + readelf -d "$so" | grep -q 'libcusparse' || { echo "ERROR: _scs_gpu not linked against cuSPARSE"; exit 1; } + echo "OK: _scs_gpu links cuBLAS and cuSPARSE" + build_native_arch: runs-on: ubuntu-latest defaults: From ded14f80d499ec0c401500227a2e1aefc86e79f2 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Wed, 22 Jul 2026 14:31:09 +0100 Subject: [PATCH 3/5] ci: build GPU with int32=true (required by GPU backend) The GPU backend requires 32-bit integers; meson errors out otherwise (meson.build:173). Match the documented use_gpu build invocation from #221. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b2fd6b4..b7d6254 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -136,7 +136,7 @@ jobs: export CUDA_PATH="$(pwd)/.pixi/envs/default" export CUDA_HOME="$CUDA_PATH" pixi r python -m pip install -v . --no-build-isolation \ - -Csetup-args=-Duse_gpu=true + -Csetup-args=-Duse_gpu=true -Csetup-args=-Dint32=true - name: Check _scs_gpu is linked against cuBLAS and cuSPARSE # A shared library links successfully even with undefined symbols, so the # real regression test is that libcublas/libcusparse are recorded as From 96e780b297b6bc52ec7e186a621201e21937e191 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Wed, 22 Jul 2026 14:33:43 +0100 Subject: [PATCH 4/5] ci: locate installed _scs_gpu via sysconfig, not import Importing scs from the repo root resolved the local ./scs source directory (a namespace package, __file__ is None) instead of the installed package, leaving the .so path empty. Find the extension under sysconfig platlib instead. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7d6254..33bb758 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,7 +143,16 @@ jobs: # NEEDED entries (the bug fixed in #221 was a missing link, seen only at # import time as `undefined symbol: cusparseDestroySpMat`). run: | - so=$(pixi r python -c "import scs, glob, os; print(glob.glob(os.path.join(os.path.dirname(scs.__file__), '_scs_gpu*.so'))[0])") + # Locate the installed extension via sysconfig rather than `import scs`, + # since importing from the repo root would pick up the local ./scs + # source directory instead of the installed package. + site=$(pixi r python -c "import sysconfig; print(sysconfig.get_path('platlib'))") + so=$(find "$site/scs" -name '_scs_gpu*.so' | head -1) + if [ -z "$so" ]; then + echo "ERROR: _scs_gpu*.so not found under $site/scs" + ls -la "$site/scs" || true + exit 1 + fi echo "Inspecting $so" readelf -d "$so" readelf -d "$so" | grep -q 'libcublas' || { echo "ERROR: _scs_gpu not linked against cuBLAS"; exit 1; } From 788db36584c8b197f03d24df91c5a8b320a68ab7 Mon Sep 17 00:00:00 2001 From: Brendan O'Donoghue Date: Wed, 22 Jul 2026 14:38:23 +0100 Subject: [PATCH 5/5] ci: find installed _scs_gpu via shell, avoid nested-quote issue pixi run stripped the inner single quotes from the python -c snippet, so sysconfig.get_path('platlib') became a bare name (NameError). Locate the .so with find over the pixi env lib dir instead of invoking python. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33bb758..aa18bd5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,14 +143,11 @@ jobs: # NEEDED entries (the bug fixed in #221 was a missing link, seen only at # import time as `undefined symbol: cusparseDestroySpMat`). run: | - # Locate the installed extension via sysconfig rather than `import scs`, - # since importing from the repo root would pick up the local ./scs - # source directory instead of the installed package. - site=$(pixi r python -c "import sysconfig; print(sysconfig.get_path('platlib'))") - so=$(find "$site/scs" -name '_scs_gpu*.so' | head -1) + # Locate the installed extension in the pixi env's site-packages. + so=$(find "$(pwd)/.pixi/envs/default/lib" -name '_scs_gpu*.so' | head -1) if [ -z "$so" ]; then - echo "ERROR: _scs_gpu*.so not found under $site/scs" - ls -la "$site/scs" || true + echo "ERROR: _scs_gpu*.so not found in the pixi environment" + find "$(pwd)/.pixi/envs/default/lib" -name '_scs_*.so' || true exit 1 fi echo "Inspecting $so"