Skip to content

Commit a80853a

Browse files
committed
Strict cmake config to ensure openmp is used
1 parent 96bb9a6 commit a80853a

3 files changed

Lines changed: 78 additions & 1 deletion

File tree

suitesparse.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ if [ -z "${NPROC}" ]; then
2727
fi
2828

2929
cmake_params=()
30+
31+
# Fail the build if OpenMP is missing instead of silently producing a serial
32+
# library. GraphBLAS only warns and carries on, so a serial build stays
33+
# invisible until someone measures it: conda-forge's graphblas 10.5.0 shipped
34+
# that way on osx-arm64 while still depending on llvm-openmp. The matching
35+
# runtime check on the built wheel is tests/test_package.py::test_openmp.
36+
cmake_params+=(-DSUITESPARSE_USE_OPENMP=ON)
37+
cmake_params+=(-DSUITESPARSE_USE_STRICT=ON)
38+
# STRICT makes any requested-but-missing feature fatal, and SuiteSparsePolicy
39+
# defaults both of these to ON, so they must be turned off explicitly or the
40+
# configure step dies on "CUDA required for SuiteSparse but not found".
41+
cmake_params+=(-DSUITESPARSE_USE_CUDA=OFF)
42+
cmake_params+=(-DSUITESPARSE_USE_FORTRAN=OFF)
43+
3044
if [ -n "${BREW_LIBOMP}" ]; then
3145
# macOS OpenMP flags.
3246
# FindOpenMP doesn't find brew's libomp, so set the necessary configs manually.

suitesparse_graphblas/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@
2424
) from exc
2525
del importlib, platform
2626

27+
# The SuiteSparse:GraphBLAS C library this binding was built against, as
28+
# (major, minor, sub). ``__version__`` is the version of *this package*,
29+
# which should normally match.
30+
libgraphblas_version = (
31+
lib.GxB_IMPLEMENTATION_MAJOR,
32+
lib.GxB_IMPLEMENTATION_MINOR,
33+
lib.GxB_IMPLEMENTATION_SUB,
34+
)
35+
2736
# It is strongly recommended to use the non-variadic version of functions to be
2837
# compatible with the most number of architectures. For example, you should use
2938
# GxB_Matrix_Option_get_INT32 instead of GxB_Matrix_Option_get.

suitesparse_graphblas/tests/test_package.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import pytest
2+
13
import suitesparse_graphblas
2-
from suitesparse_graphblas import ffi, lib # noqa: F401
4+
from suitesparse_graphblas import ffi, lib
35

46

57
def test_matrix_existence():
@@ -12,3 +14,55 @@ def test_version():
1214
version = suitesparse_graphblas.__version__
1315
version = [int(x) for x in version.split("+")[0].split(".")]
1416
assert version > [9, 4, 4, 0]
17+
18+
19+
def test_openmp():
20+
# GraphBLAS only warns at configure time when OpenMP is missing and still
21+
# builds a working, but serial, library, so a serial build is invisible
22+
# from the outside: conda-forge's graphblas 10.5.0 shipped that way on
23+
# osx-arm64. Wheels build GraphBLAS from source in suitesparse.sh, where
24+
# macOS has to point CMake at Homebrew's libomp by hand, so the same slip
25+
# is possible here.
26+
val_ptr = ffi.new("int32_t*")
27+
info = lib.GrB_Global_get_INT32(lib.GrB_GLOBAL, val_ptr, lib.GxB_LIBRARY_OPENMP)
28+
assert info == lib.GrB_SUCCESS
29+
assert val_ptr[0], "libgraphblas was built without OpenMP"
30+
31+
32+
def test_libgraphblas_version_matches_the_loaded_library():
33+
"""The GraphBLAS this extension was compiled against must be the one it loads.
34+
35+
``libgraphblas_version`` reads cffi ``#define`` constants, which are
36+
resolved when the C extension is compiled against a particular
37+
GraphBLAS.h. The values below come from the shared library actually loaded
38+
at runtime. When those disagree, every cffi call is reading a struct laid
39+
out by a different build -- a mismatch that surfaces later as an
40+
unexplained ``GrB_OUT_OF_MEMORY`` or a crash, never as an import error.
41+
"""
42+
val_ptr = ffi.new("int32_t*")
43+
runtime = []
44+
for field in (
45+
lib.GrB_LIBRARY_VER_MAJOR,
46+
lib.GrB_LIBRARY_VER_MINOR,
47+
lib.GrB_LIBRARY_VER_PATCH,
48+
):
49+
assert lib.GrB_Global_get_INT32(lib.GrB_GLOBAL, val_ptr, field) == lib.GrB_SUCCESS
50+
runtime.append(val_ptr[0])
51+
assert tuple(runtime) == suitesparse_graphblas.libgraphblas_version
52+
53+
54+
def test_version_tracks_libgraphblas():
55+
"""A released version's first three parts are the SuiteSparse:GraphBLAS version.
56+
57+
That convention is what lets a caller infer the library version from the
58+
package version, and it only holds for a release: between releases the
59+
version reports the previous tag, which is why ``libgraphblas_version``
60+
exists and why anything gating on a library feature should read that
61+
instead. Skipped on a development build rather than asserted, since
62+
disagreeing there is the expected state, not a defect.
63+
"""
64+
version = suitesparse_graphblas.__version__
65+
if "+" in version:
66+
pytest.skip(f"development build between releases: {version}")
67+
parts = tuple(int(x) for x in version.split(".")[:3])
68+
assert parts == suitesparse_graphblas.libgraphblas_version

0 commit comments

Comments
 (0)