Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions suitesparse.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ if [ -z "${NPROC}" ]; then
fi

cmake_params=()

# Fail the build if OpenMP is missing instead of silently producing a serial
# library. GraphBLAS only warns and carries on, so a serial build stays
# invisible until someone measures it: conda-forge's graphblas 10.5.0 shipped
# that way on osx-arm64 while still depending on llvm-openmp. The matching
# runtime check on the built wheel is tests/test_package.py::test_openmp.
cmake_params+=(-DSUITESPARSE_USE_OPENMP=ON)
cmake_params+=(-DSUITESPARSE_USE_STRICT=ON)
# STRICT makes any requested-but-missing feature fatal, and SuiteSparsePolicy
# defaults both of these to ON, so they must be turned off explicitly or the
# configure step dies on "CUDA required for SuiteSparse but not found".
cmake_params+=(-DSUITESPARSE_USE_CUDA=OFF)
cmake_params+=(-DSUITESPARSE_USE_FORTRAN=OFF)

if [ -n "${BREW_LIBOMP}" ]; then
# macOS OpenMP flags.
# FindOpenMP doesn't find brew's libomp, so set the necessary configs manually.
Expand Down
9 changes: 9 additions & 0 deletions suitesparse_graphblas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
) from exc
del importlib, platform

# The SuiteSparse:GraphBLAS C library this binding was built against, as
# (major, minor, sub). ``__version__`` is the version of *this package*,
# which should normally match.
libgraphblas_version = (
lib.GxB_IMPLEMENTATION_MAJOR,
lib.GxB_IMPLEMENTATION_MINOR,
lib.GxB_IMPLEMENTATION_SUB,
)

# It is strongly recommended to use the non-variadic version of functions to be
# compatible with the most number of architectures. For example, you should use
# GxB_Matrix_Option_get_INT32 instead of GxB_Matrix_Option_get.
Expand Down
56 changes: 55 additions & 1 deletion suitesparse_graphblas/tests/test_package.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest

import suitesparse_graphblas
from suitesparse_graphblas import ffi, lib # noqa: F401
from suitesparse_graphblas import ffi, lib


def test_matrix_existence():
Expand All @@ -12,3 +14,55 @@ def test_version():
version = suitesparse_graphblas.__version__
version = [int(x) for x in version.split("+")[0].split(".")]
assert version > [9, 4, 4, 0]


def test_openmp():
# GraphBLAS only warns at configure time when OpenMP is missing and still
# builds a working, but serial, library, so a serial build is invisible
# from the outside: conda-forge's graphblas 10.5.0 shipped that way on
# osx-arm64. Wheels build GraphBLAS from source in suitesparse.sh, where
# macOS has to point CMake at Homebrew's libomp by hand, so the same slip
# is possible here.
val_ptr = ffi.new("int32_t*")
info = lib.GrB_Global_get_INT32(lib.GrB_GLOBAL, val_ptr, lib.GxB_LIBRARY_OPENMP)
assert info == lib.GrB_SUCCESS
assert val_ptr[0], "libgraphblas was built without OpenMP"


def test_libgraphblas_version_matches_the_loaded_library():
"""The GraphBLAS this extension was compiled against must be the one it loads.

``libgraphblas_version`` reads cffi ``#define`` constants, which are
resolved when the C extension is compiled against a particular
GraphBLAS.h. The values below come from the shared library actually loaded
at runtime. When those disagree, every cffi call is reading a struct laid
out by a different build -- a mismatch that surfaces later as an
unexplained ``GrB_OUT_OF_MEMORY`` or a crash, never as an import error.
"""
val_ptr = ffi.new("int32_t*")
runtime = []
for field in (
lib.GrB_LIBRARY_VER_MAJOR,
lib.GrB_LIBRARY_VER_MINOR,
lib.GrB_LIBRARY_VER_PATCH,
):
assert lib.GrB_Global_get_INT32(lib.GrB_GLOBAL, val_ptr, field) == lib.GrB_SUCCESS
runtime.append(val_ptr[0])
assert tuple(runtime) == suitesparse_graphblas.libgraphblas_version


def test_version_tracks_libgraphblas():
"""A released version's first three parts are the SuiteSparse:GraphBLAS version.

That convention is what lets a caller infer the library version from the
package version, and it only holds for a release: between releases the
version reports the previous tag, which is why ``libgraphblas_version``
exists and why anything gating on a library feature should read that
instead. Skipped on a development build rather than asserted, since
disagreeing there is the expected state, not a defect.
"""
version = suitesparse_graphblas.__version__
if "+" in version:
pytest.skip(f"development build between releases: {version}")
parts = tuple(int(x) for x in version.split(".")[:3])
assert parts == suitesparse_graphblas.libgraphblas_version
Loading