From 1abec158866b32d015f754877172c507dd9d57fa Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Mon, 13 Oct 2025 17:50:35 -0700 Subject: [PATCH 1/8] Support Python 3.14 (and drop Python 3.9) --- .github/workflows/test.yml | 2 +- .pre-commit-config.yaml | 24 +++++++++++++----------- Dockerfile | 6 +++--- build_graphblas_cffi.py | 2 +- pyproject.toml | 10 ++++++---- suitesparse_graphblas/utils.pyx | 6 ++++++ 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 508d4a7..9619d60 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: source: ["conda-forge"] # os: ["ubuntu-latest"] # source: ["source"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a8fd94..131b537 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,8 @@ repos: - id: mixed-line-ending args: [--fix=lf] - id: trailing-whitespace + - id: name-tests-test + args: ["--pytest-test-first"] - repo: https://github.com/abravalheri/validate-pyproject rev: v0.24.1 hooks: @@ -39,16 +41,16 @@ repos: - id: autoflake args: [--in-place] - repo: https://github.com/pycqa/isort - rev: 6.0.1 + rev: 7.0.0 hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v3.20.0 + rev: v3.21.0 hooks: - id: pyupgrade - args: [--py39-plus] + args: [--py310-plus] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.1.0 + rev: 25.9.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 @@ -61,7 +63,7 @@ repos: - flake8==7.3.0 - flake8-comprehensions==3.17.0 - flake8-bugbear==24.12.12 - # - flake8-simplify==0.21.0 + # - flake8-simplify==0.22.0 - repo: https://github.com/asottile/yesqa rev: v1.5.0 hooks: @@ -69,13 +71,13 @@ repos: additional_dependencies: *flake8_dependencies # `pyroma` may help keep our package standards up to date if best practices change. # This is a "low value" check though and too slow to run as part of pre-commit. - # - repo: https://github.com/regebro/pyroma - # rev: "4.2" - # hooks: - # - id: pyroma - # args: [-n, "10", .] + - repo: https://github.com/regebro/pyroma + rev: "5.0" + hooks: + - id: pyroma + args: [-n, "10", .] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.3 + rev: 0.34.1 hooks: - id: check-dependabot - id: check-github-workflows diff --git a/Dockerfile b/Dockerfile index fa101e5..f20e91f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG BASE_CONTAINER=python:3.9-slim-buster +ARG BASE_CONTAINER=python:3.10-slim-buster FROM ${BASE_CONTAINER} as suitesparse ENV DEBIAN_FRONTEND=noninteractive @@ -24,7 +24,7 @@ ENV PYTHONUNBUFFERED 1 COPY --from=suitesparse /usr/include/GraphBLAS.h /usr/local/include/ COPY --from=suitesparse /usr/lib/x86_64-linux-gnu/libgraphblas* /usr/lib/x86_64-linux-gnu/ -COPY --from=suitesparse /build/pycparser/utils/fake_libc_include/* /usr/local/lib/python3.9/site-packages/pycparser/utils/fake_libc_include/ +COPY --from=suitesparse /build/pycparser/utils/fake_libc_include/* /usr/local/lib/python3.10/site-packages/pycparser/utils/fake_libc_include/ RUN apt-get update && apt-get install -yq build-essential git RUN pip3 install numpy cffi pytest cython @@ -44,4 +44,4 @@ RUN apt-get -y --purge remove git python3-pip && apt-get clean FROM ${BASE_CONTAINER} COPY --from=suitesparse /usr/lib/x86_64-linux-gnu/libgraphblas* /usr/lib/x86_64-linux-gnu/ COPY --from=suitesparse /usr/lib/x86_64-linux-gnu/libgomp* /usr/lib/x86_64-linux-gnu/ -COPY --from=psg /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages +COPY --from=psg /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages diff --git a/build_graphblas_cffi.py b/build_graphblas_cffi.py index 7548102..94594e6 100644 --- a/build_graphblas_cffi.py +++ b/build_graphblas_cffi.py @@ -13,7 +13,7 @@ # GraphBLAS_ROOT env var can point to the root directory of GraphBLAS to link against. # Expected subdirectories: include/ (contains GraphBLAS.h), lib/, and bin/ (on Windows only) # Otherwise fallback to default system folders. -graphblas_root = os.environ.get("GraphBLAS_ROOT", None) +graphblas_root = os.environ.get("GraphBLAS_ROOT") if not graphblas_root: # Windows wheels.yml configures suitesparse.sh to install GraphBLAS to "C:\\GraphBLAS". diff --git a/pyproject.toml b/pyproject.toml index 3a47125..929fbba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,8 @@ requires = [ "setuptools-git-versioning", "wheel", "cffi>=1.11", - "cython", + "cython; python_version<'3.14'", + "cython>=3.1; python_version>='3.14'", "numpy>=2.0", ] @@ -15,7 +16,7 @@ name = "suitesparse-graphblas" dynamic = ["version"] description = "SuiteSparse:GraphBLAS Python bindings." readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" license = {file = "LICENSE"} authors = [ {name = "Erik Welch", email = "erik.n.welch@gmail.com"}, @@ -51,12 +52,13 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Intended Audience :: Developers", "Intended Audience :: Other Audience", "Intended Audience :: Science/Research", @@ -93,7 +95,7 @@ dirty_template = "{tag}+{ccount}.g{sha}.dirty" [tool.black] line-length = 100 -target-version = ["py39", "py310", "py311", "py312", "py313"] +target-version = ["py310", "py311", "py312", "py313", "py314"] [tool.isort] sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] diff --git a/suitesparse_graphblas/utils.pyx b/suitesparse_graphblas/utils.pyx index 95d2f1b..5c9ae99 100644 --- a/suitesparse_graphblas/utils.pyx +++ b/suitesparse_graphblas/utils.pyx @@ -1,3 +1,9 @@ +# cython: freethreading_compatible=True +# +# We don't do anything special to support free-threading, but GraphBLAS C +# libraries are required to be thread-safe, so things should "just work". +# Of course, users writing multithreaded code can find many creative ways +# to fail, but python-suitesparse-graphblas shouldn't crash. import numpy as np from cpython.ref cimport Py_INCREF from libc.stdint cimport uintptr_t From 85c335d9cce96a2250dfc68c7d1c157530016498 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Mon, 13 Oct 2025 18:17:25 -0700 Subject: [PATCH 2/8] Update pypa/cibuildwheel from 2.22 to 3.2.1 (and others) --- .github/workflows/lint.yml | 4 ++-- .github/workflows/test.yml | 2 +- .github/workflows/update_graphblas.yml | 4 ++-- .github/workflows/wheels.yml | 10 +++++----- pyproject.toml | 1 + 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4140839..c1dc616 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,8 +14,8 @@ jobs: name: pre-commit-hooks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: "3.10" - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9619d60..ab3b4d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Conda diff --git a/.github/workflows/update_graphblas.yml b/.github/workflows/update_graphblas.yml index 153f1a7..939f5d5 100644 --- a/.github/workflows/update_graphblas.yml +++ b/.github/workflows/update_graphblas.yml @@ -27,11 +27,11 @@ jobs: if: github.repository == 'GraphBLAS/python-suitesparse-graphblas' || github.repository == 'alugowski/python-suitesparse-graphblas' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.11" diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 20434c0..062d7c9 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -34,7 +34,7 @@ jobs: name: Build SDist runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 @@ -94,7 +94,7 @@ jobs: cibw_archs: "arm64" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 @@ -145,7 +145,7 @@ jobs: brew fetch --retry libomp && brew reinstall libomp echo MACOSX_DEPLOYMENT_TARGET=$(otool -l $(brew --prefix libomp)/lib/libomp.dylib | grep minos | awk '{print $2}') >> $GITHUB_ENV - - uses: pypa/cibuildwheel@v2.22 + - uses: pypa/cibuildwheel@v3.2.1 with: output-dir: wheelhouse env: @@ -207,11 +207,11 @@ jobs: if: github.repository == 'GraphBLAS/python-suitesparse-graphblas' && ((github.event_name == 'release' && github.event.action == 'published') || (github.event_name == 'workflow_dispatch' && github.event.inputs.upload_dest != 'No Upload')) steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.x" - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: path: dist merge-multiple: true diff --git a/pyproject.toml b/pyproject.toml index 929fbba..5442f49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ requires = [ "setuptools-git-versioning", "wheel", "cffi>=1.11", + # Free-threading in Python 3.14 requires Cython >=3.1 "cython; python_version<'3.14'", "cython>=3.1; python_version>='3.14'", "numpy>=2.0", From 559288622ae11f58bb4709b3e474b985e5807b8e Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Tue, 14 Oct 2025 19:53:07 -0700 Subject: [PATCH 3/8] Change cibuildwheel back to v2.22 --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 062d7c9..a24a653 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -145,7 +145,7 @@ jobs: brew fetch --retry libomp && brew reinstall libomp echo MACOSX_DEPLOYMENT_TARGET=$(otool -l $(brew --prefix libomp)/lib/libomp.dylib | grep minos | awk '{print $2}') >> $GITHUB_ENV - - uses: pypa/cibuildwheel@v3.2.1 + - uses: pypa/cibuildwheel@v2.22 with: output-dir: wheelhouse env: From 181719498c32931c2b7e5de5e4c892c80dd307ab Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Wed, 15 Oct 2025 08:12:44 -0700 Subject: [PATCH 4/8] Don't run pyroma --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 131b537..aebc857 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -71,11 +71,11 @@ repos: additional_dependencies: *flake8_dependencies # `pyroma` may help keep our package standards up to date if best practices change. # This is a "low value" check though and too slow to run as part of pre-commit. - - repo: https://github.com/regebro/pyroma - rev: "5.0" - hooks: - - id: pyroma - args: [-n, "10", .] + # - repo: https://github.com/regebro/pyroma + # rev: "5.0" + # hooks: + # - id: pyroma + # args: [-n, "10", .] - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.34.1 hooks: From 5fc0ec0867d32aa1ddf848b4b394277180526228 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Wed, 15 Oct 2025 08:15:31 -0700 Subject: [PATCH 5/8] persist-credentials: false --- .github/workflows/lint.yml | 3 +++ .github/workflows/test.yml | 1 + .github/workflows/update_graphblas.yml | 1 + .github/workflows/wheels.yml | 2 ++ 4 files changed, 7 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c1dc616..be2f338 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,6 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@v6 with: python-version: "3.10" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab3b4d6..7b70df0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,7 @@ jobs: uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: false - name: Conda uses: conda-incubator/setup-miniconda@v3 with: diff --git a/.github/workflows/update_graphblas.yml b/.github/workflows/update_graphblas.yml index 939f5d5..8a0ad69 100644 --- a/.github/workflows/update_graphblas.yml +++ b/.github/workflows/update_graphblas.yml @@ -30,6 +30,7 @@ jobs: - uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@v6 with: diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index a24a653..e47b0a4 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -37,6 +37,7 @@ jobs: - uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: false - name: Build SDist run: pipx run build --sdist @@ -97,6 +98,7 @@ jobs: - uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: false # aarch64 Linux builds are cross-compiled on x86 runners using emulation # see https://cibuildwheel.readthedocs.io/en/stable/faq/#emulation From c97eee790dbd1c6d7f2747818d70823c02a0ee19 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Fri, 27 Mar 2026 13:18:08 -0700 Subject: [PATCH 6/8] Update to GraphBLAS 10.3.1, drop Python 3.10, modernize packaging and CI - Update SuiteSparse:GraphBLAS from 10.1.1 to 10.3.1 - Regenerate headers (GPU_ID -> GPU_IDS, add NGPUS, PRINT_FUNCTION) - Drop Python 3.10 support (minimum now 3.11) - PEP 639: license = "Apache-2.0" with license-files - Bump setuptools >= 77, numpy >= 1.24 - Update CI: cibuildwheel v2 -> v3.4, checkout v6, QEMU v4, create-pull-request v8 - Enable PyPy and free-threading explicitly (CIBW_ENABLE) - Fix manylinux_2_28 package manager (dnf instead of yum) - Update pre-commit hooks (black 26.3.1, isort 8.0.1, pyupgrade 3.21.2, etc.) - Add shellcheck, pygrep-hooks; enable check-executables-have-shebangs - Fix shellcheck warnings in docker_build.sh and suitesparse.sh --- .github/workflows/lint.yml | 4 +-- .github/workflows/test.yml | 4 +-- .github/workflows/update_graphblas.yml | 4 +-- .github/workflows/wheels.yml | 30 +++++++++-------- .pre-commit-config.yaml | 32 +++++++++++++------ GB_VERSION.txt | 2 +- docker_build.sh | 23 ++++++------- pyproject.toml | 14 ++++---- suitesparse.sh | 16 ++++++---- suitesparse_graphblas/create_headers.py | 3 +- suitesparse_graphblas/io/binary.py | 6 ++-- suitesparse_graphblas/suitesparse_graphblas.h | 14 +++++--- .../suitesparse_graphblas_no_complex.h | 14 +++++--- 13 files changed, 97 insertions(+), 69 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index be2f338..63644a1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,11 +14,11 @@ jobs: name: pre-commit-hooks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false - uses: actions/setup-python@v6 with: - python-version: "3.10" + python-version: "3.11" - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b70df0..0c93034 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,10 +18,10 @@ jobs: source: ["conda-forge"] # os: ["ubuntu-latest"] # source: ["source"] - python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/update_graphblas.yml b/.github/workflows/update_graphblas.yml index 8a0ad69..532072f 100644 --- a/.github/workflows/update_graphblas.yml +++ b/.github/workflows/update_graphblas.yml @@ -27,7 +27,7 @@ jobs: if: github.repository == 'GraphBLAS/python-suitesparse-graphblas' || github.repository == 'alugowski/python-suitesparse-graphblas' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false @@ -43,7 +43,7 @@ jobs: shell: bash - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: # See documentation: https://github.com/peter-evans/create-pull-request # Action behavior: https://github.com/peter-evans/create-pull-request#action-behaviour diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e47b0a4..34a0829 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -34,7 +34,7 @@ jobs: name: Build SDist runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false @@ -64,8 +64,8 @@ jobs: # Linux x86 manylinux - os: ubuntu-latest cibw_archs: "x86_64" - # Python 3.12 wheel requires libffi-devel to be installed. manylinux container uses yum - cibw_before_build_linux: "yum install -y libffi-devel" + # manylinux_2_28 uses dnf (not yum). libffi-devel needed for Python 3.12+ cffi. + cibw_before_build_linux: "dnf install -y libffi-devel || yum install -y libffi-devel || true" # skip musllinux cibw_skip: "*musl*" @@ -74,15 +74,15 @@ jobs: - os: ubuntu-latest cibw_archs: "x86_64" arch_note: "musl" - # skip manylinux (built elsewhere), PyPy (no musl numpy wheels), CPython 3.8 (no musl numpy wheels) - cibw_skip: "*many* pp* cp38*" + # skip manylinux (built elsewhere) + cibw_skip: "*many*" # Linux aarch64 # Separate runner because this requires emulation (only x86 runners are available) and is very slow. - os: ubuntu-latest cibw_archs: "aarch64" - # numpy wheels not available for aarch64 PyPy or musllinux - cibw_skip: "pp* *musl*" + # numpy wheels not available for aarch64 musllinux + cibw_skip: "*musl*" # macOS x86 # Note: keep as old as possible as due to libomp this will be the oldest supported macOS version. @@ -95,7 +95,7 @@ jobs: cibw_archs: "arm64" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false @@ -104,7 +104,7 @@ jobs: # see https://cibuildwheel.readthedocs.io/en/stable/faq/#emulation - name: Setup QEMU (for aarch64) if: matrix.cibw_archs == 'aarch64' - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 with: platforms: arm64 @@ -147,13 +147,16 @@ jobs: brew fetch --retry libomp && brew reinstall libomp echo MACOSX_DEPLOYMENT_TARGET=$(otool -l $(brew --prefix libomp)/lib/libomp.dylib | grep minos | awk '{print $2}') >> $GITHUB_ENV - - uses: pypa/cibuildwheel@v2.22 + - uses: pypa/cibuildwheel@v3.4 with: output-dir: wheelhouse env: # very verbose CIBW_BUILD_VERBOSITY: 3 + # cibuildwheel v3: PyPy and free-threading no longer built by default + CIBW_ENABLE: "pypy cpython-freethreading" + # Build SuiteSparse CIBW_BEFORE_ALL: bash suitesparse.sh ${{ env.GB_VERSION_REF }} @@ -174,8 +177,7 @@ jobs: # Architectures to build specified in matrix CIBW_ARCHS: ${{ matrix.cibw_archs }} - # as of writing numpy does not support pypy 3.10 - CIBW_SKIP: "${{ matrix.cibw_skip }} pp310*" + CIBW_SKIP: "${{ matrix.cibw_skip }}" # Use delvewheel on Windows. # This copies graphblas.dll into the wheel. "repair" in cibuildwheel parlance includes copying any shared @@ -219,7 +221,7 @@ jobs: merge-multiple: true # Upload to PyPI - - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: pypa/gh-action-pypi-publish@v1.13.0 name: Upload to PyPI if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.upload_dest == 'PyPI') with: @@ -229,7 +231,7 @@ jobs: password: ${{ secrets.PYPI_TOKEN }} # Upload to Test PyPI - - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: pypa/gh-action-pypi-publish@v1.13.0 name: Upload to Test PyPI if: github.event_name == 'workflow_dispatch' && github.event.inputs.upload_dest == 'Test PyPI' with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aebc857..a88a72c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,12 +13,12 @@ repos: hooks: - id: check-added-large-files - id: check-case-conflict + - id: check-executables-have-shebangs - id: check-illegal-windows-names - id: check-merge-conflict - id: check-ast - id: check-toml - id: check-yaml - # - id: check-executables-have-shebangs - id: check-vcs-permalinks - id: debug-statements - id: destroyed-symlinks @@ -31,26 +31,26 @@ repos: - id: name-tests-test args: ["--pytest-test-first"] - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.24.1 + rev: v0.25 hooks: - id: validate-pyproject name: Validate pyproject.toml - repo: https://github.com/PyCQA/autoflake - rev: v2.3.1 + rev: v2.3.3 hooks: - id: autoflake args: [--in-place] - repo: https://github.com/pycqa/isort - rev: 7.0.0 + rev: 8.0.1 hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v3.21.0 + rev: v3.21.2 hooks: - id: pyupgrade - args: [--py310-plus] + args: [--py311-plus] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.9.0 + rev: 26.3.1 hooks: - id: black - repo: https://github.com/PyCQA/flake8 @@ -62,7 +62,7 @@ repos: # These versions need updated manually - flake8==7.3.0 - flake8-comprehensions==3.17.0 - - flake8-bugbear==24.12.12 + - flake8-bugbear==25.11.29 # - flake8-simplify==0.22.0 - repo: https://github.com/asottile/yesqa rev: v1.5.0 @@ -72,12 +72,24 @@ repos: # `pyroma` may help keep our package standards up to date if best practices change. # This is a "low value" check though and too slow to run as part of pre-commit. # - repo: https://github.com/regebro/pyroma - # rev: "5.0" + # rev: "5.0.1" # hooks: # - id: pyroma # args: [-n, "10", .] + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: "v0.11.0.1" + hooks: + - id: shellcheck + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: python-check-blanket-noqa + - id: python-check-blanket-type-ignore + - id: python-no-eval + - id: python-no-log-warn + - id: text-unicode-replacement-char - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.1 + rev: 0.37.0 hooks: - id: check-dependabot - id: check-github-workflows diff --git a/GB_VERSION.txt b/GB_VERSION.txt index 2312799..a936832 100644 --- a/GB_VERSION.txt +++ b/GB_VERSION.txt @@ -1 +1 @@ -10.1.1 +10.3.1 diff --git a/docker_build.sh b/docker_build.sh index 31e2b25..77d3d80 100755 --- a/docker_build.sh +++ b/docker_build.sh @@ -1,3 +1,4 @@ +#!/usr/bin/env bash if [ $# -eq 0 ] then echo "Usage: ./docker_build.sh SUITESPARSE_BRANCH VERSION [BRANCH LOCATION PUSH] @@ -21,29 +22,29 @@ COMPACT=${COMPACT:-0} if [ "$LOCATION" = "clone" ] then TMPDIR=$(mktemp -d) - if [ ! -e $TMPDIR ]; then + if [ ! -e "$TMPDIR" ]; then >&2 echo "Failed to create temp directory" exit 1 fi trap "exit 1" HUP INT PIPE QUIT TERM trap 'rm -rf "$TMPDIR"' EXIT - cd $TMPDIR - git clone --branch $BRANCH https://github.com/GraphBLAS/python-suitesparse-graphblas.git - cd python-suitesparse-graphblas + cd "$TMPDIR" || exit + git clone --branch "$BRANCH" https://github.com/GraphBLAS/python-suitesparse-graphblas.git + cd python-suitesparse-graphblas || exit fi docker build \ - --build-arg SUITESPARSE=${SUITESPARSE} \ - --build-arg VERSION=${VERSION} \ - --build-arg COMPACT=${COMPACT} \ - -t $IMAGE:$VERSION \ + --build-arg SUITESPARSE="${SUITESPARSE}" \ + --build-arg VERSION="${VERSION}" \ + --build-arg COMPACT="${COMPACT}" \ + -t "$IMAGE:$VERSION" \ . -docker tag $IMAGE:$VERSION $IMAGE:latest +docker tag "$IMAGE:$VERSION" "$IMAGE:latest" if [ "$PUSH" = "push" ] then - docker push $IMAGE:$VERSION - docker push $IMAGE:latest + docker push "$IMAGE:$VERSION" + docker push "$IMAGE:latest" fi diff --git a/pyproject.toml b/pyproject.toml index 5442f49..976de9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,7 @@ [build-system] build-backend = "setuptools.build_meta" requires = [ - # setuptools<74 until PyPy vendors cffi 1.15.1 - "setuptools >=64, <74", + "setuptools >=77", "setuptools-git-versioning", "wheel", "cffi>=1.11", @@ -17,8 +16,9 @@ name = "suitesparse-graphblas" dynamic = ["version"] description = "SuiteSparse:GraphBLAS Python bindings." readme = "README.md" -requires-python = ">=3.10" -license = {file = "LICENSE"} +requires-python = ">=3.11" +license = "Apache-2.0" +license-files = ["LICENSE"] authors = [ {name = "Erik Welch", email = "erik.n.welch@gmail.com"}, {name = "Jim Kitchen"}, @@ -47,13 +47,11 @@ keywords = [ ] classifiers = [ "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -70,7 +68,7 @@ classifiers = [ ] dependencies = [ "cffi>=1.15", - "numpy>=1.23", + "numpy>=1.24", ] [project.urls] homepage = "https://github.com/GraphBLAS/python-suitesparse-graphblas" @@ -96,7 +94,7 @@ dirty_template = "{tag}+{ccount}.g{sha}.dirty" [tool.black] line-length = 100 -target-version = ["py310", "py311", "py312", "py313", "py314"] +target-version = ["py311", "py312", "py313", "py314"] [tool.isort] sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] diff --git a/suitesparse.sh b/suitesparse.sh index f804f40..0b47855 100755 --- a/suitesparse.sh +++ b/suitesparse.sh @@ -10,9 +10,9 @@ elif [[ $1 =~ refs/tags/([0-9]*\.[0-9]*\.[0-9]*)\..*$ ]]; then VERSION=${BASH_REMATCH[1]} else echo "Specify a SuiteSparse version, such as: $0 refs/tags/7.4.3.0 (got: $1)" - exit -1 + exit 1 fi -echo VERSION: $VERSION +echo VERSION: "$VERSION" NPROC="$(nproc)" if [ -z "${NPROC}" ]; then @@ -27,7 +27,8 @@ if [ -n "${BREW_LIBOMP}" ]; then cmake_params+=(-DOpenMP_C_FLAGS="-Xclang -fopenmp -I$(brew --prefix libomp)/include") cmake_params+=(-DOpenMP_C_LIB_NAMES="libomp") cmake_params+=(-DOpenMP_libomp_LIBRARY="omp") - export LDFLAGS="-L$(brew --prefix libomp)/lib" + LDFLAGS="-L$(brew --prefix libomp)/lib" + export LDFLAGS if [ -n "${SUITESPARSE_MACOS_ARCH}" ]; then export CFLAGS="-arch ${SUITESPARSE_MACOS_ARCH}" @@ -50,12 +51,13 @@ if [ -n "${GRAPHBLAS_PREFIX}" ]; then cmake_params+=(-DCMAKE_INSTALL_PREFIX="${GRAPHBLAS_PREFIX}") fi -curl -L https://github.com/DrTimothyAldenDavis/GraphBLAS/archive/refs/tags/v${VERSION}.tar.gz | tar xzf - -cd GraphBLAS-${VERSION}/build +curl -L "https://github.com/DrTimothyAldenDavis/GraphBLAS/archive/refs/tags/v${VERSION}.tar.gz" | tar xzf - +cd "GraphBLAS-${VERSION}/build" || exit # Disable optimizing some rarely-used types for significantly faster builds and significantly smaller wheel size. # Also the build with all types enabled sometimes stalls on GitHub Actions. Probably due to exceeded resource limits. # These can still be used, they'll just have reduced performance (AFAIK similar to UDTs). +# shellcheck disable=SC2129 # echo "#define GxB_NO_BOOL 1" >> ../Source/GB_control.h # # echo "#define GxB_NO_FP32 1" >> ../Source/GB_control.h # # echo "#define GxB_NO_FP64 1" >> ../Source/GB_control.h # @@ -74,6 +76,7 @@ if [ -n "${SUITESPARSE_FAST_BUILD}" ]; then echo "suitesparse.sh: Fast build requested." # Disable optimizing even more types. This is for builds that don't finish in runner resource limits, # such as emulated aarm64. + # shellcheck disable=SC2129 # echo "#define GxB_NO_BOOL 1" >> ../Source/GB_control.h # echo "#define GxB_NO_FP32 1" >> ../Source/GB_control.h @@ -93,6 +96,7 @@ fi if [ -n "${SUITESPARSE_FASTEST_BUILD}" ]; then echo "suitesparse.sh: Fastest build requested." # Fastest build possible. For use in development and automated tests that do not depend on performance. + # shellcheck disable=SC2129 echo "#define GxB_NO_BOOL 1" >> ../Source/GB_control.h echo "#define GxB_NO_FP32 1" >> ../Source/GB_control.h @@ -137,7 +141,7 @@ else fi cmake .. -DCMAKE_BUILD_TYPE=Release -G 'Unix Makefiles' "${cmake_params[@]}" -make -j$NPROC +make -j"$NPROC" $SUDO make install if [ -n "${CMAKE_GNUtoMS}" ]; then diff --git a/suitesparse_graphblas/create_headers.py b/suitesparse_graphblas/create_headers.py index fbfbd0f..0ec57fb 100644 --- a/suitesparse_graphblas/create_headers.py +++ b/suitesparse_graphblas/create_headers.py @@ -279,7 +279,8 @@ def groupby(index, seq): "GRB_SUBVERSION", "GxB_NTHREADS", "GxB_CHUNK", - "GxB_GPU_ID", + "GxB_GPU_IDS", + "GxB_NGPUS", "GxB_HYPERSPARSE", "GxB_SPARSE", "GxB_BITMAP", diff --git a/suitesparse_graphblas/io/binary.py b/suitesparse_graphblas/io/binary.py index 0248e7b..4e95931 100644 --- a/suitesparse_graphblas/io/binary.py +++ b/suitesparse_graphblas/io/binary.py @@ -6,11 +6,9 @@ from suitesparse_graphblas import __version__, check_status, ffi, lib, matrix stdffi = FFI() -stdffi.cdef( - """ +stdffi.cdef(""" void *malloc(size_t size); -""" -) +""") stdlib = stdffi.dlopen(find_library("c")) # When "packing" a matrix the owner of the memory buffer is transfered diff --git a/suitesparse_graphblas/suitesparse_graphblas.h b/suitesparse_graphblas/suitesparse_graphblas.h index f0a4b44..2e3e66b 100644 --- a/suitesparse_graphblas/suitesparse_graphblas.h +++ b/suitesparse_graphblas/suitesparse_graphblas.h @@ -24,6 +24,7 @@ typedef struct GB_SelectOp_opaque *GxB_SelectOp; typedef struct GxB_Container_struct *GxB_Container; /* GxB typedefs (functions) */ +typedef int64_t (*GxB_print_function)(char *string, size_t string_size, const void *value, int verbose); typedef void (*GxB_binary_function)(void *, const void *, const void *); typedef void (*GxB_index_binary_function)(void *, const void *, GrB_Index, GrB_Index, const void *, GrB_Index, GrB_Index, const void *); typedef void (*GxB_index_unary_function)(void *z, const void *x, GrB_Index i, GrB_Index j, const void *y); @@ -166,7 +167,8 @@ typedef enum { GxB_CONTEXT_NTHREADS = 7086, GxB_CONTEXT_CHUNK = 7087, - GxB_CONTEXT_GPU_ID = 7088 + GxB_CONTEXT_NGPUS = 7102, + GxB_CONTEXT_GPU_IDS = 7101 } GxB_Context_Field; typedef enum @@ -256,7 +258,9 @@ typedef enum GxB_FREE_FUNCTION = 7040, GxB_GLOBAL_NTHREADS = 7086, GxB_GLOBAL_CHUNK = 7087, - GxB_GLOBAL_GPU_ID = 7088, + GxB_GLOBAL_NGPUS = 7102, + GxB_GLOBAL_GPU_IDS = 7101, + GxB_NGPUS_MAX = 7103, GxB_BURBLE = 7019, GxB_PRINTF = 7020, GxB_FLUSH = 7021, @@ -273,7 +277,8 @@ typedef enum GxB_JIT_C_CMAKE_LIBS = 7031, GxB_JIT_USE_CMAKE = 7032, GxB_JIT_ERROR_LOG = 7033, - GxB_JIT_CUDA_PREFACE = 7100 + GxB_JIT_CUDA_PREFACE = 7100, + GxB_PRINT_FUNCTION = 7104 } GxB_Option_Field; typedef enum @@ -3757,7 +3762,7 @@ GrB_Info GxB_unload_Vector_into_Container(GrB_Vector V, GxB_Container Container, #define GxB_END ... #define GxB_FAST_IMPORT ... #define GxB_FULL ... -#define GxB_GPU_ID ... +#define GxB_GPU_IDS ... #define GxB_HYPERSPARSE ... #define GxB_IMPLEMENTATION ... #define GxB_IMPLEMENTATION_MAJOR ... @@ -3767,6 +3772,7 @@ GrB_Info GxB_unload_Vector_into_Container(GrB_Vector V, GxB_Container Container, #define GxB_INDEX_MAX ... #define GxB_MAX_NAME_LEN ... #define GxB_NBITMAP_SWITCH ... +#define GxB_NGPUS ... #define GxB_NTHREADS ... #define GxB_RANGE ... #define GxB_SPARSE ... diff --git a/suitesparse_graphblas/suitesparse_graphblas_no_complex.h b/suitesparse_graphblas/suitesparse_graphblas_no_complex.h index 86a6798..4376bec 100644 --- a/suitesparse_graphblas/suitesparse_graphblas_no_complex.h +++ b/suitesparse_graphblas/suitesparse_graphblas_no_complex.h @@ -22,6 +22,7 @@ typedef struct GB_SelectOp_opaque *GxB_SelectOp; typedef struct GxB_Container_struct *GxB_Container; /* GxB typedefs (functions) */ +typedef int64_t (*GxB_print_function)(char *string, size_t string_size, const void *value, int verbose); typedef void (*GxB_binary_function)(void *, const void *, const void *); typedef void (*GxB_index_binary_function)(void *, const void *, GrB_Index, GrB_Index, const void *, GrB_Index, GrB_Index, const void *); typedef void (*GxB_index_unary_function)(void *z, const void *x, GrB_Index i, GrB_Index j, const void *y); @@ -162,7 +163,8 @@ typedef enum { GxB_CONTEXT_NTHREADS = 7086, GxB_CONTEXT_CHUNK = 7087, - GxB_CONTEXT_GPU_ID = 7088 + GxB_CONTEXT_NGPUS = 7102, + GxB_CONTEXT_GPU_IDS = 7101 } GxB_Context_Field; typedef enum @@ -252,7 +254,9 @@ typedef enum GxB_FREE_FUNCTION = 7040, GxB_GLOBAL_NTHREADS = 7086, GxB_GLOBAL_CHUNK = 7087, - GxB_GLOBAL_GPU_ID = 7088, + GxB_GLOBAL_NGPUS = 7102, + GxB_GLOBAL_GPU_IDS = 7101, + GxB_NGPUS_MAX = 7103, GxB_BURBLE = 7019, GxB_PRINTF = 7020, GxB_FLUSH = 7021, @@ -269,7 +273,8 @@ typedef enum GxB_JIT_C_CMAKE_LIBS = 7031, GxB_JIT_USE_CMAKE = 7032, GxB_JIT_ERROR_LOG = 7033, - GxB_JIT_CUDA_PREFACE = 7100 + GxB_JIT_CUDA_PREFACE = 7100, + GxB_PRINT_FUNCTION = 7104 } GxB_Option_Field; typedef enum @@ -3523,7 +3528,7 @@ GrB_Info GxB_unload_Vector_into_Container(GrB_Vector V, GxB_Container Container, #define GxB_END ... #define GxB_FAST_IMPORT ... #define GxB_FULL ... -#define GxB_GPU_ID ... +#define GxB_GPU_IDS ... #define GxB_HYPERSPARSE ... #define GxB_IMPLEMENTATION ... #define GxB_IMPLEMENTATION_MAJOR ... @@ -3533,6 +3538,7 @@ GrB_Info GxB_unload_Vector_into_Container(GrB_Vector V, GxB_Container Container, #define GxB_INDEX_MAX ... #define GxB_MAX_NAME_LEN ... #define GxB_NBITMAP_SWITCH ... +#define GxB_NGPUS ... #define GxB_NTHREADS ... #define GxB_RANGE ... #define GxB_SPARSE ... From 470074f1e25676ba18d52e3dfc308138b0553315 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Fri, 27 Mar 2026 13:41:35 -0700 Subject: [PATCH 7/8] Don't enable cpython-freethreading wheels yet --- .github/workflows/wheels.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 34a0829..020e795 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -154,8 +154,9 @@ jobs: # very verbose CIBW_BUILD_VERBOSITY: 3 - # cibuildwheel v3: PyPy and free-threading no longer built by default - CIBW_ENABLE: "pypy cpython-freethreading" + # cibuildwheel v3: PyPy no longer built by default; enable explicitly + # Note: cpython-freethreading not enabled because cffi doesn't support it yet + CIBW_ENABLE: "pypy" # Build SuiteSparse CIBW_BEFORE_ALL: bash suitesparse.sh ${{ env.GB_VERSION_REF }} From 276c8c4d05a50322e9436584fa0ad9542c951cbb Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Fri, 27 Mar 2026 14:25:22 -0700 Subject: [PATCH 8/8] Update wheel build matrix: native ARM runner, macOS 15 - Replace QEMU-emulated aarch64 with native ubuntu-24.04-arm runner - Replace deprecated macos-13 with macos-15-intel - Remove QEMU setup and SUITESPARSE_FAST_BUILD (no longer needed) --- .github/workflows/wheels.yml | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 020e795..05bebdd 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -61,7 +61,7 @@ jobs: - os: windows-latest cibw_archs: "auto64" - # Linux x86 manylinux + # Linux x86_64 manylinux - os: ubuntu-latest cibw_archs: "x86_64" # manylinux_2_28 uses dnf (not yum). libffi-devel needed for Python 3.12+ cffi. @@ -69,7 +69,7 @@ jobs: # skip musllinux cibw_skip: "*musl*" - # Linux x86 musllinux + # Linux x86_64 musllinux # Separate runner for a Musl build of graphblas. The glibc build is not guaranteed to be compatible. - os: ubuntu-latest cibw_archs: "x86_64" @@ -77,20 +77,19 @@ jobs: # skip manylinux (built elsewhere) cibw_skip: "*many*" - # Linux aarch64 - # Separate runner because this requires emulation (only x86 runners are available) and is very slow. - - os: ubuntu-latest - cibw_archs: "aarch64" + # Linux aarch64 — native ARM runner (no QEMU emulation needed) + - os: ubuntu-24.04-arm + cibw_archs: "auto" # numpy wheels not available for aarch64 musllinux cibw_skip: "*musl*" - # macOS x86 - # Note: keep as old as possible as due to libomp this will be the oldest supported macOS version. - - os: macos-13 + # macOS x86_64 — macos-15-intel is the last Intel macOS runner. + # libomp from Homebrew determines the minimum supported macOS version. + - os: macos-15-intel cibw_archs: "x86_64" # macOS Apple Silicon - # Note: keep as old as possible as due to libomp this will be the oldest supported macOS version. + # Keep runner version as old as possible: libomp determines minimum macOS version. - os: macos-14 cibw_archs: "arm64" @@ -100,21 +99,6 @@ jobs: fetch-depth: 0 persist-credentials: false - # aarch64 Linux builds are cross-compiled on x86 runners using emulation - # see https://cibuildwheel.readthedocs.io/en/stable/faq/#emulation - - name: Setup QEMU (for aarch64) - if: matrix.cibw_archs == 'aarch64' - uses: docker/setup-qemu-action@v4 - with: - platforms: arm64 - - - name: Setup env (for aarch64) - if: matrix.cibw_archs == 'aarch64' - # Ask suitesparse.sh to compile faster by optimizing fewer types. Otherwise, the build takes too long to finish - # in 6 hour limit. - run: | - echo "SUITESPARSE_FAST_BUILD=1" >> $GITHUB_ENV - - name: Setup for testing if: github.event_name == 'push' || github.event_name == 'pull_request' # Ask suitesparse.sh to compile in the fastest way possible and provide a GB version to build @@ -163,7 +147,7 @@ jobs: CIBW_BEFORE_BUILD_LINUX: ${{ matrix.cibw_before_build_linux }} - CIBW_ENVIRONMENT_PASS_LINUX: SUITESPARSE_FAST_BUILD SUITESPARSE_FASTEST_BUILD + CIBW_ENVIRONMENT_PASS_LINUX: SUITESPARSE_FASTEST_BUILD # CMAKE_GNUtoMS=ON asks suitesparse.sh to build libraries in MSVC style on Windows. CIBW_ENVIRONMENT_WINDOWS: CMAKE_GNUtoMS=ON GRAPHBLAS_PREFIX="C:/GraphBLAS"