From e5e8e94327e08d1c8743f5b61e747bc5dd3e67f6 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sat, 19 Sep 2026 09:59:11 +0000 Subject: [PATCH] cvxopt: Add version 1.3.3 Convex optimization package: seven C extensions over BLAS/LAPACK and SuiteSparse, plus the optional fftw one. Upstream publishes no riscv64 wheel. Rocky 10 has no SuiteSparse, GSL or GLPK for riscv64 (and no EPEL to fall back on), so umfpack/cholmod/amd are compiled from the SuiteSparse tarball upstream's own CI pins, and the gsl/glpk/dsdp extensions are left off. --- .github/workflows/build-cvxopt.yml | 193 +++++++++++++++++++++++++++++ docs/packages/cvxopt.yaml | 5 + 2 files changed, 198 insertions(+) create mode 100644 .github/workflows/build-cvxopt.yml create mode 100644 docs/packages/cvxopt.yaml diff --git a/.github/workflows/build-cvxopt.yml b/.github/workflows/build-cvxopt.yml new file mode 100644 index 00000000000..c5901b9f18e --- /dev/null +++ b/.github/workflows/build-cvxopt.yml @@ -0,0 +1,193 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/cvxopt/cvxopt/blob/1.3.3/.github/workflows/linux_build.yml +name: Build cvxopt wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/cvxopt.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-cvxopt.yml' + - 'docs/packages/cvxopt.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-cvxopt.yml' + - 'docs/packages/cvxopt.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + # Rocky 10 has no SuiteSparse for riscv64, so umfpack/cholmod/amd are compiled + # from source; version and hash are upstream's own pin (linux_build.yml). + SUITESPARSE_VERSION: '7.11.0' + SUITESPARSE_SHA256: '93ed4c4e546a49fc75884c3a8b807d5af4a91e39d191fbbc60a07380b12a35d1' + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: cvxopt + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build cvxopt ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 180 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + python: + - "cp312" + - "cp313" + - "cp314" + - "cp314t" + + env: + CVXOPT_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout cvxopt ${{ env.CVXOPT_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: cvxopt/cvxopt + ref: ${{ env.CVXOPT_VERSION }} + fetch-depth: 0 + persist-credentials: false + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # Replaces upstream's before_all_linux.sh, which exits on any + # architecture other than x86_64/i686/aarch64. + CIBW_BEFORE_ALL_LINUX: >- + dnf -y install openblas-devel fftw-devel && + curl -fsSL -o /tmp/suitesparse.tar.gz https://github.com/DrTimothyAldenDavis/SuiteSparse/archive/v${{ env.SUITESPARSE_VERSION }}.tar.gz && + echo "${{ env.SUITESPARSE_SHA256 }} /tmp/suitesparse.tar.gz" | sha256sum -c - && + tar xzf /tmp/suitesparse.tar.gz -C {project} && + cp {project}/SuiteSparse-${{ env.SUITESPARSE_VERSION }}/LICENSE.txt {project}/LICENSE.suitesparse && + cp /usr/share/licenses/openblas/LICENSE {project}/LICENSE.openblas && + cp /usr/share/licenses/fftw-libs-double/COPYING {project}/LICENSE.fftw + # Upstream's own linux environment table, minus the gsl, glpk and dsdp + # extensions (no riscv64 packages, and no EPEL for it either). The + # *_LIB_DIR overrides are needed because setup.py only guesses + # /usr/lib64 when it finds SuiteSparse installed there. + CIBW_ENVIRONMENT: >- + CVXOPT_BLAS_LIB=openblas + CVXOPT_LAPACK_LIB=openblas + CVXOPT_BLAS_LIB_DIR=/usr/lib64 + CVXOPT_BUILD_FFTW=1 + CVXOPT_FFTW_LIB_DIR=/usr/lib64 + CVXOPT_SUITESPARSE_SRC_DIR=SuiteSparse-${{ env.SUITESPARSE_VERSION }} + + - name: Check the wheel ships the extensions and all licences + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for whl in sys.argv[1:]: + names = zipfile.ZipFile(whl).namelist() + for mod in ("base", "blas", "lapack", "umfpack", "cholmod", "amd", + "misc_solvers", "fftw"): + assert any(n.startswith(f"cvxopt/{mod}.") and n.endswith(".so") + for n in names), (whl, mod) + licences = {n.rsplit("/", 1)[1] + for n in names if ".dist-info/licenses/" in n} - {""} + assert licences == {"LICENSE", "LICENSE.openblas", "LICENSE.suitesparse", + "LICENSE.fftw"}, (whl, licences) + print(whl, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: cvxopt-${{ env.CVXOPT_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources + runs-on: ubuntu-24.04-riscv + + env: + CVXOPT_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # OpenBLAS is compiled with gfortran, so auditwheel vendors the image's + # libgfortran alongside libfftw3 (GPL-2.0-or-later). + - uses: ./actions/collect-gpl-sources + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc fftw + output: rpm-gpl-sources.tar + + # UMFPACK and CHOLMOD's supernodal module (GPL-2.0-or-later) are compiled + # straight into the wheel from the pinned tarball, which is not in the + # image's repos, so their source is collected here rather than as an RPM. + - name: Fetch pinned SuiteSparse source + run: | + set -euo pipefail + curl -fsSLO "https://github.com/DrTimothyAldenDavis/SuiteSparse/archive/v${SUITESPARSE_VERSION}.tar.gz" + echo "${SUITESPARSE_SHA256} v${SUITESPARSE_VERSION}.tar.gz" | sha256sum -c - + + - name: Combine GPL sources into one archive + run: | + set -euo pipefail + mkdir gpl-sources + tar xf rpm-gpl-sources.tar -C gpl-sources + cp "v${SUITESPARSE_VERSION}.tar.gz" "gpl-sources/SuiteSparse-${SUITESPARSE_VERSION}.tar.gz" + tar cf gpl-sources.tar -C gpl-sources . + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: cvxopt-${{ env.CVXOPT_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish cvxopt ${{ matrix.version }} + needs: [setup, build_wheels, gpl_sources] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: cvxopt-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: cvxopt-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc, fftw, suitesparse diff --git a/docs/packages/cvxopt.yaml b/docs/packages/cvxopt.yaml new file mode 100644 index 00000000000..37adaaba52a --- /dev/null +++ b/docs/packages/cvxopt.yaml @@ -0,0 +1,5 @@ +package-name: cvxopt +source-code: https://github.com/cvxopt/cvxopt +license: GPL-3.0-or-later +versions: +- version: 1.3.3