Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
eef2dad
Revert x86_64 Linux and Windows wheels to OpenBLAS, add clean-contain…
bodono Aug 31, 2026
3b00317
Gate PyPI upload on the clean-container wheel smoke test
bodono Aug 31, 2026
b23bb4d
Restore MKL in Linux x86-64 wheels via the scs[mkl] extra
bodono Aug 31, 2026
d16f2da
Restore compiler pkgconfig dir so wheels link threaded (iomp) MKL
bodono Aug 31, 2026
fb28639
Smoke test: select the cp312 wheel matching the container python
bodono Aug 31, 2026
bdf282b
Smoke test: identify backends by __file__, add scs[mkl] import diagno…
bodono Aug 31, 2026
fbbcf23
Resolve scs[mkl] via a prefix-relative RUNPATH instead of ctypes prel…
bodono Aug 31, 2026
41209d6
README: recommend scs[mkl] prominently in the install section
bodono Aug 31, 2026
4c6f456
Fix reviewed #232 issues: normalize RPATH, audit all wheels, widen th…
bodono Aug 31, 2026
8cc38e0
Make the wheel audit fail-closed
bodono Aug 31, 2026
4d9999b
Audit: include _scs_dense, forbid vacuity, split RPATH/RUNPATH, scan …
bodono Aug 31, 2026
af9e091
Audit: scope the DT_RPATH prohibition to _scs_mkl
bodono Aug 31, 2026
95b9a81
Audit: require the exact python/ABI tag set per platform
bodono Aug 31, 2026
bf89d69
Link MKL statically into _scs_mkl: self-contained wheels, no runtime …
bodono Sep 3, 2026
a50807e
Smoke test: the dense backend is named cpu_dense
bodono Sep 3, 2026
ca985bf
link_mkl: keep mkl_rt a hard dependency under --as-needed
bodono Sep 3, 2026
8e12289
Tidy the static-MKL build: keep rt in link_mkl, say #423 once
bodono Sep 3, 2026
720a0f8
wheel_audit: require the shipped inventory; publish waits for the sou…
bodono Sep 4, 2026
6355503
Windows wheels: pin conda-forge OpenBLAS to 0.3.33
bodono Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/scripts/install_mkl_static.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
# Lay out Intel's static oneMKL archives (the mkl-static and mkl-include PyPI
# wheels) under a prefix for the static _scs_mkl link; see mkl_static_prefix
# in meson.options. Runs inside the cibuildwheel manylinux container.
set -euo pipefail
prefix=${1:?usage: install_mkl_static.sh <prefix>}
py=$(ls -d /opt/python/cp3*-cp3*/bin/python | head -1)
tmp=$(mktemp -d)
"$py" -m pip download --quiet --no-deps --only-binary=:all: \
--platform manylinux_2_28_x86_64 -d "$tmp" mkl-static==2026.1.0 mkl-include==2026.1.0
for whl in "$tmp"/*.whl; do
"$py" -m zipfile -e "$whl" "$tmp/unpacked"
done
mkdir -p "$prefix"
cp -r "$tmp"/unpacked/*.data/data/. "$prefix"/ # {lib,include}
rm -rf "$tmp"
139 changes: 135 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,14 @@ jobs:
uses: conda-incubator/setup-miniconda@v4
with:
miniconda-version: "latest"
channels: https://software.repos.intel.com/python/conda/, conda-forge
channels: conda-forge, anaconda

- name: Install MKL from conda on Windows
- name: Install openblas from conda on Windows
if: runner.os == 'Windows'
run: conda install -y -c https://software.repos.intel.com/python/conda/ -c conda-forge mkl mkl-devel intel-openmp pkgconfig
# 0.3.33 pinned: conda-forge's win-64 0.3.34 build crashes inside its
# DGEMM kernels on AMD Zen 4/5 CPUs that expose AVX-512
# (conda-forge/openblas-feedstock#196). Unpin once a fixed build ships.
run: conda install -y "openblas=0.3.33" pkgconfig

- name: Build wheels
uses: pypa/cibuildwheel@v4.2.0
Expand All @@ -279,6 +282,132 @@ jobs:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl

wheel_audit:
# Every Linux wheel must ship the standard extensions and a vendored
# OpenBLAS, _scs_mkl exactly in the x86-64 manylinux wheels; no wheel may
# depend on a dynamic MKL (it is linked statically), ship an Intel shared
# library, or carry an absolute rpath entry; see pyproject.toml (#423).
name: Audit wheel ELF metadata
needs: build_wheels
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v7
with:
pattern: cibw-wheels-ubuntu-*
path: wheelhouse
merge-multiple: true

- name: Audit NEEDED, rpath and shipped libraries
run: |
cat > audit.py <<'EOF'
import glob, os, re, subprocess, sys, tempfile, zipfile
bad = []
whls = sorted(glob.glob("wheelhouse/*.whl"))
assert whls, "no wheels downloaded"
for whl in whls:
name = os.path.basename(whl)
with tempfile.TemporaryDirectory() as td:
with zipfile.ZipFile(whl) as z:
z.extractall(td)
sos = glob.glob(os.path.join(td, "**", "*.so*"), recursive=True)
mods = {os.path.basename(so).split(".")[0] for so in sos}
want = {"_scs_direct", "_scs_indirect", "_scs_dense"}
if "manylinux" in name and "x86_64" in name:
want.add("_scs_mkl")
elif "_scs_mkl" in mods:
bad.append((name, "-", "_scs_mkl belongs only in x86-64 manylinux wheels"))
if want - mods:
bad.append((name, "-", f"missing extensions: {sorted(want - mods)}"))
if not any(os.path.basename(so).startswith("libopenblas") for so in sos):
bad.append((name, "-", "no vendored OpenBLAS"))
intel = [os.path.basename(so) for so in sos
if os.path.basename(so).startswith(("libmkl", "libiomp"))]
if intel:
bad.append((name, "-", f"Intel shared libraries in wheel: {intel}"))
for so in sos:
rel = os.path.relpath(so, td)
out = subprocess.run(["readelf", "-d", so], capture_output=True,
text=True, check=True).stdout
needed = re.findall(r"\(NEEDED\)[^\[]*\[([^\]]+)\]", out)
mkl = [n for n in needed if n.startswith(("libmkl", "libiomp"))]
if mkl:
bad.append((name, rel, f"dynamic MKL dependency: {mkl}"))
for rp in re.findall(r"\((?:RPATH|RUNPATH)\)[^\[]*\[([^\]]*)\]", out):
for entry in rp.split(":"):
if entry and not entry.startswith("$ORIGIN"):
bad.append((name, rel, f"non-relative rpath entry '{entry}'"))
assert any("manylinux" in w and "x86_64" in w for w in map(os.path.basename, whls)), "no x86-64 manylinux wheel"
print(f"audited {len(whls)} wheels")
for whl, so, msg in bad:
print(f"BAD: {whl} :: {so} :: {msg}")
sys.exit(1 if bad else 0)
EOF
python3 audit.py

wheel_smoke_clean_env:
# The release gate: install the built wheel in a pristine container with
# nothing on the loader path and solve with every backend (a PSD cone so
# BLAS/LAPACK run). On x86-64 the static MKL backend must be AUTO's choice.
name: Clean-container wheel smoke test (${{ matrix.python }})
needs: build_wheels
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: ["3.9", "3.14", "3.14t"]
steps:
- uses: actions/download-artifact@v7
with:
pattern: cibw-wheels-ubuntu-latest-*
path: wheelhouse
merge-multiple: true

- name: Solve in pristine container (${{ matrix.python }})
run: |
cat > smoke.py <<'EOF'
import os, sys
import numpy as np, scipy.sparse as sp, scs
# the loader must be exactly as clean as a user's machine
assert not os.environ.get("LD_LIBRARY_PATH"), os.environ["LD_LIBRARY_PATH"]
print("scs", scs.__version__, "python", sys.version)
data = {
"A": sp.csc_matrix(np.vstack([np.array([[1.0, 1.0], [1.0, -1.0], [0.0, 1.0]]),
np.array([[-1.0, 0.0], [0.0, 0.0], [0.0, -1.0]])])),
"b": np.hstack([np.array([1.0, 0.5, 0.7]), np.zeros(3)]),
"c": np.array([1.0, 1.0]),
}
cone = {"z": 1, "l": 2, "s": [2]}
scs._load_module("_scs_mkl") # static MKL: must import with nothing installed
auto_file = os.path.basename(scs._resolve_auto().__file__)
assert auto_file.startswith("_scs_mkl"), auto_file
print("auto resolves to", auto_file)
for name in ["qdldl", "cpu_indirect", "cpu_dense", "mkl", "auto"]:
kw = {} if name == "auto" else {"linear_solver": name}
sol = scs.SCS(data, cone, verbose=True, **kw).solve()
assert sol["info"]["status"] == "solved", (name, sol["info"]["status"])
print(name, "ok:", sol["info"].get("lin_sys_solver", "n/a"))
print("ALL OK")
EOF
case "${{ matrix.python }}" in
3.9) IMAGE=python:3.9-slim; TAG=cp39-cp39 ;;
3.14) IMAGE=python:3.14-slim; TAG=cp314-cp314 ;;
3.14t) IMAGE=ghcr.io/astral-sh/uv:debian-slim; TAG=cp314-cp314t ;;
esac
docker run --rm -v "$PWD:/work" -w /tmp -e PY=${{ matrix.python }} -e TAG=$TAG \
"$IMAGE" bash -ec '
if command -v uv >/dev/null; then
uv python install "$PY"
uv venv -p "$PY" /venv
PIP="uv pip install --python /venv/bin/python"
PYBIN=/venv/bin/python
else
PIP="pip install --quiet"
PYBIN=python
fi
$PIP numpy scipy
$PIP /work/wheelhouse/scs-*-${TAG}-manylinux*x86_64.whl
$PYBIN /work/smoke.py'

build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
Expand All @@ -296,7 +425,9 @@ jobs:
path: dist/*.tar.gz

upload_pypi:
needs: [build_wheels, build_sdist]
# publishing waits for the clean-container smoke tests, the wheel audit
# and the source-MKL lanes (the sdist carries the link_mkl build path)
needs: [build_wheels, build_sdist, wheel_smoke_clean_env, wheel_audit, build_mkl]
runs-on: ubuntu-latest
environment: pypi
permissions:
Expand Down
25 changes: 25 additions & 0 deletions LICENSE-INTEL-MKL.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Intel Simplified Software License (Version October 2022)

Use and Redistribution. You may use and redistribute the software, which is provided in binary form only, (the “Software”), without modification, provided the following conditions are met:

* Redistributions must reproduce the above copyright notice and these terms of use in the Software and in the documentation and/or other materials provided with the distribution.
* Neither the name of Intel nor the names of its suppliers may be used to endorse or promote products derived from this Software without specific prior written permission.
* No reverse engineering, decompilation, or disassembly of the Software is permitted, nor any modification or alteration of the Software or its operation at any time, including during execution.

No other licenses. Except as provided in the preceding section, Intel grants no licenses or other rights by implication, estoppel or otherwise to, patent, copyright, trademark, trade name, service mark or other intellectual property licenses or rights of Intel.

Third party software. “Third Party Software” means the files (if any) listed in the “third-party-software.txt” or other similarly-named text file that may be included with the Software. Third Party Software, even if included with the distribution of the Software, may be governed by separate license terms, including without limitation, third party license terms, open source software notices and terms, and/or other Intel software license terms. These separate license terms solely govern Your use of the Third Party Software.

DISCLAIMER. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED. THIS SOFTWARE IS NOT INTENDED FOR USE IN SYSTEMS OR APPLICATIONS WHERE FAILURE OF THE SOFTWARE MAY CAUSE PERSONAL INJURY OR DEATH AND YOU AGREE THAT YOU ARE FULLY RESPONSIBLE FOR ANY CLAIMS, COSTS, DAMAGES, EXPENSES, AND ATTORNEYS’ FEES ARISING OUT OF ANY SUCH USE, EVEN IF ANY CLAIM ALLEGES THAT INTEL WAS NEGLIGENT REGARDING THE DESIGN OR MANUFACTURE OF THE SOFTWARE.

LIMITATION OF LIABILITY. IN NO EVENT WILL INTEL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

No support. Intel may make changes to the Software, at any time without notice, and is not obligated to support, update or provide training for the Software.

Termination. Your right to use the Software is terminated in the event of your breach of this license.

Feedback. Should you provide Intel with comments, modifications, corrections, enhancements or other input (“Feedback”) related to the Software, Intel will be free to use, disclose, reproduce, license or otherwise distribute or exploit the Feedback in its sole discretion without any obligations or restrictions of any kind, including without limitation, intellectual property rights or licensing obligations.

Compliance with laws. You agree to comply with all relevant laws and regulations governing your use, transfer, import or export (or prohibition thereof) of the Software.

Governing law. All disputes will be governed by the laws of the United States of America and the State of Delaware without reference to conflict of law principles and subject to the exclusive jurisdiction of the state or federal courts sitting in the State of Delaware, and each party agrees that it submits to the personal jurisdiction and venue of those courts and waives any objections. THE UNITED NATIONS CONVENTION ON CONTRACTS FOR THE INTERNATIONAL SALE OF GOODS (1980) IS SPECIFICALLY EXCLUDED AND WILL NOT APPLY TO THE SOFTWARE.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ The full documentation is available [here](https://www.cvxgrp.org/scs/).
pip install scs
```

On x86-64 Linux the manylinux (glibc) wheels include the MKL Pardiso direct linear solver
(MKL linked statically into `_scs_mkl`, single-threaded) and use it
automatically: it is faster than the built-in QDLDL solver for most problems,
often dramatically so on larger ones, and nothing extra needs to be
installed. Intel's license notice ships in the wheel as
`LICENSE-INTEL-MKL.txt`. Every other wheel falls back to QDLDL.

To install from source:
```bash
git clone --recursive https://github.com/bodono/scs-python.git
Expand All @@ -40,9 +47,13 @@ solver = scs.SCS(data, cone, linear_solver=scs.LinearSolver.QDLDL)
Available values: `AUTO`, `QDLDL`, `CPU_INDIRECT`, `MKL`, `ACCELERATE`,
`CPU_DENSE`, `GPU_INDIRECT`, `CUDSS`.

The pre-built wheels (`pip install scs`) include MKL on x86_64 Linux and
Windows, and Apple Accelerate on macOS. When installing from source, additional
backends can be enabled with build-time flags:
The pre-built wheels (`pip install scs`) link OpenBLAS on Linux and Windows,
and Apple Accelerate on macOS; the x86-64 manylinux wheels also ship the MKL
Pardiso backend (see Installation). MKL is linked statically rather than
bundled as shared libraries, whose dlopen'd CPU dispatch kernels wheel-repair
tools cannot see (cvxgrp/scs#423). The MKL backend is also available in
source builds (e.g. conda environments providing MKL), where
additional backends can be enabled with build-time flags:

```bash
# MKL Pardiso direct solver
Expand All @@ -62,7 +73,8 @@ pip install . -Csetup-args=-Duse_spectral_cones=true
```

Notes:
- Linux x86_64 wheels are built and tested against threaded MKL, and CI asserts a `libiomp5` dependency on the packaged `_scs_mkl` extension. Windows currently falls back to sequential MKL because Intel's conda `pkg-config` metadata for the threaded variant is still broken.
- x86-64 manylinux wheels ship a `_scs_mkl` extension with sequential MKL linked statically (CI asserts the shipped inventory and that no wheel carries a dynamic MKL dependency). The musllinux, aarch64, macOS and Windows wheels do not include the MKL backend; Windows source builds use sequential MKL because Intel's conda `pkg-config` metadata for the threaded variant is still broken.
- Windows wheels link conda-forge OpenBLAS pinned to 0.3.33: the win-64 0.3.34 build crashes inside its DGEMM kernels on AMD Zen 4/5 CPUs that expose AVX-512 (conda-forge/openblas-feedstock#196), so Windows source builds should avoid that build too.
- `BLAS64` is a general SCS build mode for ILP64 BLAS/LAPACK libraries, not an MKL-only feature.
- For the MKL Pardiso backend specifically, `BLAS64` must be paired with 64-bit SCS integers (`DLONG` / `int32=false`), and SCS now fails early if another library in the process has already fixed MKL to an incompatible LP64/ILP64 interface layer.

Expand Down
Loading